mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-01 10:20:35 +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.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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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.
|
// 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}}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user