Enable functions to defer failures

so multiple validators can be run sequentially without shortcircuiting the pipeline
This commit is contained in:
Phillip Wittrock
2020-04-17 20:26:56 -07:00
parent c7b00733c1
commit 6d9702561a
5 changed files with 254 additions and 20 deletions

View File

@@ -151,6 +151,10 @@ type ContainerFilter struct {
Results *yaml.RNode
DeferFailure bool
Exit error
// SetFlowStyleForConfig sets the style for config to Flow when serializing it
SetFlowStyleForConfig bool
@@ -160,7 +164,18 @@ type ContainerFilter struct {
checkInput func(string)
}
func (c ContainerFilter) GetExit() error {
return c.Exit
}
type DeferFailureFunction interface {
GetExit() error
}
func (c ContainerFilter) String() string {
if c.DeferFailure {
return fmt.Sprintf("%s deferFailure: %v", c.Image, c.DeferFailure)
}
return c.Image
}
@@ -300,18 +315,9 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
}
cmd.Stdin = in
cmd.Stdout = out
if err := cmd.Run(); err != nil {
// write the results file on failure
results, e := r.Read()
if e != nil {
return nil, e
}
if e = c.doResults(r); e != nil {
return nil, e
}
// return the results from the function even on failure
return results, err
}
// don't exit immediately if the function fails -- write out the validation
c.Exit = cmd.Run()
output, err := r.Read()
if err != nil {
@@ -322,6 +328,10 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, err
}
if c.Exit != nil && !c.DeferFailure {
return append(output, saved...), c.Exit
}
// annotate any generated Resources with a path and index if they don't already have one
if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil {
return nil, err
@@ -380,6 +390,7 @@ func (c *ContainerFilter) getArgs() []string {
// tell functions to write error messages to stderr as well as results
os.Setenv("LOG_TO_STDERR", "true")
os.Setenv("STRUCTURED_RESULTS", "true")
// export the local environment vars to the container
for _, pair := range os.Environ() {

View File

@@ -18,13 +18,14 @@ import (
func TestContainerFilter_Filter(t *testing.T) {
var tests = []struct {
name string
input []string
expectedOutput []string
expectedError string
expectedResults string
noMakeResultsFile bool
instance ContainerFilter
name string
input []string
expectedOutput []string
expectedError string
expectedSavedError string
expectedResults string
noMakeResultsFile bool
instance ContainerFilter
}{
{
name: "add_path_annotation",
@@ -220,6 +221,87 @@ metadata:
`,
},
{
name: "write_results_defer_failure",
expectedSavedError: "exit status 1",
instance: ContainerFilter{
DeferFailure: true,
args: []string{"sh", "-c",
`echo '
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-foo
- apiVersion: v1
kind: Service
metadata:
name: service-foo
results:
- apiVersion: config.k8s.io/v1alpha1
kind: ObjectError
name: "some-validator"
items:
- type: error
message: "some message"
resourceRef:
apiVersion: apps/v1
kind: Deployment
name: foo
namespace: bar
file:
path: deploy.yaml
index: 0
field:
path: "spec.template.spec.containers[3].resources.limits.cpu"
currentValue: "200"
suggestedValue: "2"
' && cat not-real-dir
`,
},
},
expectedOutput: []string{
`
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
`,
`
apiVersion: v1
kind: Service
metadata:
name: service-foo
annotations:
config.kubernetes.io/path: 'service_service-foo.yaml'
`,
},
expectedResults: `
- apiVersion: config.k8s.io/v1alpha1
kind: ObjectError
name: "some-validator"
items:
- type: error
message: "some message"
resourceRef:
apiVersion: apps/v1
kind: Deployment
name: foo
namespace: bar
file:
path: deploy.yaml
index: 0
field:
path: "spec.template.spec.containers[3].resources.limits.cpu"
currentValue: "200"
suggestedValue: "2"
`,
},
{
name: "write_results_non_0_exit_missing_file",
expectedError: "open /not/real/file: no such file or directory",
@@ -332,6 +414,13 @@ metadata:
return
}
if tt.expectedSavedError != "" {
if !assert.EqualError(t, tt.instance.Exit, tt.expectedSavedError) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}

View File

@@ -19,6 +19,8 @@ type FunctionSpec struct {
// Network is the name of the network to use from a container
Network string `json:"network,omitempty" yaml:"network,omitempty"`
DeferFailure bool `json:"deferFailure,omitempty" yaml:"deferFailure,omitempty"`
// Container is the spec for running a function as a container
Container ContainerSpec `json:"container,omitempty" yaml:"container,omitempty"`