Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[breaking] update results field of ResourceList to implement function spec v1 #4248

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 15 additions & 20 deletions kyaml/fn/framework/command/example_test.go
Expand Up @@ -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 {
Expand All @@ -319,28 +319,25 @@ 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{
TypeMeta: meta.TypeMeta,
NameMeta: meta.ObjectMeta.NameMeta,
},
Field: framework.Field{
Path: "spec.replicas",
SuggestedValue: "1",
Path: "spec.replicas",
ProposedValue: "1",
},
})
}

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)
Expand Down Expand Up @@ -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
// proposedValue: "1"
}
39 changes: 20 additions & 19 deletions kyaml/fn/framework/framework.go
Expand Up @@ -17,6 +17,22 @@ 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.
natasha41575 marked this conversation as resolved.
Show resolved Hide resolved
//
// 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.
// This field is required.
Items []*yaml.RNode `yaml:"items" json:"items"`
natasha41575 marked this conversation as resolved.
Show resolved Hide resolved

// FunctionConfig is the ResourceList.functionConfig input value.
//
// e.g. given the input:
Expand All @@ -33,25 +49,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.
// Results is ResourceList.results 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
Expand Down Expand Up @@ -119,8 +120,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)
}
Expand Down
80 changes: 39 additions & 41 deletions kyaml/fn/framework/framework_test.go
Expand Up @@ -16,33 +16,31 @@ 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",
ProposedValue: "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)
Expand All @@ -62,7 +60,7 @@ 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, 1, err.(*framework.Results).ExitCode())
assert.Equal(t, `apiVersion: config.kubernetes.io/v1
kind: ResourceList
items:
Expand All @@ -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"
proposedValue: "3"
file:
path: /path/to/deployment.yaml
- message: some error
severity: error
tags:
foo: bar`, strings.TrimSpace(out.String()))
}
59 changes: 29 additions & 30 deletions kyaml/fn/framework/result.go
Expand Up @@ -23,25 +23,30 @@ const (
)

// ResultItem defines a validation result
type ResultItem struct {
// Message is a human readable message
Message string `yaml:"message,omitempty"`
type Result struct {
// Message is a human readable message. This field is required.
Message string `yaml:"message,omitempty" json:"message,omitempty"`

// Severity is the severity of this result
Severity Severity `yaml:"severity,omitempty"`
Severity Severity `yaml:"severity,omitempty" json:"severity,omitempty"`

// ResourceRef is a reference to a resource
ResourceRef yaml.ResourceIdentifier `yaml:"resourceRef,omitempty"`
// ResourceRef is a reference to a resource.
// Required fields: apiVersion, kind, name.
ResourceRef yaml.ResourceIdentifier `yaml:"resourceRef,omitempty" json:"resourceRef,omitempty"`

// Field is a reference to the field in a resource this result refers to
Field Field `yaml:"field,omitempty"`
Field Field `yaml:"field,omitempty" json:"field,omitempty"`

// File references a file containing the resource this result refers to
File File `yaml:"file,omitempty"`
File File `yaml:"file,omitempty" json:"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" json:"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 != "" {
Expand All @@ -65,47 +70,41 @@ func (i ResultItem) String() string {

// File references a file containing a resource
type File struct {
// Path is relative path to the file containing the resource
Path string `yaml:"path,omitempty"`
// Path is relative path to the file containing the resource.
// This field is required.
Path string `yaml:"path,omitempty" json:"path,omitempty"`

// Index is the index into the file containing the resource
// (i.e. if there are multiple resources in a single file)
Index int `yaml:"index,omitempty"`
Index int `yaml:"index,omitempty" json:"index,omitempty"`
}

// Field references a field in a resource
type Field struct {
// Path is the field path
Path string `yaml:"path,omitempty"`
// Path is the field path. This field is required.
Path string `yaml:"path,omitempty" json:"path,omitempty"`

// CurrentValue is the current field value
CurrentValue string `yaml:"currentValue,omitempty"`
CurrentValue interface{} `yaml:"currentValue,omitempty" json:"currentValue,omitempty"`

// SuggestedValue is the suggested field value
SuggestedValue string `yaml:"suggestedValue,omitempty"`
// ProposedValue is the proposed value of the field to fix an issue.
ProposedValue interface{} `yaml:"proposedValue,omitempty" json:"proposedValue,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
}
Expand Down