mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-28 09:18:19 +00:00
Merge pull request #2362 from pwittrock/validation-spec
Support for serializing ResourceList.results field from functions
This commit is contained in:
@@ -51,6 +51,9 @@ func GetRunFnRunner(name string) *RunFnRunner {
|
||||
&r.StarName, "star-name", "", "name of starlark program.")
|
||||
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.Network, "network", false, "enable network access for functions that declare it")
|
||||
r.Command.Flags().StringVar(
|
||||
@@ -77,6 +80,7 @@ type RunFnRunner struct {
|
||||
StarPath string
|
||||
StarName string
|
||||
RunFns runfn.RunFns
|
||||
ResultsDir string
|
||||
Network bool
|
||||
NetworkName string
|
||||
Mounts []string
|
||||
@@ -267,6 +271,7 @@ func (r *RunFnRunner) preRunE(c *cobra.Command, args []string) error {
|
||||
NetworkName: r.NetworkName,
|
||||
EnableStarlark: r.EnableStar,
|
||||
StorageMounts: storageMounts,
|
||||
ResultsDir: r.ResultsDir,
|
||||
}
|
||||
|
||||
// don't consider args for the function
|
||||
|
||||
@@ -11,23 +11,25 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/runfn"
|
||||
)
|
||||
|
||||
// TestRunFnCommand_preRunE verifies that preRunE correctly parses the commandline
|
||||
// flags and arguments into the RunFns structure to be executed.
|
||||
func TestRunFnCommand_preRunE(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
err string
|
||||
path string
|
||||
input io.Reader
|
||||
output io.Writer
|
||||
functionPaths []string
|
||||
network bool
|
||||
networkName string
|
||||
mount []string
|
||||
name string
|
||||
args []string
|
||||
expected string
|
||||
expectedStruct *runfn.RunFns
|
||||
err string
|
||||
path string
|
||||
input io.Reader
|
||||
output io.Writer
|
||||
functionPaths []string
|
||||
network bool
|
||||
networkName string
|
||||
mount []string
|
||||
}{
|
||||
{
|
||||
name: "config map",
|
||||
@@ -234,6 +236,26 @@ metadata:
|
||||
data: {g: h, i: j=k}
|
||||
kind: Foo
|
||||
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()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, r.RunFns, r.RunFns) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if !assert.Equal(t, toStorageMounts(tt.mount), r.RunFns.StorageMounts) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ type ByteReadWriter struct {
|
||||
|
||||
FunctionConfig *yaml.RNode
|
||||
|
||||
Results *yaml.RNode
|
||||
|
||||
WrappingAPIVersion string
|
||||
WrappingKind string
|
||||
}
|
||||
@@ -52,6 +54,7 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
|
||||
}
|
||||
val, err := b.Read()
|
||||
rw.FunctionConfig = b.FunctionConfig
|
||||
rw.Results = b.Results
|
||||
rw.WrappingAPIVersion = b.WrappingAPIVersion
|
||||
rw.WrappingKind = b.WrappingKind
|
||||
return val, errors.Wrap(err)
|
||||
@@ -63,6 +66,7 @@ func (rw *ByteReadWriter) Write(nodes []*yaml.RNode) error {
|
||||
KeepReaderAnnotations: rw.KeepReaderAnnotations,
|
||||
Style: rw.Style,
|
||||
FunctionConfig: rw.FunctionConfig,
|
||||
Results: rw.Results,
|
||||
WrappingAPIVersion: rw.WrappingAPIVersion,
|
||||
WrappingKind: rw.WrappingKind,
|
||||
}.Write(nodes)
|
||||
@@ -85,6 +89,8 @@ type ByteReader struct {
|
||||
|
||||
FunctionConfig *yaml.RNode
|
||||
|
||||
Results *yaml.RNode
|
||||
|
||||
// DisableUnwrapping prevents Resources in Lists and ResourceLists from being unwrapped
|
||||
DisableUnwrapping bool
|
||||
|
||||
@@ -142,10 +148,12 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
|
||||
r.WrappingAPIVersion = meta.APIVersion
|
||||
|
||||
// unwrap the list
|
||||
fc := node.Field("functionConfig")
|
||||
if fc != nil {
|
||||
if fc := node.Field("functionConfig"); fc != nil {
|
||||
r.FunctionConfig = fc.Value
|
||||
}
|
||||
if res := node.Field("results"); res != nil {
|
||||
r.Results = res.Value
|
||||
}
|
||||
|
||||
items := node.Field("items")
|
||||
if items != nil {
|
||||
|
||||
@@ -5,36 +5,64 @@ package kio_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
// getByteReaderTestInput returns test input
|
||||
func getByteReaderTestInput(t *testing.T) *bytes.Buffer {
|
||||
b := &bytes.Buffer{}
|
||||
_, err := b.WriteString(`
|
||||
---
|
||||
a: b # first resource
|
||||
c: d
|
||||
---
|
||||
# second resource
|
||||
e: f
|
||||
g:
|
||||
- h
|
||||
---
|
||||
---
|
||||
i: j
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
assert.FailNow(t, "")
|
||||
func TestByteReader(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
input string
|
||||
err string
|
||||
expectedItems []string
|
||||
expectedFunctionConfig string
|
||||
expectedResults string
|
||||
wrappingAPIVersion string
|
||||
wrappingAPIKind string
|
||||
instance ByteReader
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestByteReader_Read_wrappedResourceßßList(t *testing.T) {
|
||||
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: config.kubernetes.io/v1alpha1
|
||||
testCases := []testCase{
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
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
|
||||
functionConfig:
|
||||
foo: bar
|
||||
@@ -50,109 +78,87 @@ items:
|
||||
spec:
|
||||
selectors:
|
||||
foo: bar
|
||||
`)}
|
||||
nodes, err := r.Read()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// verify the contents
|
||||
if !assert.Len(t, nodes, 2) {
|
||||
return
|
||||
}
|
||||
expected := []string{
|
||||
`kind: Deployment
|
||||
`,
|
||||
expectedItems: []string{
|
||||
`kind: Deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
`,
|
||||
`kind: Service
|
||||
`kind: Service
|
||||
spec:
|
||||
selectors:
|
||||
foo: bar
|
||||
`,
|
||||
}
|
||||
for i := range nodes {
|
||||
if !assert.Equal(t, expected[i], nodes[i].MustString()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// verify the function config
|
||||
assert.Equal(t, `foo: bar
|
||||
},
|
||||
expectedFunctionConfig: `foo: bar
|
||||
elems:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
`, r.FunctionConfig.MustString())
|
||||
- c`,
|
||||
wrappingAPIVersion: ResourceListAPIVersion,
|
||||
wrappingAPIKind: ResourceListKind,
|
||||
},
|
||||
|
||||
assert.Equal(t, ResourceListKind, r.WrappingKind)
|
||||
assert.Equal(t, ResourceListAPIVersion, r.WrappingAPIVersion)
|
||||
}
|
||||
|
||||
func TestByteReader_Read_wrappedList(t *testing.T) {
|
||||
r := &ByteReader{Reader: bytes.NewBufferString(`apiVersion: v1
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "wrapped_list",
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: List
|
||||
items:
|
||||
- kind: Deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
- kind: Deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
- kind: Service
|
||||
spec:
|
||||
selectors:
|
||||
foo: bar
|
||||
`)}
|
||||
nodes, err := r.Read()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
// verify the contents
|
||||
if !assert.Len(t, nodes, 2) {
|
||||
return
|
||||
}
|
||||
expected := []string{
|
||||
`kind: Deployment
|
||||
`,
|
||||
expectedItems: []string{
|
||||
`
|
||||
kind: Deployment
|
||||
spec:
|
||||
replicas: 1
|
||||
`,
|
||||
`kind: Service
|
||||
`
|
||||
kind: Service
|
||||
spec:
|
||||
selectors:
|
||||
foo: bar
|
||||
`,
|
||||
}
|
||||
for i := range nodes {
|
||||
if !assert.Equal(t, expected[i], nodes[i].MustString()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
wrappingAPIKind: "List",
|
||||
wrappingAPIVersion: "v1",
|
||||
},
|
||||
|
||||
// verify the function config
|
||||
assert.Nil(t, r.FunctionConfig)
|
||||
assert.Equal(t, "List", r.WrappingKind)
|
||||
assert.Equal(t, "v1", r.WrappingAPIVersion)
|
||||
}
|
||||
|
||||
// TestByteReader_Read tests the default Read behavior
|
||||
// - Resources are read into a slice
|
||||
// - ReaderAnnotations are set on the ResourceNodes
|
||||
func TestByteReader_Read(t *testing.T) {
|
||||
nodes, err := (&ByteReader{Reader: getByteReaderTestInput(t)}).Read()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Len(t, nodes, 3) {
|
||||
return
|
||||
}
|
||||
expected := []string{
|
||||
`a: b # first resource
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "unwrapped_items",
|
||||
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:
|
||||
config.kubernetes.io/index: '0'
|
||||
`,
|
||||
`# second resource
|
||||
`# second resource
|
||||
e: f
|
||||
g:
|
||||
- h
|
||||
@@ -160,150 +166,209 @@ metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: '1'
|
||||
`,
|
||||
`i: j
|
||||
`i: j
|
||||
metadata:
|
||||
annotations:
|
||||
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{
|
||||
Reader: getByteReaderTestInput(t),
|
||||
OmitReaderAnnotations: true}).Read()
|
||||
if !assert.NoError(t, err) {
|
||||
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
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "omit_annotations",
|
||||
input: `
|
||||
---
|
||||
a: b # first resource
|
||||
c: d
|
||||
metadata:
|
||||
annotations:
|
||||
foo: 'bar'
|
||||
`,
|
||||
`# second resource
|
||||
---
|
||||
# second resource
|
||||
e: f
|
||||
g:
|
||||
- h
|
||||
metadata:
|
||||
annotations:
|
||||
foo: 'bar'
|
||||
---
|
||||
---
|
||||
i: j
|
||||
`,
|
||||
`i: j
|
||||
metadata:
|
||||
annotations:
|
||||
foo: 'bar'
|
||||
expectedItems: []string{
|
||||
`
|
||||
a: b # first resource
|
||||
c: d
|
||||
`,
|
||||
}
|
||||
for i := range nodes {
|
||||
val, err := nodes[i].String()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.Equal(t, expected[i], val) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
# second resource
|
||||
e: f
|
||||
g:
|
||||
- h
|
||||
`,
|
||||
`
|
||||
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) {
|
||||
nodes, err := (&ByteReader{
|
||||
Reader: getByteReaderTestInput(t),
|
||||
SetAnnotations: map[string]string{"foo": "bar"},
|
||||
}).Read()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.Len(t, nodes, 3) {
|
||||
return
|
||||
}
|
||||
expected := []string{
|
||||
`a: b # first resource
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "no_omit_annotations",
|
||||
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:
|
||||
config.kubernetes.io/index: '0'
|
||||
foo: 'bar'
|
||||
`,
|
||||
`# second resource
|
||||
`
|
||||
# second resource
|
||||
e: f
|
||||
g:
|
||||
- h
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: '1'
|
||||
foo: 'bar'
|
||||
`,
|
||||
`i: j
|
||||
`
|
||||
i: j
|
||||
metadata:
|
||||
annotations:
|
||||
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'
|
||||
`,
|
||||
`# 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()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
if !assert.Equal(t, expected[i], val) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tc := testCases[i]
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := tc.instance
|
||||
r.Reader = bytes.NewBufferString(tc.input)
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
263
kyaml/kio/byteio_readwriter_test.go
Normal file
263
kyaml/kio/byteio_readwriter_test.go
Normal 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ type ByteWriter struct {
|
||||
// wrap the results in an ResourceList.
|
||||
FunctionConfig *yaml.RNode
|
||||
|
||||
Results *yaml.RNode
|
||||
|
||||
// WrappingKind if set will cause ByteWriter to wrap the Resources in
|
||||
// an 'items' field in this kind. e.g. if WrappingKind is 'List',
|
||||
// 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"},
|
||||
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{
|
||||
Kind: yaml.DocumentNode,
|
||||
Content: []*yaml.Node{list}}
|
||||
|
||||
@@ -1,96 +1,84 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package kio
|
||||
package kio_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
. "sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// TestByteWriter_Write_withoutAnnotations tests:
|
||||
// - Resource Config ordering is preserved if no annotations are present
|
||||
func TestByteWriter_Write_wrapped(t *testing.T) {
|
||||
node1, err := yaml.Parse(`a: b #first
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
func TestByteWriter(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
err string
|
||||
items []string
|
||||
functionConfig string
|
||||
results string
|
||||
expectedOutput string
|
||||
instance kio.ByteWriter
|
||||
}
|
||||
node2, err := yaml.Parse(`c: d # second
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
node3, err := yaml.Parse(`e: f
|
||||
|
||||
testCases := []testCase{
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "wrap_resource_list",
|
||||
instance: ByteWriter{
|
||||
Sort: true,
|
||||
WrappingKind: ResourceListKind,
|
||||
WrappingAPIVersion: ResourceListAPIVersion,
|
||||
},
|
||||
items: []string{
|
||||
`a: b #first`,
|
||||
`c: d # second`,
|
||||
},
|
||||
functionConfig: `
|
||||
e: f
|
||||
g:
|
||||
h:
|
||||
- i # has a list
|
||||
- j
|
||||
`)
|
||||
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
|
||||
- j`,
|
||||
expectedOutput: `apiVersion: config.kubernetes.io/v1alpha1
|
||||
kind: ResourceList
|
||||
items:
|
||||
- c: d # second
|
||||
- a: b #first
|
||||
- c: d # second
|
||||
functionConfig:
|
||||
e: f
|
||||
g:
|
||||
h:
|
||||
- i # has a list
|
||||
- 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
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
node2, err := yaml.Parse(`c: d # second
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
node3, err := yaml.Parse(`e: f
|
||||
//
|
||||
//
|
||||
//
|
||||
{
|
||||
name: "multiple_items",
|
||||
items: []string{
|
||||
`c: d # second`,
|
||||
`e: f
|
||||
g:
|
||||
h:
|
||||
# has a list
|
||||
- i : [i1, i2] # line comment
|
||||
# has a list 2
|
||||
- j : j1
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
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
|
||||
`,
|
||||
`a: b #first`,
|
||||
},
|
||||
expectedOutput: `
|
||||
c: d # second
|
||||
---
|
||||
e: f
|
||||
g:
|
||||
@@ -101,32 +89,23 @@ g:
|
||||
- j: j1
|
||||
---
|
||||
a: b #first
|
||||
`, buff.String())
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// TestByteWriter_Write_withAnnotationsKeepAnnotations tests:
|
||||
// - Resource Config is sorted by annotations if present
|
||||
// - IndexAnnotations are retained
|
||||
func TestByteWriter_Write_withAnnotationsKeepAnnotations(t *testing.T) {
|
||||
node1, err := yaml.Parse(`a: b #first
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{
|
||||
name: "sort_keep_annotation",
|
||||
instance: ByteWriter{Sort: true, KeepReaderAnnotations: true},
|
||||
items: []string{
|
||||
`a: b #first
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 0
|
||||
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
|
||||
`,
|
||||
`e: f
|
||||
g:
|
||||
h:
|
||||
- i # has a list
|
||||
@@ -135,18 +114,16 @@ metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 0
|
||||
config.kubernetes.io/path: "a/b/b_test.yaml"
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
`,
|
||||
`c: d # second
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 1
|
||||
config.kubernetes.io/path: "a/b/a_test.yaml"
|
||||
`,
|
||||
},
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
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
|
||||
expectedOutput: `a: b #first
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 0
|
||||
@@ -167,109 +144,36 @@ metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 0
|
||||
config.kubernetes.io/path: "a/b/b_test.yaml"
|
||||
`, buff.String())
|
||||
}
|
||||
`,
|
||||
},
|
||||
|
||||
// TestByteWriter_Write_withAnnotations tests:
|
||||
// - Resource Config is sorted by annotations if present
|
||||
// - IndexAnnotations are pruned
|
||||
func TestByteWriter_Write_withAnnotations(t *testing.T) {
|
||||
node1, err := yaml.Parse(`a: b #first
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{
|
||||
name: "sort_partial_annotations",
|
||||
instance: ByteWriter{Sort: true},
|
||||
items: []string{
|
||||
`a: b #first
|
||||
metadata:
|
||||
annotations:
|
||||
config.kubernetes.io/index: 0
|
||||
config.kubernetes.io/path: "a/b/a_test.yaml"
|
||||
`)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
node2, err := yaml.Parse(`c: d # second
|
||||
`,
|
||||
`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
|
||||
`,
|
||||
`e: f
|
||||
g:
|
||||
h:
|
||||
- i # has a list
|
||||
- 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{}
|
||||
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
|
||||
expectedOutput: `e: f
|
||||
g:
|
||||
h:
|
||||
- i # has a list
|
||||
@@ -284,5 +188,45 @@ c: d # second
|
||||
metadata:
|
||||
annotations:
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ package filters
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
@@ -146,6 +147,13 @@ type ContainerFilter struct {
|
||||
// nodes instead of only nodes scoped under the function.
|
||||
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 []string
|
||||
|
||||
@@ -257,10 +265,7 @@ func (c *ContainerFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode,
|
||||
// GrepFilter implements kio.GrepFilter
|
||||
func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
// get the command to filter the Resources
|
||||
cmd, err := c.getCommand()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd := c.getCommand()
|
||||
|
||||
in := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
@@ -296,7 +301,16 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
cmd.Stdin = in
|
||||
cmd.Stdout = out
|
||||
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()
|
||||
@@ -304,6 +318,10 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
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
|
||||
if err := kioutil.DefaultPathAnnotation(functionDir, output); err != nil {
|
||||
return nil, err
|
||||
@@ -314,6 +332,25 @@ func (c *ContainerFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
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
|
||||
func (c *ContainerFilter) getArgs() []string {
|
||||
// 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())
|
||||
}
|
||||
|
||||
// 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
|
||||
for _, pair := range os.Environ() {
|
||||
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
|
||||
func (c *ContainerFilter) getCommand() (*exec.Cmd, error) {
|
||||
// encode the filter command API configuration
|
||||
cfg := &bytes.Buffer{}
|
||||
if err := func() error {
|
||||
e := yaml.NewEncoder(cfg)
|
||||
defer e.Close()
|
||||
// make it fit on a single line
|
||||
func (c *ContainerFilter) getCommand() *exec.Cmd {
|
||||
if c.SetFlowStyleForConfig {
|
||||
c.Config.YNode().Style = yaml.FlowStyle
|
||||
return e.Encode(c.Config.YNode())
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(c.args) == 0 {
|
||||
@@ -375,7 +407,7 @@ func (c *ContainerFilter) getCommand() (*exec.Cmd, error) {
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
// set stderr for err messaging
|
||||
return cmd, nil
|
||||
return cmd
|
||||
}
|
||||
|
||||
// IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource.
|
||||
|
||||
@@ -6,6 +6,7 @@ package filters
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -15,6 +16,367 @@ import (
|
||||
"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) {
|
||||
cfg, err := yaml.Parse(`apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
@@ -29,10 +391,7 @@ metadata:
|
||||
Config: cfg,
|
||||
}
|
||||
os.Setenv("KYAML_TEST", "FOO")
|
||||
cmd, err := instance.getCommand()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
cmd := instance.getCommand()
|
||||
|
||||
expected := []string{
|
||||
"docker", "run",
|
||||
@@ -78,10 +437,7 @@ metadata:
|
||||
Config: cfg,
|
||||
StorageMounts: []StorageMount{bindMount, localVol, tmpfs},
|
||||
}
|
||||
cmd, err := instance.getCommand()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
cmd := instance.getCommand()
|
||||
|
||||
expected := []string{
|
||||
"docker", "run",
|
||||
@@ -116,10 +472,7 @@ metadata:
|
||||
Network: "test-net",
|
||||
Config: cfg,
|
||||
}
|
||||
cmd, err := instance.getCommand()
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
cmd := instance.getCommand()
|
||||
|
||||
expected := []string{
|
||||
"docker", "run",
|
||||
@@ -168,9 +521,10 @@ metadata:
|
||||
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -252,9 +606,10 @@ metadata:
|
||||
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sh", "-c", "cat <&0"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sh", "-c", "cat <&0"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -597,8 +952,9 @@ metadata:
|
||||
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"echo", `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -671,8 +1027,9 @@ metadata:
|
||||
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"echo", `apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -756,9 +1113,10 @@ metadata:
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -831,10 +1189,11 @@ metadata:
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
GlobalScope: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
GlobalScope: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -926,9 +1285,10 @@ metadata:
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -1022,9 +1382,10 @@ metadata:
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
@@ -1118,9 +1479,10 @@ metadata:
|
||||
// no resources match the scope
|
||||
called := false
|
||||
result, err := (&ContainerFilter{
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
SetFlowStyleForConfig: true,
|
||||
Image: "example.com:version",
|
||||
Config: cfg,
|
||||
args: []string{"sed", "s/Deployment/StatefulSet/g"},
|
||||
checkInput: func(s string) {
|
||||
called = true
|
||||
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
package runfn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
@@ -64,6 +66,12 @@ type RunFns struct {
|
||||
// DisableContainers will disable functions run as containers
|
||||
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.
|
||||
// this is a variable so it can be mocked in tests
|
||||
functionFilterProvider func(
|
||||
@@ -219,6 +227,7 @@ func (r RunFns) getFunctionFilters(global bool, fns ...*yaml.RNode) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
@@ -304,12 +313,21 @@ func (r *RunFns) init() {
|
||||
// ffp provides function filters
|
||||
func (r *RunFns) ffp(spec filters.FunctionSpec, api *yaml.RNode) (kio.Filter, error) {
|
||||
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{
|
||||
Image: spec.Container.Image,
|
||||
Config: api,
|
||||
Network: spec.Network,
|
||||
StorageMounts: r.StorageMounts,
|
||||
GlobalScope: r.GlobalScope,
|
||||
ResultsFile: resultsFile,
|
||||
}, nil
|
||||
}
|
||||
if r.EnableStarlark && spec.Starlark.Path != "" {
|
||||
|
||||
Reference in New Issue
Block a user