diff --git a/kyaml/fn/framework/command/example_test.go b/kyaml/fn/framework/command/example_test.go index 0badd3f9521..260f8785143 100644 --- a/kyaml/fn/framework/command/example_test.go +++ b/kyaml/fn/framework/command/example_test.go @@ -296,7 +296,7 @@ functionConfig: func ExampleBuild_validate() { fn := func(rl *framework.ResourceList) error { // validation results - var validationResults []framework.ResultItem + var validationResults framework.Results // validate that each Deployment resource has spec.replicas set for i := range rl.Items { @@ -319,7 +319,7 @@ func ExampleBuild_validate() { if r != nil { continue } - validationResults = append(validationResults, framework.ResultItem{ + validationResults = append(validationResults, &framework.Result{ Severity: framework.Error, Message: "field is required", ResourceRef: yaml.ResourceIdentifier{ @@ -334,13 +334,10 @@ func ExampleBuild_validate() { } if len(validationResults) > 0 { - rl.Result = &framework.Result{ - Name: "replicas-validator", - Items: validationResults, - } + rl.Results = validationResults } - return rl.Result + return rl.Results } cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneDisabled, true) @@ -370,15 +367,13 @@ items: // metadata: // name: foo // results: - // name: replicas-validator - // items: - // - message: field is required - // severity: error - // resourceRef: - // apiVersion: apps/v1 - // kind: Deployment - // name: foo - // field: - // path: spec.replicas - // suggestedValue: "1" + // - message: field is required + // severity: error + // resourceRef: + // apiVersion: apps/v1 + // kind: Deployment + // name: foo + // field: + // path: spec.replicas + // suggestedValue: "1" } diff --git a/kyaml/fn/framework/framework.go b/kyaml/fn/framework/framework.go index 12c30a2c221..0224404f838 100644 --- a/kyaml/fn/framework/framework.go +++ b/kyaml/fn/framework/framework.go @@ -17,6 +17,21 @@ import ( // This framework facilitates building functions that receive and emit ResourceLists, // as required by the specification. type ResourceList struct { + // Items is the ResourceList.items input and output value. + // + // e.g. given the function input: + // + // kind: ResourceList + // items: + // - kind: Deployment + // ... + // - kind: Service + // ... + // + // Items will be a slice containing the Deployment and Service resources + // Mutating functions will alter this field during processing. + Items []*yaml.RNode `yaml:"items" json:"items"` + // FunctionConfig is the ResourceList.functionConfig input value. // // e.g. given the input: @@ -33,25 +48,10 @@ type ResourceList struct { // foo: var FunctionConfig *yaml.RNode `yaml:"functionConfig" json:"functionConfig"` - // Items is the ResourceList.items input and output value. - // - // e.g. given the function input: - // - // kind: ResourceList - // items: - // - kind: Deployment - // ... - // - kind: Service - // ... - // - // Items will be a slice containing the Deployment and Service resources - // Mutating functions will alter this field during processing. - Items []*yaml.RNode `yaml:"items" json:"items"` - // Result is ResourceList.result output value. // Validating functions can optionally use this field to communicate structured // validation error data to downstream functions. - Result *Result `yaml:"results" json:"results"` + Results Results `yaml:"results" json:"results"` } // ResourceListProcessor is implemented by configuration functions built with this framework @@ -119,8 +119,8 @@ func Execute(p ResourceListProcessor, rlSource *kio.ByteReadWriter) error { // Write the results // Set the ResourceList.results for validating functions - if rl.Result != nil && len(rl.Result.Items) > 0 { - b, err := yaml.Marshal(rl.Result) + if len(rl.Results) > 0 { + b, err := yaml.Marshal(rl.Results) if err != nil { return errors.Wrap(err) } diff --git a/kyaml/fn/framework/framework_test.go b/kyaml/fn/framework/framework_test.go index 0a4eb1b8f34..426c5d593b7 100644 --- a/kyaml/fn/framework/framework_test.go +++ b/kyaml/fn/framework/framework_test.go @@ -16,39 +16,37 @@ import ( func TestExecute_Result(t *testing.T) { p := framework.ResourceListProcessorFunc(func(rl *framework.ResourceList) error { - err := &framework.Result{ - Name: "Incompatible config", - Items: []framework.ResultItem{ - { - Message: "bad value for replicas", - Severity: framework.Error, - ResourceRef: yaml.ResourceIdentifier{ - TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"}, - NameMeta: yaml.NameMeta{Name: "tester", Namespace: "default"}, - }, - Field: framework.Field{ - Path: ".spec.Replicas", - CurrentValue: "0", - SuggestedValue: "3", - }, - File: framework.File{ - Path: "/path/to/deployment.yaml", - Index: 0, - }, + err := &framework.Results{ + { + Message: "bad value for replicas", + Severity: framework.Error, + ResourceRef: yaml.ResourceIdentifier{ + TypeMeta: yaml.TypeMeta{APIVersion: "v1", Kind: "Deployment"}, + NameMeta: yaml.NameMeta{Name: "tester", Namespace: "default"}, }, - { - Message: "some error", - Severity: framework.Error, + Field: framework.Field{ + Path: ".spec.Replicas", + CurrentValue: "0", + SuggestedValue: "3", }, + File: framework.File{ + Path: "/path/to/deployment.yaml", + Index: 0, + }, + }, + { + Message: "some error", + Severity: framework.Error, + Tags: map[string]string{"foo": "bar"}, }, } - rl.Result = err + rl.Results = *err return err }) out := new(bytes.Buffer) source := &kio.ByteReadWriter{Reader: bytes.NewBufferString(` kind: ResourceList -apiVersion: config.kubernetes.io/v1alpha1 +apiVersion: config.kubernetes.io/v1 items: - kind: Deployment apiVersion: v1 @@ -62,8 +60,8 @@ items: assert.EqualError(t, err, `[error] v1/Deployment/default/tester .spec.Replicas: bad value for replicas [error] : some error`) - assert.Equal(t, 1, err.(*framework.Result).ExitCode()) - assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 + assert.Equal(t, 1, err.(*framework.Results).ExitCode()) + assert.Equal(t, `apiVersion: config.kubernetes.io/v1 kind: ResourceList items: - kind: Deployment @@ -74,21 +72,21 @@ items: spec: replicas: 0 results: - name: Incompatible config - items: - - message: bad value for replicas - severity: error - resourceRef: - apiVersion: v1 - kind: Deployment - name: tester - namespace: default - field: - path: .spec.Replicas - currentValue: "0" - suggestedValue: "3" - file: - path: /path/to/deployment.yaml - - message: some error - severity: error`, strings.TrimSpace(out.String())) +- message: bad value for replicas + severity: error + resourceRef: + apiVersion: v1 + kind: Deployment + name: tester + namespace: default + field: + path: .spec.Replicas + currentValue: "0" + suggestedValue: "3" + file: + path: /path/to/deployment.yaml +- message: some error + severity: error + tags: + foo: bar`, strings.TrimSpace(out.String())) } diff --git a/kyaml/fn/framework/result.go b/kyaml/fn/framework/result.go index 94e43ee3e3e..a64816beb21 100644 --- a/kyaml/fn/framework/result.go +++ b/kyaml/fn/framework/result.go @@ -23,7 +23,7 @@ const ( ) // ResultItem defines a validation result -type ResultItem struct { +type Result struct { // Message is a human readable message Message string `yaml:"message,omitempty"` @@ -38,10 +38,14 @@ type ResultItem struct { // File references a file containing the resource this result refers to File File `yaml:"file,omitempty"` + + // Tags is an unstructured key value map stored with a result that may be set + // by external tools to store and retrieve arbitrary metadata + Tags map[string]string `yaml:"tags,omitempty"` } // String provides a human-readable message for the result item -func (i ResultItem) String() string { +func (i Result) String() string { identifier := i.ResourceRef var idStringList []string if identifier.APIVersion != "" { @@ -79,33 +83,26 @@ type Field struct { Path string `yaml:"path,omitempty"` // CurrentValue is the current field value - CurrentValue string `yaml:"currentValue,omitempty"` + CurrentValue interface{} `yaml:"currentValue,omitempty"` // SuggestedValue is the suggested field value - SuggestedValue string `yaml:"suggestedValue,omitempty"` + SuggestedValue interface{} `yaml:"suggestedValue,omitempty"` } -// Result defines a function result which will be set on the emitted ResourceList -type Result struct { - // Name is the name of the function creating the result - Name string `yaml:"name,omitempty"` - - // Items are the individual results - Items []ResultItem `yaml:"items,omitempty"` -} +type Results []*Result -// Error enables a Result to be returned as an error -func (e Result) Error() string { +// Error enables Results to be returned as an error +func (e Results) Error() string { var msgs []string - for _, i := range e.Items { + for _, i := range e { msgs = append(msgs, i.String()) } return strings.Join(msgs, "\n\n") } // ExitCode provides the exit code based on the result's severity -func (e Result) ExitCode() int { - for _, i := range e.Items { +func (e Results) ExitCode() int { + for _, i := range e { if i.Severity == Error { return 1 }