Merge pull request #2362 from pwittrock/validation-spec

Support for serializing ResourceList.results field from functions
This commit is contained in:
Kubernetes Prow Robot
2020-04-17 09:23:06 -07:00
committed by GitHub
10 changed files with 1199 additions and 461 deletions

View File

@@ -51,6 +51,9 @@ func GetRunFnRunner(name string) *RunFnRunner {
&r.StarName, "star-name", "", "name of starlark program.") &r.StarName, "star-name", "", "name of starlark program.")
r.Command.Flags().MarkHidden("star-name") r.Command.Flags().MarkHidden("star-name")
r.Command.Flags().StringVar(
&r.ResultsDir, "results-dir", "", "write function results to this dir")
r.Command.Flags().BoolVar( r.Command.Flags().BoolVar(
&r.Network, "network", false, "enable network access for functions that declare it") &r.Network, "network", false, "enable network access for functions that declare it")
r.Command.Flags().StringVar( r.Command.Flags().StringVar(
@@ -77,6 +80,7 @@ type RunFnRunner struct {
StarPath string StarPath string
StarName string StarName string
RunFns runfn.RunFns RunFns runfn.RunFns
ResultsDir string
Network bool Network bool
NetworkName string NetworkName string
Mounts []string Mounts []string
@@ -267,6 +271,7 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
NetworkName: r.NetworkName, NetworkName: r.NetworkName,
EnableStarlark: r.EnableStar, EnableStarlark: r.EnableStar,
StorageMounts: storageMounts, StorageMounts: storageMounts,
ResultsDir: r.ResultsDir,
} }
// don't consider args for the function // don't consider args for the function

View File

@@ -11,23 +11,25 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/runfn"
) )
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline // TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
// flags and arguments into the RunFns structure to be executed. // flags and arguments into the RunFns structure to be executed.
func TestRunFnCommand_preRunE(t *testing.T) { func TestRunFnCommand_preRunE(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
args []string args []string
expected string expected string
err string expectedStruct *runfn.RunFns
path string err string
input io.Reader path string
output io.Writer input io.Reader
functionPaths []string output io.Writer
network bool functionPaths []string
networkName string network bool
mount []string networkName string
mount []string
}{ }{
{ {
name: "config map", name: "config map",
@@ -234,6 +236,26 @@ metadata:
data: {g: h, i: j=k} data: {g: h, i: j=k}
kind: Foo kind: Foo
apiVersion: v1 apiVersion: v1
`,
},
{
name: "results_dir",
args: []string{"run", "dir", "--results-dir", "foo/", "--image", "foo:bar", "--", "a=b", "c=d", "e=f"},
path: "dir",
expectedStruct: &runfn.RunFns{
Path: "dir",
NetworkName: "bridge",
ResultsDir: "foo/",
},
expected: `
metadata:
name: function-input
annotations:
config.kubernetes.io/function: |
container: {image: 'foo:bar'}
data: {a: b, c: d, e: f}
kind: ConfigMap
apiVersion: v1
`, `,
}, },
{ {
@@ -324,6 +346,10 @@ apiVersion: v1
t.FailNow() t.FailNow()
} }
if !assert.Equal(t, r.RunFns, r.RunFns) {
t.FailNow()
}
if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) { if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) {
t.FailNow() t.FailNow()
} }
@@ -339,6 +365,14 @@ apiVersion: v1
} }
} }
if tt.expectedStruct != nil {
r.RunFns.Functions = nil
tt.expectedStruct.FunctionPaths = tt.functionPaths
if !assert.Equal(t, *tt.expectedStruct, r.RunFns) {
t.FailNow()
}
}
}) })
} }

View File

@@ -41,6 +41,8 @@ type ByteReadWriter struct {
FunctionConfig *yaml.RNode FunctionConfig *yaml.RNode
Results *yaml.RNode
WrappingAPIVersion string WrappingAPIVersion string
WrappingKind string WrappingKind string
} }
@@ -52,6 +54,7 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
} }
val, err := b.Read() val, err := b.Read()
rw.FunctionConfig = b.FunctionConfig rw.FunctionConfig = b.FunctionConfig
rw.Results = b.Results
rw.WrappingAPIVersion = b.WrappingAPIVersion rw.WrappingAPIVersion = b.WrappingAPIVersion
rw.WrappingKind = b.WrappingKind rw.WrappingKind = b.WrappingKind
return val, errors.Wrap(err) return val, errors.Wrap(err)
@@ -63,6 +66,7 @@ func (rw *ByteReadWriter) Write(nodes []*yaml.RNode) error {
KeepReaderAnnotations: rw.KeepReaderAnnotations, KeepReaderAnnotations: rw.KeepReaderAnnotations,
Style: rw.Style, Style: rw.Style,
FunctionConfig: rw.FunctionConfig, FunctionConfig: rw.FunctionConfig,
Results: rw.Results,
WrappingAPIVersion: rw.WrappingAPIVersion, WrappingAPIVersion: rw.WrappingAPIVersion,
WrappingKind: rw.WrappingKind, WrappingKind: rw.WrappingKind,
}.Write(nodes) }.Write(nodes)
@@ -85,6 +89,8 @@ type ByteReader struct {
FunctionConfig *yaml.RNode FunctionConfig *yaml.RNode
Results *yaml.RNode
// DisableUnwrapping prevents Resources in Lists and ResourceLists from being unwrapped // DisableUnwrapping prevents Resources in Lists and ResourceLists from being unwrapped
DisableUnwrapping bool DisableUnwrapping bool
@@ -142,10 +148,12 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
r.WrappingAPIVersion = meta.APIVersion r.WrappingAPIVersion = meta.APIVersion
// unwrap the list // unwrap the list
fc := node.Field("functionConfig") if fc := node.Field("functionConfig"); fc != nil {
if fc != nil {
r.FunctionConfig = fc.Value r.FunctionConfig = fc.Value
} }
if res := node.Field("results"); res != nil {
r.Results = res.Value
}
items := node.Field("items") items := node.Field("items")
if items != nil { if items != nil {

View File

@@ -5,36 +5,64 @@ package kio_test
import ( import (
"bytes" "bytes"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
. "sigs.k8s.io/kustomize/kyaml/kio" . "sigs.k8s.io/kustomize/kyaml/kio"
) )
// getByteReaderTestInput returns test input func TestByteReader(t *testing.T) {
func getByteReaderTestInput(t *testing.T) *bytes.Buffer { type testCase struct {
b := &bytes.Buffer{} name string
_, err := b.WriteString(` input string
--- err string
a: b # first resource expectedItems []string
c: d expectedFunctionConfig string
--- expectedResults string
# second resource wrappingAPIVersion string
e: f wrappingAPIKind string
g: instance ByteReader
- h
---
---
i: j
`)
if !assert.NoError(t, err) {
assert.FailNow(t, "")
} }
return b
}
func TestByteReader_Read_wrappedResourceßßList(t *testing.T) { testCases := []testCase{
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1 //
//
//
{
name: "wrapped_resource_list",
input: `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
expectedItems: []string{
`kind: Deployment
spec:
replicas: 1
`,
`kind: Service
spec:
selectors:
foo: bar
`,
},
wrappingAPIVersion: ResourceListAPIVersion,
wrappingAPIKind: ResourceListKind,
},
//
//
//
{
name: "wrapped_resource_list_function_config",
input: `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList kind: ResourceList
functionConfig: functionConfig:
foo: bar foo: bar
@@ -50,109 +78,87 @@ items:
spec: spec:
selectors: selectors:
foo: bar foo: bar
`)} `,
nodes, err := r.Read() expectedItems: []string{
if !assert.NoError(t, err) { `kind: Deployment
return
}
// verify the contents
if !assert.Len(t, nodes, 2) {
return
}
expected := []string{
`kind: Deployment
spec: spec:
replicas: 1 replicas: 1
`, `,
`kind: Service `kind: Service
spec: spec:
selectors: selectors:
foo: bar foo: bar
`, `,
} },
for i := range nodes { expectedFunctionConfig: `foo: bar
if !assert.Equal(t, expected[i], nodes[i].MustString()) {
return
}
}
// verify the function config
assert.Equal(t, `foo: bar
elems: elems:
- a - a
- b - b
- c - c`,
`, r.FunctionConfig.MustString()) wrappingAPIVersion: ResourceListAPIVersion,
wrappingAPIKind: ResourceListKind,
},
assert.Equal(t, ResourceListKind, r.WrappingKind) //
assert.Equal(t, ResourceListAPIVersion, r.WrappingAPIVersion) //
} //
{
func TestByteReader_Read_wrappedList(t *testing.T) { name: "wrapped_list",
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: v1 input: `
apiVersion: v1
kind: List kind: List
items: items:
- kind: Deployment - kind: Deployment
spec: spec:
replicas: 1 replicas: 1
- kind: Service - kind: Service
spec: spec:
selectors: selectors:
foo: bar foo: bar
`)} `,
nodes, err := r.Read() expectedItems: []string{
if !assert.NoError(t, err) { `
return kind: Deployment
}
// verify the contents
if !assert.Len(t, nodes, 2) {
return
}
expected := []string{
`kind: Deployment
spec: spec:
replicas: 1 replicas: 1
`, `,
`kind: Service `
kind: Service
spec: spec:
selectors: selectors:
foo: bar foo: bar
`, `,
} },
for i := range nodes { wrappingAPIKind: "List",
if !assert.Equal(t, expected[i], nodes[i].MustString()) { wrappingAPIVersion: "v1",
return },
}
}
// verify the function config //
assert.Nil(t, r.FunctionConfig) //
assert.Equal(t, "List", r.WrappingKind) //
assert.Equal(t, "v1", r.WrappingAPIVersion) {
} name: "unwrapped_items",
input: `
// TestByteReader_Read tests the default Read behavior ---
// - Resources are read into a slice a: b # first resource
// - ReaderAnnotations are set on the ResourceNodes c: d
func TestByteReader_Read(t *testing.T) { ---
nodes, err := (&ByteReader{Reader: getByteReaderTestInput(t)}).Read() # second resource
if !assert.NoError(t, err) { e: f
return g:
} - h
---
if !assert.Len(t, nodes, 3) { ---
return i: j
} `,
expected := []string{ expectedItems: []string{
`a: b # first resource `a: b # first resource
c: d c: d
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: '0' config.kubernetes.io/index: '0'
`, `,
`# second resource `# second resource
e: f e: f
g: g:
- h - h
@@ -160,150 +166,209 @@ metadata:
annotations: annotations:
config.kubernetes.io/index: '1' config.kubernetes.io/index: '1'
`, `,
`i: j `i: j
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: '2' config.kubernetes.io/index: '2'
`, `,
} },
for i := range nodes { },
val, err := nodes[i].String()
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, expected[i], val) {
return
}
}
}
// TestByteReader_Read_omitReaderAnnotations tests //
// - Resources are read into a slice //
// - ReaderAnnotations are not set on the ResourceNodes //
func TestByteReader_Read_omitReaderAnnotations(t *testing.T) { {
nodes, err := (&ByteReader{ name: "omit_annotations",
Reader: getByteReaderTestInput(t), input: `
OmitReaderAnnotations: true}).Read() ---
if !assert.NoError(t, err) { a: b # first resource
return
}
// should have parsed 3 resources
if !assert.Len(t, nodes, 3) {
return
}
expected := []string{
"a: b # first resource\nc: d\n",
"# second resource\ne: f\ng:\n- h\n",
"i: j\n",
}
for i := range nodes {
val, err := nodes[i].String()
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, expected[i], val) {
return
}
}
}
// TestByteReader_Read_omitReaderAnnotations tests
// - Resources are read into a slice
// - ReaderAnnotations are NOT set on the ResourceNodes
// - Additional annotations ARE set on the ResourceNodes
func TestByteReader_Read_setAnnotationsOmitReaderAnnotations(t *testing.T) {
nodes, err := (&ByteReader{
Reader: getByteReaderTestInput(t),
SetAnnotations: map[string]string{"foo": "bar"},
OmitReaderAnnotations: true,
}).Read()
if !assert.NoError(t, err) {
return
}
if !assert.Len(t, nodes, 3) {
return
}
expected := []string{
`a: b # first resource
c: d c: d
metadata: ---
annotations: # second resource
foo: 'bar'
`,
`# second resource
e: f e: f
g: g:
- h - h
metadata: ---
annotations: ---
foo: 'bar' i: j
`, `,
`i: j expectedItems: []string{
metadata: `
annotations: a: b # first resource
foo: 'bar' c: d
`, `,
} `
for i := range nodes { # second resource
val, err := nodes[i].String() e: f
if !assert.NoError(t, err) { g:
return - h
} `,
if !assert.Equal(t, expected[i], val) { `
return i: j
} `,
} },
} instance: ByteReader{OmitReaderAnnotations: true},
},
// TestByteReader_Read_omitReaderAnnotations tests //
// - Resources are read into a slice //
// - ReaderAnnotations ARE set on the ResourceNodes //
// - Additional annotations ARE set on the ResourceNodes {
func TestByteReader_Read_setAnnotations(t *testing.T) { name: "no_omit_annotations",
nodes, err := (&ByteReader{ input: `
Reader: getByteReaderTestInput(t), ---
SetAnnotations: map[string]string{"foo": "bar"}, a: b # first resource
}).Read() c: d
if !assert.NoError(t, err) { ---
return # second resource
} e: f
g:
if !assert.Len(t, nodes, 3) { - h
return ---
} ---
expected := []string{ i: j
`a: b # first resource `,
expectedItems: []string{
`
a: b # first resource
c: d c: d
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: '0' config.kubernetes.io/index: '0'
foo: 'bar'
`, `,
`# second resource `
# second resource
e: f e: f
g: g:
- h - h
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: '1' config.kubernetes.io/index: '1'
foo: 'bar'
`, `,
`i: j `
i: j
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: '2' config.kubernetes.io/index: '2'
`,
},
instance: ByteReader{},
},
//
//
//
{
name: "set_annotation",
input: `
---
a: b # first resource
c: d
---
# second resource
e: f
g:
- h
---
---
i: j
`,
expectedItems: []string{
`a: b # first resource
c: d
metadata:
annotations:
foo: 'bar' foo: 'bar'
`, `,
`# second resource
e: f
g:
- h
metadata:
annotations:
foo: 'bar'
`,
`i: j
metadata:
annotations:
foo: 'bar'
`,
},
instance: ByteReader{
OmitReaderAnnotations: true,
SetAnnotations: map[string]string{"foo": "bar"}},
},
} }
for i := range nodes {
val, err := nodes[i].String() for i := range testCases {
if !assert.NoError(t, err) { tc := testCases[i]
return t.Run(tc.name, func(t *testing.T) {
} r := tc.instance
if !assert.Equal(t, expected[i], val) { r.Reader = bytes.NewBufferString(tc.input)
return nodes, err := r.Read()
} if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
// verify the contents
if !assert.Len(t, nodes, len(tc.expectedItems)) {
t.FailNow()
}
for i := range nodes {
actual, err := nodes[i].String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedItems[i]),
strings.TrimSpace(actual)) {
t.FailNow()
}
}
// verify the function config
if tc.expectedFunctionConfig != "" {
actual, err := r.FunctionConfig.String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedFunctionConfig),
strings.TrimSpace(actual)) {
t.FailNow()
}
} else if !assert.Nil(t, r.FunctionConfig) {
t.FailNow()
}
if tc.expectedResults != "" {
actual, err := r.Results.String()
actual = strings.TrimSpace(actual)
if !assert.NoError(t, err) {
t.FailNow()
}
tc.expectedResults = strings.TrimSpace(tc.expectedResults)
if !assert.Equal(t, tc.expectedResults, actual) {
t.FailNow()
}
} else if !assert.Nil(t, r.Results) {
t.FailNow()
}
if !assert.Equal(t, tc.wrappingAPIKind, r.WrappingKind) {
t.FailNow()
}
if !assert.Equal(t, tc.wrappingAPIVersion, r.WrappingAPIVersion) {
t.FailNow()
}
})
} }
} }

View File

@@ -0,0 +1,263 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio_test
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func TestByteReadWriter(t *testing.T) {
type testCase struct {
name string
err string
input string
expectedOutput string
instance kio.ByteReadWriter
}
testCases := []testCase{
{
name: "round_trip",
input: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
expectedOutput: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
},
{
name: "function_config",
input: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
functionConfig:
a: b # something
`,
expectedOutput: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
functionConfig:
a: b # something
`,
},
{
name: "results",
input: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
results:
a: b # something
`,
expectedOutput: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
results:
a: b # something
`,
},
{
name: "drop_invalid_resource_list_field",
input: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
foo:
a: b # something
`,
expectedOutput: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
},
{
name: "list",
input: `
apiVersion: v1
kind: List
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
expectedOutput: `
apiVersion: v1
kind: List
items:
- kind: Deployment
spec:
replicas: 1
- kind: Service
spec:
selectors:
foo: bar
`,
},
{
name: "multiple_documents",
input: `
kind: Deployment
spec:
replicas: 1
---
kind: Service
spec:
selectors:
foo: bar
`,
expectedOutput: `
kind: Deployment
spec:
replicas: 1
---
kind: Service
spec:
selectors:
foo: bar
`,
},
{
name: "keep_annotations",
input: `
kind: Deployment
spec:
replicas: 1
---
kind: Service
spec:
selectors:
foo: bar
`,
expectedOutput: `
kind: Deployment
spec:
replicas: 1
metadata:
annotations:
config.kubernetes.io/index: '0'
---
kind: Service
spec:
selectors:
foo: bar
metadata:
annotations:
config.kubernetes.io/index: '1'
`,
instance: kio.ByteReadWriter{KeepReaderAnnotations: true},
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
var in, out bytes.Buffer
in.WriteString(tc.input)
w := tc.instance
w.Writer = &out
w.Reader = &in
nodes, err := w.Read()
if !assert.NoError(t, err) {
t.FailNow()
}
err = w.Write(nodes)
if !assert.NoError(t, err) {
t.FailNow()
}
if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
t.FailNow()
}
return
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}

View File

@@ -30,6 +30,8 @@ type ByteWriter struct {
// wrap the results in an ResourceList. // wrap the results in an ResourceList.
FunctionConfig *yaml.RNode FunctionConfig *yaml.RNode
Results *yaml.RNode
// WrappingKind if set will cause ByteWriter to wrap the Resources in // WrappingKind if set will cause ByteWriter to wrap the Resources in
// an 'items' field in this kind. e.g. if WrappingKind is 'List', // an 'items' field in this kind. e.g. if WrappingKind is 'List',
// ByteWriter will wrap the Resources in a List .items field. // ByteWriter will wrap the Resources in a List .items field.
@@ -112,6 +114,11 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
&yaml.Node{Kind: yaml.ScalarNode, Value: "functionConfig"}, &yaml.Node{Kind: yaml.ScalarNode, Value: "functionConfig"},
w.FunctionConfig.YNode()) w.FunctionConfig.YNode())
} }
if w.Results != nil {
list.Content = append(list.Content,
&yaml.Node{Kind: yaml.ScalarNode, Value: "results"},
w.Results.YNode())
}
doc := &yaml.Node{ doc := &yaml.Node{
Kind: yaml.DocumentNode, Kind: yaml.DocumentNode,
Content: []*yaml.Node{list}} Content: []*yaml.Node{list}}

View File

@@ -1,96 +1,84 @@
// Copyright 2019 The Kubernetes Authors. // Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
package kio package kio_test
import ( import (
"bytes" "bytes"
"strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/kio"
. "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml"
) )
// TestByteWriter_Write_withoutAnnotations tests: func TestByteWriter(t *testing.T) {
// - Resource Config ordering is preserved if no annotations are present type testCase struct {
func TestByteWriter_Write_wrapped(t *testing.T) { name string
node1, err := yaml.Parse(`a: b #first err string
`) items []string
if !assert.NoError(t, err) { functionConfig string
return results string
expectedOutput string
instance kio.ByteWriter
} }
node2, err := yaml.Parse(`c: d # second
`) testCases := []testCase{
if !assert.NoError(t, err) { //
return //
} //
node3, err := yaml.Parse(`e: f {
name: "wrap_resource_list",
instance: ByteWriter{
Sort: true,
WrappingKind: ResourceListKind,
WrappingAPIVersion: ResourceListAPIVersion,
},
items: []string{
`a: b #first`,
`c: d # second`,
},
functionConfig: `
e: f
g: g:
h: h:
- i # has a list - i # has a list
- j - j`,
`) expectedOutput: `apiVersion: config.kubernetes.io/v1alpha1
if !assert.NoError(t, err) {
return
}
buff := &bytes.Buffer{}
err = ByteWriter{
Sort: true,
Writer: buff,
FunctionConfig: node3,
WrappingKind: ResourceListKind,
WrappingAPIVersion: ResourceListAPIVersion}.
Write([]*yaml.RNode{node2, node1})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList kind: ResourceList
items: items:
- c: d # second
- a: b #first - a: b #first
- c: d # second
functionConfig: functionConfig:
e: f e: f
g: g:
h: h:
- i # has a list - i # has a list
- j - j
`, buff.String()) `,
} },
// TestByteWriter_Write_withoutAnnotations tests: //
// - Resource Config ordering is preserved if no annotations are present //
func TestByteWriter_Write_withoutAnnotations(t *testing.T) { //
node1, err := yaml.Parse(`a: b #first {
`) name: "multiple_items",
if !assert.NoError(t, err) { items: []string{
return `c: d # second`,
} `e: f
node2, err := yaml.Parse(`c: d # second
`)
if !assert.NoError(t, err) {
return
}
node3, err := yaml.Parse(`e: f
g: g:
h: h:
# has a list # has a list
- i : [i1, i2] # line comment - i : [i1, i2] # line comment
# has a list 2 # has a list 2
- j : j1 - j : j1
`) `,
if !assert.NoError(t, err) { `a: b #first`,
return },
} expectedOutput: `
c: d # second
buff := &bytes.Buffer{}
err = ByteWriter{Writer: buff}.
Write([]*yaml.RNode{node2, node3, node1})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `c: d # second
--- ---
e: f e: f
g: g:
@@ -101,32 +89,23 @@ g:
- j: j1 - j: j1
--- ---
a: b #first a: b #first
`, buff.String()) `,
} },
// TestByteWriter_Write_withAnnotationsKeepAnnotations tests: //
// - Resource Config is sorted by annotations if present // Test Case
// - IndexAnnotations are retained //
func TestByteWriter_Write_withAnnotationsKeepAnnotations(t *testing.T) { {
node1, err := yaml.Parse(`a: b #first name: "sort_keep_annotation",
instance: ByteWriter{Sort: true, KeepReaderAnnotations: true},
items: []string{
`a: b #first
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: 0 config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml" config.kubernetes.io/path: "a/b/a_test.yaml"
`) `,
if !assert.NoError(t, err) { `e: f
return
}
node2, err := yaml.Parse(`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
`)
if !assert.NoError(t, err) {
return
}
node3, err := yaml.Parse(`e: f
g: g:
h: h:
- i # has a list - i # has a list
@@ -135,18 +114,16 @@ metadata:
annotations: annotations:
config.kubernetes.io/index: 0 config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml" config.kubernetes.io/path: "a/b/b_test.yaml"
`) `,
if !assert.NoError(t, err) { `c: d # second
return metadata:
} annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
`,
},
buff := &bytes.Buffer{} expectedOutput: `a: b #first
err = ByteWriter{Sort: true, Writer: buff, KeepReaderAnnotations: true}.
Write([]*yaml.RNode{node2, node3, node1})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `a: b #first
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: 0 config.kubernetes.io/index: 0
@@ -167,109 +144,36 @@ metadata:
annotations: annotations:
config.kubernetes.io/index: 0 config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml" config.kubernetes.io/path: "a/b/b_test.yaml"
`, buff.String()) `,
} },
// TestByteWriter_Write_withAnnotations tests: //
// - Resource Config is sorted by annotations if present // Test Case
// - IndexAnnotations are pruned //
func TestByteWriter_Write_withAnnotations(t *testing.T) { {
node1, err := yaml.Parse(`a: b #first name: "sort_partial_annotations",
instance: ByteWriter{Sort: true},
items: []string{
`a: b #first
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml" config.kubernetes.io/path: "a/b/a_test.yaml"
`) `,
if !assert.NoError(t, err) { `c: d # second
return
}
node2, err := yaml.Parse(`c: d # second
metadata: metadata:
annotations: annotations:
config.kubernetes.io/index: 1 config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml" config.kubernetes.io/path: "a/b/a_test.yaml"
`) `,
if !assert.NoError(t, err) { `e: f
return
}
node3, err := yaml.Parse(`e: f
g: g:
h: h:
- i # has a list - i # has a list
- j - j
metadata: `,
annotations: },
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
`)
if !assert.NoError(t, err) {
return
}
buff := &bytes.Buffer{} expectedOutput: `e: f
err = ByteWriter{Sort: true, Writer: buff}.
Write([]*yaml.RNode{node2, node3, node1})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `a: b #first
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
---
c: d # second
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
---
e: f
g:
h:
- i # has a list
- j
metadata:
annotations:
config.kubernetes.io/path: "a/b/b_test.yaml"
`, buff.String())
}
// TestByteWriter_Write_partialValues tests:
// - Resource Config is sorted when annotations are present on some but not all ResourceNodes
func TestByteWriter_Write_partialAnnotations(t *testing.T) {
node1, err := yaml.Parse(`a: b #first
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
`)
if !assert.NoError(t, err) {
return
}
node2, err := yaml.Parse(`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
`)
if !assert.NoError(t, err) {
return
}
node3, err := yaml.Parse(`e: f
g:
h:
- i # has a list
- j
`)
if !assert.NoError(t, err) {
return
}
buff := &bytes.Buffer{}
rw := ByteWriter{Sort: true, Writer: buff}
err = rw.Write([]*yaml.RNode{node2, node3, node1})
if !assert.NoError(t, err) {
return
}
assert.Equal(t, `e: f
g: g:
h: h:
- i # has a list - i # has a list
@@ -284,5 +188,45 @@ c: d # second
metadata: metadata:
annotations: annotations:
config.kubernetes.io/path: "a/b/a_test.yaml" config.kubernetes.io/path: "a/b/a_test.yaml"
`, buff.String()) `,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
actual := &bytes.Buffer{}
w := tc.instance
w.Writer = actual
if tc.functionConfig != "" {
w.FunctionConfig = yaml.MustParse(tc.functionConfig)
}
if tc.results != "" {
w.Results = yaml.MustParse(tc.results)
}
var items []*yaml.RNode
for i := range tc.items {
items = append(items, yaml.MustParse(tc.items[i]))
}
err := w.Write(items)
if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(actual.String())) {
t.FailNow()
}
})
}
} }

View File

@@ -6,6 +6,7 @@ package filters
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -146,6 +147,13 @@ type ContainerFilter struct {
// nodes instead of only nodes scoped under the function. // nodes instead of only nodes scoped under the function.
GlobalScope bool GlobalScope bool
ResultsFile string
Results *yaml.RNode
// SetFlowStyleForConfig sets the style for config to Flow when serializing it
SetFlowStyleForConfig bool
// args may be specified by tests to override how a container is spawned // args may be specified by tests to override how a container is spawned
args []string args []string
@@ -257,10 +265,7 @@ func (c *ContainerFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode,
// GrepFilter implements kio.GrepFilter // GrepFilter implements kio.GrepFilter
func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) { func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
// get the command to filter the Resources // get the command to filter the Resources
cmd, err := c.getCommand() cmd := c.getCommand()
if err != nil {
return nil, err
}
in := &bytes.Buffer{} in := &bytes.Buffer{}
out := &bytes.Buffer{} out := &bytes.Buffer{}
@@ -296,7 +301,16 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
cmd.Stdin = in cmd.Stdin = in
cmd.Stdout = out cmd.Stdout = out
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
return nil, err // 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
} }
output, err := r.Read() output, err := r.Read()
@@ -304,6 +318,10 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, err return nil, err
} }
if err := c.doResults(r); err != nil {
return nil, err
}
// annotate any generated Resources with a path and index if they don't already have one // annotate any generated Resources with a path and index if they don't already have one
if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil { if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil {
return nil, err return nil, err
@@ -314,6 +332,25 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return append(output, saved...), nil return append(output, saved...), nil
} }
func (c *ContainerFilter) doResults(r *kio.ByteReader) error {
// Write the results to a file if configured to do so
if c.ResultsFile != "" && r.Results != nil {
results, err := r.Results.String()
if err != nil {
return err
}
err = ioutil.WriteFile(c.ResultsFile, []byte(results), 0600)
if err != nil {
return err
}
}
if r.Results != nil {
c.Results = r.Results
}
return nil
}
// getArgs returns the command + args to run to spawn the container // getArgs returns the command + args to run to spawn the container
func (c *ContainerFilter) getArgs() []string { func (c *ContainerFilter) getArgs() []string {
// run the container using docker. this is simpler than using the docker // run the container using docker. this is simpler than using the docker
@@ -341,6 +378,9 @@ func (c *ContainerFilter) getArgs() []string {
args = append(args, "--mount", storageMount.String()) args = append(args, "--mount", storageMount.String())
} }
// tell functions to write error messages to stderr as well as results
os.Setenv("LOG_TO_STDERR", "true")
// export the local environment vars to the container // export the local environment vars to the container
for _, pair := range os.Environ() { for _, pair := range os.Environ() {
tokens := strings.Split(pair, "=") tokens := strings.Split(pair, "=")
@@ -353,17 +393,9 @@ func (c *ContainerFilter) getArgs() []string {
} }
// getCommand returns a command which will apply the Filter using the container image // getCommand returns a command which will apply the Filter using the container image
func (c *ContainerFilter) getCommand() (*exec.Cmd, error) { func (c *ContainerFilter) getCommand() *exec.Cmd {
// encode the filter command API configuration if c.SetFlowStyleForConfig {
cfg := &bytes.Buffer{}
if err := func() error {
e := yaml.NewEncoder(cfg)
defer e.Close()
// make it fit on a single line
c.Config.YNode().Style = yaml.FlowStyle c.Config.YNode().Style = yaml.FlowStyle
return e.Encode(c.Config.YNode())
}(); err != nil {
return nil, err
} }
if len(c.args) == 0 { if len(c.args) == 0 {
@@ -375,7 +407,7 @@ func (c *ContainerFilter) getCommand() (*exec.Cmd, error) {
cmd.Env = os.Environ() cmd.Env = os.Environ()
// set stderr for err messaging // set stderr for err messaging
return cmd, nil return cmd
} }
// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource. // IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource.

View File

@@ -6,6 +6,7 @@ package filters
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strings" "strings"
"testing" "testing"
@@ -15,6 +16,367 @@ import (
"sigs.k8s.io/kustomize/kyaml/yaml" "sigs.k8s.io/kustomize/kyaml/yaml"
) )
func TestContainerFilter_Filter(t *testing.T) {
var tests = []struct {
name string
input []string
expectedOutput []string
expectedError string
expectedResults string
noMakeResultsFile bool
instance ContainerFilter
}{
{
name: "add_path_annotation",
instance: ContainerFilter{args: []string{
"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
`,
},
},
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'
`,
},
},
{
name: "write_results",
instance: ContainerFilter{args: []string{
"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"
`,
},
},
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",
expectedError: "exit status 1",
instance: ContainerFilter{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",
noMakeResultsFile: true,
instance: ContainerFilter{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"
`,
},
}
for i := range tests {
tt := tests[i]
t.Run(tt.name, func(t *testing.T) {
if len(tt.expectedResults) > 0 && !tt.noMakeResultsFile {
f, err := ioutil.TempFile("", "test-kyaml-*.yaml")
if !assert.NoError(t, err) {
t.FailNow()
}
defer os.RemoveAll(f.Name())
tt.instance.ResultsFile = f.Name()
} else if len(tt.expectedResults) > 0 {
tt.instance.ResultsFile = "/not/real/file"
}
var inputs []*yaml.RNode
for i := range tt.input {
node, err := yaml.Parse(tt.input[i])
if !assert.NoError(t, err) {
t.FailNow()
}
inputs = append(inputs, node)
}
output, err := tt.instance.Filter(inputs)
if tt.expectedError != "" {
if !assert.EqualError(t, err, tt.expectedError) {
t.FailNow()
}
return
}
if !assert.NoError(t, err) {
t.FailNow()
}
var actual []string
for i := range output {
s, err := output[i].String()
if !assert.NoError(t, err) {
t.FailNow()
}
actual = append(actual, strings.TrimSpace(s))
}
var expected []string
for i := range tt.expectedOutput {
expected = append(expected, strings.TrimSpace(tt.expectedOutput[i]))
}
if !assert.Equal(t, expected, actual) {
t.FailNow()
}
if len(tt.instance.ResultsFile) > 0 {
tt.expectedResults = strings.TrimSpace(tt.expectedResults)
results, err := tt.instance.Results.String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, tt.expectedResults, strings.TrimSpace(results)) {
t.FailNow()
}
b, err := ioutil.ReadFile(tt.instance.ResultsFile)
writtenResults := strings.TrimSpace(string(b))
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, tt.expectedResults, writtenResults) {
t.FailNow()
}
}
})
}
}
func TestFilter_command(t *testing.T) { func TestFilter_command(t *testing.T) {
cfg, err := yaml.Parse(`apiVersion: apps/v1 cfg, err := yaml.Parse(`apiVersion: apps/v1
kind: Deployment kind: Deployment
@@ -29,10 +391,7 @@ metadata:
Config: cfg, Config: cfg,
} }
os.Setenv("KYAML_TEST", "FOO") os.Setenv("KYAML_TEST", "FOO")
cmd, err := instance.getCommand() cmd := instance.getCommand()
if !assert.NoError(t, err) {
return
}
expected := []string{ expected := []string{
"docker", "run", "docker", "run",
@@ -78,10 +437,7 @@ metadata:
Config: cfg, Config: cfg,
StorageMounts: []StorageMount{bindMount, localVol, tmpfs}, StorageMounts: []StorageMount{bindMount, localVol, tmpfs},
} }
cmd, err := instance.getCommand() cmd := instance.getCommand()
if !assert.NoError(t, err) {
return
}
expected := []string{ expected := []string{
"docker", "run", "docker", "run",
@@ -116,10 +472,7 @@ metadata:
Network: "test-net", Network: "test-net",
Config: cfg, Config: cfg,
} }
cmd, err := instance.getCommand() cmd := instance.getCommand()
if !assert.NoError(t, err) {
return
}
expected := []string{ expected := []string{
"docker", "run", "docker", "run",
@@ -168,9 +521,10 @@ metadata:
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -252,9 +606,10 @@ metadata:
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sh", "-c", "cat <&0"}, Config: cfg,
args: []string{"sh", "-c", "cat <&0"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -597,8 +952,9 @@ metadata:
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
Config: cfg,
args: []string{"echo", `apiVersion: apps/v1 args: []string{"echo", `apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -671,8 +1027,9 @@ metadata:
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
Config: cfg,
args: []string{"echo", `apiVersion: apps/v1 args: []string{"echo", `apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
@@ -756,9 +1113,10 @@ metadata:
// no resources match the scope // no resources match the scope
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -831,10 +1189,11 @@ metadata:
// no resources match the scope // no resources match the scope
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
GlobalScope: true, SetFlowStyleForConfig: true,
Image: "example.com:version", GlobalScope: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -926,9 +1285,10 @@ metadata:
// no resources match the scope // no resources match the scope
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -1022,9 +1382,10 @@ metadata:
// no resources match the scope // no resources match the scope
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
@@ -1118,9 +1479,10 @@ metadata:
// no resources match the scope // no resources match the scope
called := false called := false
result, err := (&ContainerFilter{ result, err := (&ContainerFilter{
Image: "example.com:version", SetFlowStyleForConfig: true,
Config: cfg, Image: "example.com:version",
args: []string{"sed", "s/Deployment/StatefulSet/g"}, Config: cfg,
args: []string{"sed", "s/Deployment/StatefulSet/g"},
checkInput: func(s string) { checkInput: func(s string) {
called = true called = true
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1 if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1

View File

@@ -4,12 +4,14 @@
package runfn package runfn
import ( import (
"fmt"
"io" "io"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"sync/atomic"
"sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/kio"
@@ -64,6 +66,12 @@ type RunFns struct {
// DisableContainers will disable functions run as containers // DisableContainers will disable functions run as containers
DisableContainers bool DisableContainers bool
// ResultsDir is where to write each functions results
ResultsDir string
// resultsCount is used to generate the results filename for each container
resultsCount uint32
// functionFilterProvider provides a filter to perform the function. // functionFilterProvider provides a filter to perform the function.
// this is a variable so it can be mocked in tests // this is a variable so it can be mocked in tests
functionFilterProvider func( functionFilterProvider func(
@@ -219,6 +227,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
if err != nil { if err != nil {
return nil, err return nil, err
} }
if c == nil { if c == nil {
continue continue
} }
@@ -304,12 +313,21 @@ func (r *RunFns) init() {
// ffp provides function filters // ffp provides function filters
func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) { func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) {
if !r.DisableContainers && spec.Container.Image != "" { if !r.DisableContainers && spec.Container.Image != "" {
var resultsFile string
// TODO: Add a test for this behavior
if r.ResultsDir != "" {
resultsFile = filepath.Join(r.ResultsDir, fmt.Sprintf(
"results-%v.yaml", r.resultsCount))
atomic.AddUint32(&r.resultsCount, 1)
}
return &filters.ContainerFilter{ return &filters.ContainerFilter{
Image: spec.Container.Image, Image: spec.Container.Image,
Config: api, Config: api,
Network: spec.Network, Network: spec.Network,
StorageMounts: r.StorageMounts, StorageMounts: r.StorageMounts,
GlobalScope: r.GlobalScope, GlobalScope: r.GlobalScope,
ResultsFile: resultsFile,
}, nil }, nil
} }
if r.EnableStarlark && spec.Starlark.Path != "" { if r.EnableStarlark && spec.Starlark.Path != "" {