Files
kustomize/api/resource/resource_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

1595 lines
34 KiB
Go

// Copyright 2020 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package resource_test
import (
"fmt"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/api/internal/utils"
"sigs.k8s.io/kustomize/api/provider"
. "sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/resid"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
)
var factory = provider.NewDefaultDepProvider().GetResourceFactory()
var testConfigMap = factory.FromMap(
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
"namespace": "hundred-acre-wood",
},
})
//nolint:gosec
const configMapAsString = `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie","namespace":"hundred-acre-wood"}}`
var testDeployment = factory.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "pooh",
},
})
const deploymentAsString = `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"pooh"}}`
func TestAsYAML(t *testing.T) {
expected := `apiVersion: apps/v1
kind: Deployment
metadata:
name: pooh
`
yaml, err := testDeployment.AsYAML()
if err != nil {
t.Fatal(err)
}
if string(yaml) != expected {
t.Fatalf("--- expected\n%s\n--- got\n%s\n", expected, string(yaml))
}
}
func TestResourceString(t *testing.T) {
tests := []struct {
in *Resource
s string
}{
{
in: testConfigMap,
s: configMapAsString,
},
{
in: testDeployment,
s: deploymentAsString,
},
}
for _, test := range tests {
assert.Equal(t, test.in.String(), test.s)
}
}
func TestResourceId(t *testing.T) {
tests := []struct {
in *Resource
id resid.ResId
}{
{
in: testConfigMap,
id: resid.NewResIdWithNamespace(
resid.NewGvk("", "v1", "ConfigMap"),
"winnie", "hundred-acre-wood"),
},
{
in: testDeployment,
id: resid.NewResId(
resid.NewGvk("apps", "v1", "Deployment"), "pooh"),
},
}
for _, test := range tests {
if test.in.OrgId() != test.id {
t.Fatalf("Expected %v, but got %v\n", test.id, test.in.OrgId())
}
}
}
func TestDeepCopy(t *testing.T) {
r := factory.FromMap(
map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "pooh",
},
})
r.AppendRefBy(resid.NewResId(resid.Gvk{Group: "somegroup", Kind: "MyKind"}, "random"))
var1 := types.Var{
Name: "SERVICE_ONE",
ObjRef: types.Target{
Gvk: resid.Gvk{Version: "v1", Kind: "Service"},
Name: "backendOne"},
}
r.AppendRefVarName(var1)
cr := r.DeepCopy()
if !reflect.DeepEqual(r, cr) {
t.Errorf("expected %v\nbut got%v", r, cr)
}
}
func TestApplySmPatch_1(t *testing.T) {
resource, err := factory.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: bingo
spec:
replicas: 1
selector:
matchLabels:
foo: bar
template:
metadata:
labels:
app: mungebot
spec:
containers:
- env:
- name: foo
value: bar
image: nginx
name: nginx
ports:
- containerPort: 80
`))
require.NoError(t, err)
patch, err := factory.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: baseprefix-mungebot
spec:
template:
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 777
`))
require.NoError(t, err)
require.NoError(t, resource.ApplySmPatch(patch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, `apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
baseAnno: This is a base annotation
labels:
app: mungebot
foo: bar
name: bingo
spec:
replicas: 1
selector:
matchLabels:
foo: bar
template:
metadata:
labels:
app: mungebot
spec:
containers:
- env:
- name: foo
value: bar
image: nginx
name: nginx
ports:
- containerPort: 777
- containerPort: 80
`, string(bytes))
}
func TestApplySmPatch_2(t *testing.T) {
resource, err := factory.FromBytes([]byte(`
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
A: X
B: Y
`))
require.NoError(t, err)
patch, err := factory.FromBytes([]byte(`
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B:
C: Z
D: W
baz:
hello: world
`))
require.NoError(t, err)
require.NoError(t, resource.ApplySmPatch(patch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, `apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
A: X
C: Z
D: W
baz:
hello: world
`, string(bytes))
}
func TestApplySmPatch_3(t *testing.T) {
resource, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
patch, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 999
`))
require.NoError(t, err)
require.NoError(t, resource.ApplySmPatch(patch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 999
`, string(bytes))
}
// regression test for https://github.com/kubernetes-sigs/kustomize/issues/5031
func TestApplySmPatch_Idempotency(t *testing.T) {
// an arbitrary number of times to apply the patch
patchApplyCount := 4
resourceYaml := `apiVersion: v1
kind: Deployment
metadata:
creationTimestamp: null
labels: null
name: my-deployment
`
resource, err := factory.FromBytes([]byte(resourceYaml))
require.NoError(t, err)
noOpPatch, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: my-deployment
`))
require.NoError(t, err)
for i := 0; i < patchApplyCount; i++ {
require.NoError(t, resource.ApplySmPatch(noOpPatch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
require.Equal(
t,
resourceYaml,
string(bytes),
"resource should be unchanged after re-application of patch",
)
}
}
func TestApplySmPatchShouldOutputListItemsInCorrectOrder(t *testing.T) {
cases := []struct {
name string
skip bool
patch string
expectedOutput string
}{
{
name: "Order should not change when patch has foo only",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: foo
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: foo
- name: bar
`,
},
{
name: "Order changes when patch has bar only",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: bar
`,
// This test records current behavior, but this behavior might be undesirable.
// If so, feel free to change the test to pass with some improved algorithm.
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: bar
- name: foo
`,
},
{
name: "Order should not change and should include a new item at the beginning when patch has a new list item",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: baz
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: baz
- name: foo
- name: bar
`,
},
{
name: "Order should not change when patch has foo and bar in same order",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: foo
- name: bar
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: foo
- name: bar
`,
},
{
name: "Order should change when patch has foo and bar in different order",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: bar
- name: foo
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: bar
- name: foo
`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.skip {
t.Skip()
}
resource, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
initContainers:
- name: foo
- name: bar
`))
require.NoError(t, err)
patch, err := factory.FromBytes([]byte(tc.patch))
require.NoError(t, err)
require.NoError(t, resource.ApplySmPatch(patch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, tc.expectedOutput, string(bytes))
})
}
}
func TestApplySmPatchShouldOutputPrimitiveListItemsInCorrectOrder(t *testing.T) {
cases := []struct {
name string
skip bool
patch string
expectedOutput string
}{
{
name: "Order should not change when patch has foo only",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
finalizers: ["foo"]
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
finalizers:
- foo
- bar
name: test
`,
},
{
name: "Order should not change when patch has bar only",
skip: true, // TODO: This test should pass but fails currently. Fix the problem and unskip this test
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
finalizers: ["bar"]
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
finalizers:
- foo
- bar
name: test
`,
},
{
name: "Order should not change and should include a new item at the beginning when patch has a new list item",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
finalizers: ["baz"]
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
finalizers:
- baz
- foo
- bar
name: test
`,
},
{
name: "Order should not change when patch has foo and bar in same order",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
finalizers: ["foo", "bar"]
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
finalizers:
- foo
- bar
name: test
`,
},
{
name: "Order should change when patch has foo and bar in different order",
patch: `apiVersion: v1
kind: Pod
metadata:
name: test
finalizers: ["bar", "foo"]
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
finalizers:
- bar
- foo
name: test
`,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.skip {
t.Skip()
}
resource, err := factory.FromBytes([]byte(`
kind: Pod
metadata:
name: test
finalizers: ["foo", "bar"]
`))
require.NoError(t, err)
patch, err := factory.FromBytes([]byte(tc.patch))
require.NoError(t, err)
require.NoError(t, resource.ApplySmPatch(patch))
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, tc.expectedOutput, string(bytes))
})
}
}
func TestMergeDataMapFrom(t *testing.T) {
resource, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: BlahBlah
metadata:
name: clown
data:
fruit: pear
`))
if !assert.NoError(t, err) {
t.FailNow()
}
patch, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Whatever
metadata:
name: spaceship
data:
spaceship: enterprise
`))
if !assert.NoError(t, err) {
t.FailNow()
}
resource.MergeDataMapFrom(patch)
bytes, err := resource.AsYAML()
require.NoError(t, err)
assert.Equal(t, `apiVersion: v1
data:
fruit: pear
spaceship: enterprise
kind: BlahBlah
metadata:
name: clown
`, string(bytes))
}
func TestApplySmPatch_SwapOrder(t *testing.T) {
s1 := `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
B:
C: Z
`
s2 := `
apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
D: W
baz:
hello: world
`
expected := `apiVersion: example.com/v1
kind: Foo
metadata:
name: my-foo
spec:
bar:
C: Z
D: W
baz:
hello: world
`
r1, err := factory.FromBytes([]byte(s1))
require.NoError(t, err)
r2, err := factory.FromBytes([]byte(s2))
require.NoError(t, err)
require.NoError(t, r1.ApplySmPatch(r2))
bytes, err := r1.AsYAML()
require.NoError(t, err)
assert.Equal(t, expected, string(bytes))
r1, _ = factory.FromBytes([]byte(s1))
r2, _ = factory.FromBytes([]byte(s2))
require.NoError(t, r2.ApplySmPatch(r1))
bytes, err = r2.AsYAML()
require.NoError(t, err)
assert.Equal(t, expected, string(bytes))
}
func TestApplySmPatch(t *testing.T) {
const (
myDeployment = "Deployment"
myCRD = "myCRD"
)
tests := map[string]struct {
base string
patch []string
expected string
errorExpected bool
errorMsg string
}{
"withschema-label-image-container": {
base: baseResource(myDeployment),
patch: []string{
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:latest"),
addContainerAndEnvPatch(myDeployment),
},
errorExpected: false,
expected: expectedResultMultiPatch(myDeployment, false),
},
"withschema-image-container-label": {
base: baseResource(myDeployment),
patch: []string{
changeImagePatch(myDeployment, "nginx:latest"),
addContainerAndEnvPatch(myDeployment),
addLabelAndEnvPatch(myDeployment),
},
errorExpected: false,
expected: expectedResultMultiPatch(myDeployment, true),
},
"withschema-container-label-image": {
base: baseResource(myDeployment),
patch: []string{
addContainerAndEnvPatch(myDeployment),
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:latest"),
},
errorExpected: false,
expected: expectedResultMultiPatch(myDeployment, true),
},
"noschema-label-image-container": {
base: baseResource(myCRD),
patch: []string{
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:latest"),
addContainerAndEnvPatch(myCRD),
},
// Might be better if this complained about patch conflict.
// See plugin/builtin/patchstrategicmergetransformer/psmt_test.go
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: ANOTHERENV
value: ANOTHERVALUE
name: nginx
- image: anotherimage
name: anothercontainer
`,
},
"noschema-image-container-label": {
base: baseResource(myCRD),
patch: []string{
changeImagePatch(myCRD, "nginx:latest"),
addContainerAndEnvPatch(myCRD),
addLabelAndEnvPatch(myCRD),
},
// Might be better if this complained about patch conflict.
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
name: nginx
`,
},
"noschema-container-label-image": {
base: baseResource(myCRD),
patch: []string{
addContainerAndEnvPatch(myCRD),
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:latest"),
},
// Might be better if this complained about patch conflict.
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- image: nginx:latest
name: nginx
`,
},
"withschema-label-latest-someV-01": {
base: baseResource(myDeployment),
patch: []string{
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:latest"),
changeImagePatch(myDeployment, "nginx:1.7.9"),
},
expected: `apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
image: nginx:1.7.9
name: nginx
`,
},
"withschema-latest-label-someV-02": {
base: baseResource(myDeployment),
patch: []string{
changeImagePatch(myDeployment, "nginx:latest"),
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:1.7.9"),
},
expected: `apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
image: nginx:1.7.9
name: nginx
`,
},
"withschema-latest-label-someV-03": {
base: baseResource(myDeployment),
patch: []string{
changeImagePatch(myDeployment, "nginx:1.7.9"),
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:latest"),
},
expected: `apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
image: nginx:latest
name: nginx
`,
},
"withschema-latest-label-someV-04": {
base: baseResource(myDeployment),
patch: []string{
changeImagePatch(myDeployment, "nginx:1.7.9"),
changeImagePatch(myDeployment, "nginx:latest"),
addLabelAndEnvPatch(myDeployment),
changeImagePatch(myDeployment, "nginx:nginx"),
},
expected: `apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
image: nginx:nginx
name: nginx
`,
},
"noschema-latest-label-someV-01": {
base: baseResource(myCRD),
patch: []string{
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:latest"),
changeImagePatch(myCRD, "nginx:1.7.9"),
},
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- image: nginx:1.7.9
name: nginx
`,
},
"noschema-latest-label-someV-02": {
base: baseResource(myCRD),
patch: []string{
changeImagePatch(myCRD, "nginx:latest"),
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:1.7.9"),
},
expected: expectedResultJMP("nginx:1.7.9"),
},
"noschema-latest-label-someV-03": {
base: baseResource(myCRD),
patch: []string{
changeImagePatch(myCRD, "nginx:1.7.9"),
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:latest"),
},
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- image: nginx:latest
name: nginx
`,
},
"noschema-latest-label-someV-04": {
base: baseResource(myCRD),
patch: []string{
changeImagePatch(myCRD, "nginx:1.7.9"),
changeImagePatch(myCRD, "nginx:latest"),
addLabelAndEnvPatch(myCRD),
changeImagePatch(myCRD, "nginx:nginx"),
},
expected: `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- image: nginx:nginx
name: nginx
`,
},
}
for name, test := range tests {
resource, err := factory.FromBytes([]byte(test.base))
require.NoError(t, err)
for _, p := range test.patch {
patch, err := factory.FromBytes([]byte(p))
require.NoError(t, err, name)
require.NoError(t, resource.ApplySmPatch(patch), name)
}
bytes, err := resource.AsYAML()
if test.errorExpected {
require.Error(t, err, name)
} else {
require.NoError(t, err, name)
assert.Equal(t, test.expected, string(bytes), name)
}
}
}
func TestResourceStorePreviousId(t *testing.T) {
tests := map[string]struct {
input string
newName string
newNs string
expected string
}{
"default namespace, first previous name": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: oldName
`,
newName: "newName",
newNs: "",
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: newName
`,
},
"default namespace, second previous name": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: oldName2
`,
newName: "newName",
newNs: "",
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,default
name: newName
`,
},
"non-default namespace": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: oldName2
namespace: oldNamespace
`,
newName: "newName",
newNs: "newNamespace",
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
namespace: newNamespace
`,
},
}
factory := provider.NewDefaultDepProvider().GetResourceFactory()
for i := range tests {
test := tests[i]
t.Run(i, func(t *testing.T) {
resources, err := factory.SliceFromBytes([]byte(test.input))
if !assert.NoError(t, err) || len(resources) == 0 {
t.FailNow()
}
r := resources[0]
r.StorePreviousId()
r.SetName(test.newName)
if test.newNs != "" {
r.SetNamespace(test.newNs)
}
bytes, err := r.AsYAML()
if !assert.NoError(t, err) {
t.FailNow()
}
assert.Equal(t, test.expected, string(bytes))
})
}
}
func TestResource_PrevIds(t *testing.T) {
tests := map[string]struct {
input string
expected []resid.ResId
}{
"no previous IDs": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: name
`,
expected: nil,
},
"one previous ID": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: newName
`,
expected: []resid.ResId{
{
Gvk: resid.Gvk{Group: "apps", Version: "v1", Kind: "Secret"},
Name: "oldName",
Namespace: resid.DefaultNamespace,
},
},
},
"two ids": {
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
namespace: newNamespace
`,
expected: []resid.ResId{
{
Gvk: resid.Gvk{Group: "apps", Version: "v1", Kind: "Secret"},
Name: "oldName",
Namespace: resid.DefaultNamespace,
},
{
Gvk: resid.Gvk{Group: "apps", Version: "v1", Kind: "Secret"},
Name: "oldName2",
Namespace: "oldNamespace",
},
},
},
}
factory := provider.NewDefaultDepProvider().GetResourceFactory()
for i := range tests {
test := tests[i]
t.Run(i, func(t *testing.T) {
resources, err := factory.SliceFromBytes([]byte(test.input))
if !assert.NoError(t, err) || len(resources) == 0 {
t.FailNow()
}
r := resources[0]
assert.Equal(t, test.expected, r.PrevIds())
})
}
}
// baseResource produces a base object which used to test
// patch transformation
// Also the structure is matching the Deployment syntax
// the kind can be replaced to allow testing using CRD
// without access to the schema
func baseResource(kind string) string {
res := `
apiVersion: apps/v1
kind: %s
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
spec:
containers:
- name: nginx
image: nginx`
return fmt.Sprintf(res, kind)
}
// addContainerAndEnvPatch produces a patch object which adds
// an entry in the env slice of the first/nginx container
// as well as adding a label in the metadata
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func addLabelAndEnvPatch(kind string) string {
return fmt.Sprintf(`
apiVersion: apps/v1
kind: %s
metadata:
name: deploy1
spec:
template:
metadata:
labels:
some-label: some-value
spec:
containers:
- name: nginx
env:
- name: SOMEENV
value: SOMEVALUE`, kind)
}
// addContainerAndEnvPatch produces a patch object which adds
// an entry in the env slice of the first/nginx container
// as well as adding a second container in the container list
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func addContainerAndEnvPatch(kind string) string {
return fmt.Sprintf(`
apiVersion: apps/v1
kind: %s
metadata:
name: deploy1
spec:
template:
spec:
containers:
- name: nginx
env:
- name: ANOTHERENV
value: ANOTHERVALUE
- name: anothercontainer
image: anotherimage`, kind)
}
// addContainerAndEnvPatch produces a patch object which replaces
// the value of the image field in the first/nginx container
// Note that for SMP/WithSchema merge, the name:nginx entry
// is mandatory
func changeImagePatch(kind string, newImage string) string {
return fmt.Sprintf(`
apiVersion: apps/v1
kind: %s
metadata:
name: deploy1
spec:
template:
spec:
containers:
- name: nginx
image: %s`, kind, newImage)
}
// utility method to build the expected result of a multipatch
// the order of the patches still have influence especially
// in the insertion location within arrays.
func expectedResultMultiPatch(kind string, reversed bool) string {
pattern := `apiVersion: apps/v1
kind: %s
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
%s
image: nginx:latest
name: nginx
- image: anotherimage
name: anothercontainer
`
if reversed {
return fmt.Sprintf(pattern, kind, `- name: SOMEENV
value: SOMEVALUE
- name: ANOTHERENV
value: ANOTHERVALUE`)
}
return fmt.Sprintf(pattern, kind, `- name: ANOTHERENV
value: ANOTHERVALUE
- name: SOMEENV
value: SOMEVALUE`)
}
// utility method building the expected output of a JMP.
// imagename parameter allows to build a result consistent
// with the JMP behavior which basically overrides the
// entire "containers" list.
func expectedResultJMP(imagename string) string {
if imagename == "" {
return `apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- env:
- name: SOMEENV
value: SOMEVALUE
name: nginx
`
}
return fmt.Sprintf(`apiVersion: apps/v1
kind: myCRD
metadata:
name: deploy1
spec:
template:
metadata:
labels:
old-label: old-value
some-label: some-value
spec:
containers:
- image: %s
name: nginx
`, imagename)
}
func TestSameEndingSubarray(t *testing.T) {
testCases := map[string]struct {
a []string
b []string
expected bool
}{
"both nil": {
expected: true,
},
"one nil": {
b: []string{},
expected: true,
},
"both empty": {
a: []string{},
b: []string{},
expected: true,
},
"no1": {
a: []string{"a"},
b: []string{},
expected: false,
},
"no2": {
a: []string{"b", "a"},
b: []string{"b"},
expected: false,
},
"yes1": {
a: []string{"a", "b"},
b: []string{"b"},
expected: true,
},
"yes2": {
a: []string{"a", "b", "c"},
b: []string{"b", "c"},
expected: true,
},
"yes3": {
a: []string{"a", "b", "c", "d", "e", "f"},
b: []string{"f"},
expected: true,
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
assert.Equal(t, tc.expected, utils.SameEndingSubSlice(tc.a, tc.b))
})
}
}
func TestGetGvk(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: apps/v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
gvk := r.GetGvk()
expected := "apps"
actual := gvk.Group
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "v1"
actual = gvk.Version
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
expected = "Deployment"
actual = gvk.Kind
if expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestSetGvk(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
r.SetGvk(resid.GvkFromString("knd.ver.grp"))
gvk := r.GetGvk()
if expected, actual := "grp", gvk.Group; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "ver", gvk.Version; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
if expected, actual := "knd", gvk.Kind; expected != actual {
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestRefBy(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
r.AppendRefBy(resid.FromString("knd1.ver1.gr1/name1.ns1"))
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
internal.config.kubernetes.io/refBy: 'knd1.ver1.gr1/name1.ns1'
spec:
numReplicas: 1
`, r.RNode.MustString())
assert.Equal(t, r.GetRefBy(), []resid.ResId{resid.FromString("knd1.ver1.gr1/name1.ns1")})
r.AppendRefBy(resid.FromString("knd2.ver2.gr2/name2.ns2"))
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
internal.config.kubernetes.io/refBy: 'knd1.ver1.gr1/name1.ns1,knd2.ver2.gr2/name2.ns2'
spec:
numReplicas: 1
`, r.RNode.MustString())
assert.Equal(t, []resid.ResId{
resid.FromString("knd1.ver1.gr1/name1.ns1"),
resid.FromString("knd2.ver2.gr2/name2.ns2"),
}, r.GetRefBy())
}
func TestOrigin(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
origin := &Origin{
Path: "deployment.yaml",
Repo: "github.com/myrepo",
Ref: "master",
}
require.NoError(t, r.SetOrigin(origin))
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
config.kubernetes.io/origin: |
path: deployment.yaml
repo: github.com/myrepo
ref: master
spec:
numReplicas: 1
`, r.MustString())
or, err := r.GetOrigin()
require.NoError(t, err)
assert.Equal(t, origin, or)
}
func TestTransformations(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
require.NoError(t, err)
origin1 := &Origin{
Repo: "github.com/myrepo",
Ref: "master",
ConfiguredIn: "config.yaml",
ConfiguredBy: kyaml.ResourceIdentifier{
TypeMeta: kyaml.TypeMeta{
APIVersion: "builtin",
Kind: "Generator",
},
NameMeta: kyaml.NameMeta{
Name: "my-name",
Namespace: "my-namespace",
},
},
}
origin2 := &Origin{
ConfiguredIn: "../base/config.yaml",
ConfiguredBy: kyaml.ResourceIdentifier{
TypeMeta: kyaml.TypeMeta{
APIVersion: "builtin",
Kind: "Generator",
},
NameMeta: kyaml.NameMeta{
Name: "my-name",
Namespace: "my-namespace",
},
},
}
require.NoError(t, r.AddTransformation(origin1))
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
alpha.config.kubernetes.io/transformations: |
- repo: github.com/myrepo
ref: master
configuredIn: config.yaml
configuredBy:
apiVersion: builtin
kind: Generator
name: my-name
namespace: my-namespace
spec:
numReplicas: 1
`, r.MustString())
require.NoError(t, r.AddTransformation(origin2))
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
alpha.config.kubernetes.io/transformations: |
- repo: github.com/myrepo
ref: master
configuredIn: config.yaml
configuredBy:
apiVersion: builtin
kind: Generator
name: my-name
namespace: my-namespace
- configuredIn: ../base/config.yaml
configuredBy:
apiVersion: builtin
kind: Generator
name: my-name
namespace: my-namespace
spec:
numReplicas: 1
`, r.MustString())
transformations, err := r.GetTransformations()
require.NoError(t, err)
assert.Equal(t, Transformations{origin1, origin2}, transformations)
require.NoError(t, r.ClearTransformations())
assert.Equal(t, `apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`, r.MustString())
}