Files
kustomize/api/resource/factory_test.go
Karl Isenberg 43868688d5 Use require for Error and NoError
Assert keeps going after failure, but require immediately fails
the tests, making it easier to find the output related to the test
failure, rather than having to comb through a bunch of subsequent
assertion failures. For equality tests, we may or may not want to
continue, but for error checks we almost always want to immediately
fail the test. Exceptions can be changed as-needed.
2024-03-20 13:19:18 -07:00

735 lines
14 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package resource_test
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/internal/loader"
. "sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func TestRNodesFromBytes(t *testing.T) {
type testCase struct {
input string
expected []string
}
testCases := map[string]testCase{
"empty1": {
input: "",
expected: []string{},
},
"empty2": {
input: `
---
---
`,
expected: []string{},
},
"deployment1": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: pooh
---
`,
expected: []string{
`apiVersion: apps/v1
kind: Deployment
metadata:
name: pooh
`,
},
},
"deployment2": {
input: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: baseprefix-mungebot
spec:
replicas: 1
selector:
matchLabels:
foo: bar
template:
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
spec:
containers:
- env:
- name: foo
value: bar
image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: baseprefix-mungebot-service
spec:
ports:
- port: 7002
selector:
app: mungebot
foo: bar
`,
expected: []string{
`apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: baseprefix-mungebot
spec:
replicas: 1
selector:
matchLabels:
foo: bar
template:
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
spec:
containers:
- env:
- name: foo
value: bar
image: nginx
name: nginx
ports:
- containerPort: 80
`,
`apiVersion: v1
kind: Service
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: baseprefix-mungebot-service
spec:
ports:
- port: 7002
selector:
app: mungebot
foo: bar
`,
},
},
}
for name := range testCases {
tc := testCases[name]
t.Run(name, func(t *testing.T) {
result, err := factory.RNodesFromBytes([]byte(tc.input))
if err != nil {
t.Fatalf("%v: fails with err: %v", name, err)
}
if len(result) != len(tc.expected) {
for i := range result {
str, err := result[i].String()
if err != nil {
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
}
t.Logf("--- %d:\n%s", i, str)
}
t.Fatalf(
"%v: actual len %d != expected len %d",
name, len(result), len(tc.expected))
}
for i := range tc.expected {
str, err := result[i].String()
if err != nil {
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
}
if str != tc.expected[i] {
t.Fatalf(
"%v: string mismatch in item %d\n"+
"actual:\n-----\n%s\n-----\n"+
"expected:\n-----\n%s\n-----\n",
name, i, str, tc.expected[i])
}
}
})
}
}
func TestSliceFromPatches(t *testing.T) {
patchGood1 := types.PatchStrategicMerge("patch1.yaml")
patch1 := `
apiVersion: apps/v1
kind: Deployment
metadata:
name: pooh
`
patchGood2 := types.PatchStrategicMerge("patch2.yaml")
patch2 := `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
namespace: hundred-acre-wood
---
# some comment
---
---
`
patchBad := types.PatchStrategicMerge("patch3.yaml")
patch3 := `
WOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`
patchList := types.PatchStrategicMerge("patch4.yaml")
patch4 := `
apiVersion: v1
kind: List
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: pooh
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
namespace: hundred-acre-wood
`
patchList2 := types.PatchStrategicMerge("patch5.yaml")
patch5 := `
apiVersion: v1
kind: DeploymentList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-b
spec:
<<: *hostAliases
`
patchList3 := types.PatchStrategicMerge("patch6.yaml")
patch6 := `
apiVersion: v1
kind: List
items:
`
patchList4 := types.PatchStrategicMerge("patch7.yaml")
patch7 := `
apiVersion: v1
kind: List
`
testDeploymentSpec := map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"hostAliases": []interface{}{
map[string]interface{}{
"hostnames": []interface{}{
"a.example.com",
},
"ip": "8.8.8.8",
},
},
},
},
}
testDeploymentA := factory.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-a",
},
"spec": testDeploymentSpec,
})
testDeploymentB := factory.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-b",
},
"spec": testDeploymentSpec,
})
fSys := filesys.MakeFsInMemory()
fSys.WriteFile(string(patchGood1), []byte(patch1))
fSys.WriteFile(string(patchGood2), []byte(patch2))
fSys.WriteFile(string(patchBad), []byte(patch3))
fSys.WriteFile(string(patchList), []byte(patch4))
fSys.WriteFile(string(patchList2), []byte(patch5))
fSys.WriteFile(string(patchList3), []byte(patch6))
fSys.WriteFile(string(patchList4), []byte(patch7))
ldr, err := loader.NewLoader(
loader.RestrictionRootOnly, filesys.Separator, fSys)
if err != nil {
t.Fatal(err)
}
tests := map[string]struct {
input []types.PatchStrategicMerge
expectedOut []*Resource
expectedErr bool
}{
"happy": {
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
expectedOut: []*Resource{testDeployment, testConfigMap},
expectedErr: false,
},
"badFileName": {
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
expectedOut: []*Resource{},
expectedErr: true,
},
"badData": {
input: []types.PatchStrategicMerge{patchGood1, patchBad},
expectedOut: []*Resource{},
expectedErr: true,
},
"listOfPatches": {
input: []types.PatchStrategicMerge{patchList},
expectedOut: []*Resource{testDeployment, testConfigMap},
expectedErr: false,
},
"listWithAnchorReference": {
input: []types.PatchStrategicMerge{patchList2},
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
expectedErr: false,
},
"listWithNoEntries": {
input: []types.PatchStrategicMerge{patchList3},
expectedOut: []*Resource{},
expectedErr: false,
},
"listWithNoItems": {
input: []types.PatchStrategicMerge{patchList4},
expectedOut: []*Resource{},
expectedErr: false,
},
}
for n, test := range tests {
t.Run(n, func(t *testing.T) {
rs, err := factory.SliceFromPatches(ldr, test.input)
if err != nil {
assert.True(t, test.expectedErr,
fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
return
}
assert.False(t, test.expectedErr, "expected no error in "+n)
assert.Equal(t, len(test.expectedOut), len(rs))
for i := range rs {
expYaml, err := test.expectedOut[i].AsYAML()
require.NoError(t, err)
actYaml, err := rs[i].AsYAML()
require.NoError(t, err)
assert.Equal(t, expYaml, actYaml)
}
})
}
}
func TestHash(t *testing.T) {
input := `
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
one: ""
binaryData:
two: ""
`
expect := "698h7c7t9m"
k, err := factory.SliceFromBytes([]byte(input))
if err != nil {
t.Fatal(err)
}
result, err := k[0].Hash(factory.Hasher())
if err != nil {
t.Fatal(err)
}
if result != expect {
t.Fatalf("expect %s but got %s", expect, result)
}
}
func TestMoreRNodesFromBytes(t *testing.T) {
type expected struct {
out []string
isErr bool
}
testCases := map[string]struct {
input []byte
exp expected
}{
"garbage": {
input: []byte("garbageIn: garbageOut"),
exp: expected{
isErr: true,
},
},
"noBytes": {
input: []byte{},
exp: expected{},
},
"goodJson": {
input: []byte(`
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
`),
exp: expected{
out: []string{
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
},
},
},
"goodYaml1": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`},
},
},
"goodYaml2": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`, `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`},
},
},
"garbageInOneOfTwoObjects": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`),
exp: expected{
isErr: true,
},
},
"emptyObjects": {
input: []byte(`
---
#a comment
---
`),
exp: expected{
out: []string{},
},
},
"Missing .metadata.name in object": {
input: []byte(`
apiVersion: v1
kind: Namespace
metadata:
annotations:
foo: bar
`),
exp: expected{
isErr: true,
},
},
"nil value in list": {
input: []byte(`
apiVersion: builtin
kind: ConfigMapGenerator
metadata:
name: kube100-site
labels:
app: web
testList:
- testA
-
`),
exp: expected{
isErr: true,
},
},
"List": {
input: []byte(`
apiVersion: v1
kind: List
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`, `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`,
},
},
},
"ConfigMapList": {
input: []byte(`
apiVersion: v1
kind: ConfigMapList
items:
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
- apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []string{
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
},
},
},
"listWithAnchors": {
input: []byte(`
apiVersion: v1
kind: DeploymentList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-a
spec: &foo
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
- apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-b
spec:
*foo
`),
exp: expected{
out: []string{
`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-a"}, ` +
`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`,
`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-b"}, ` +
`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`},
},
},
"simpleAnchor": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: &color-used blue
feeling: *color-used
`),
exp: expected{
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: blue
feeling: blue
`},
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
rs, err := factory.RNodesFromBytes(tc.input)
if err != nil {
assert.True(t, tc.exp.isErr)
return
}
assert.False(t, tc.exp.isErr)
assert.Equal(t, len(tc.exp.out), len(rs))
for i := range rs {
actual, err := rs[i].String()
require.NoError(t, err)
assert.Equal(
t, strings.TrimSpace(tc.exp.out[i]), strings.TrimSpace(actual))
}
})
}
}
func TestDropLocalNodes(t *testing.T) {
testCases := map[string]struct {
input []byte
expected []byte
}{
"localConfigUnset": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
},
"localConfigSet": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
# this annotation causes the Resource to be ignored by kustomize
config.kubernetes.io/local-config: ""
`),
expected: nil,
},
"localConfigSetToTrue": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
config.kubernetes.io/local-config: "true"
`),
expected: nil,
},
"localConfigSetToFalse": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
annotations:
config.kubernetes.io/local-config: "false"
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/local-config: "false"
name: winnie
`),
},
"localConfigMultiInput": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
config.kubernetes.io/local-config: "true"
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
nin, _ := kio.FromBytes(tc.input)
res, err := factory.DropLocalNodes(nin)
require.NoError(t, err)
if tc.expected == nil {
assert.Equal(t, 0, len(res))
} else {
actual, _ := res[0].AsYAML()
assert.Equal(t, tc.expected, actual)
}
})
}
}