mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-11 17:12:51 +00:00
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.
1786 lines
40 KiB
Go
1786 lines
40 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package resmap_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sigs.k8s.io/kustomize/api/filters/labels"
|
|
"sigs.k8s.io/kustomize/api/provider"
|
|
. "sigs.k8s.io/kustomize/api/resmap"
|
|
"sigs.k8s.io/kustomize/api/resource"
|
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
|
"sigs.k8s.io/kustomize/api/types"
|
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
var depProvider = provider.NewDefaultDepProvider()
|
|
var rf = depProvider.GetResourceFactory()
|
|
var rmF = NewFactory(rf)
|
|
var origin1 = &resource.Origin{
|
|
Repo: "github.com/myrepo",
|
|
Ref: "master",
|
|
ConfiguredIn: "config.yaml",
|
|
ConfiguredBy: yaml.ResourceIdentifier{
|
|
TypeMeta: yaml.TypeMeta{
|
|
APIVersion: "builtin",
|
|
Kind: "Generator",
|
|
},
|
|
NameMeta: yaml.NameMeta{
|
|
Name: "my-name",
|
|
Namespace: "my-namespace",
|
|
},
|
|
},
|
|
}
|
|
var origin2 = &resource.Origin{
|
|
ConfiguredIn: "../base/config.yaml",
|
|
ConfiguredBy: yaml.ResourceIdentifier{
|
|
TypeMeta: yaml.TypeMeta{
|
|
APIVersion: "builtin",
|
|
Kind: "Generator",
|
|
},
|
|
NameMeta: yaml.NameMeta{
|
|
Name: "my-name",
|
|
Namespace: "my-namespace",
|
|
},
|
|
},
|
|
}
|
|
|
|
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
|
t.Helper()
|
|
err := w.Append(r)
|
|
if err != nil {
|
|
t.Fatalf("append error: %v", err)
|
|
}
|
|
}
|
|
func doRemove(t *testing.T, w ResMap, id resid.ResId) {
|
|
t.Helper()
|
|
err := w.Remove(id)
|
|
if err != nil {
|
|
t.Fatalf("remove error: %v", err)
|
|
}
|
|
}
|
|
|
|
// Make a resource with a predictable name.
|
|
func makeCm(i int) *resource.Resource {
|
|
return rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": fmt.Sprintf("cm%03d", i),
|
|
},
|
|
})
|
|
}
|
|
|
|
// Maintain the class invariant that no two
|
|
// resources can have the same CurId().
|
|
func TestAppendRejectsDuplicateResId(t *testing.T) {
|
|
w := New()
|
|
if err := w.Append(makeCm(1)); err != nil {
|
|
t.Fatalf("append error: %v", err)
|
|
}
|
|
err := w.Append(makeCm(1))
|
|
if err == nil {
|
|
t.Fatalf("expected append error")
|
|
}
|
|
if !strings.Contains(
|
|
err.Error(),
|
|
"may not add resource with an already registered id") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAppendRemove(t *testing.T) {
|
|
w1 := New()
|
|
doAppend(t, w1, makeCm(1))
|
|
doAppend(t, w1, makeCm(2))
|
|
doAppend(t, w1, makeCm(3))
|
|
doAppend(t, w1, makeCm(4))
|
|
doAppend(t, w1, makeCm(5))
|
|
doAppend(t, w1, makeCm(6))
|
|
doAppend(t, w1, makeCm(7))
|
|
doRemove(t, w1, makeCm(1).OrgId())
|
|
doRemove(t, w1, makeCm(3).OrgId())
|
|
doRemove(t, w1, makeCm(5).OrgId())
|
|
doRemove(t, w1, makeCm(7).OrgId())
|
|
|
|
w2 := New()
|
|
doAppend(t, w2, makeCm(2))
|
|
doAppend(t, w2, makeCm(4))
|
|
doAppend(t, w2, makeCm(6))
|
|
if !reflect.DeepEqual(w1, w2) {
|
|
w1.Debug("w1")
|
|
w2.Debug("w2")
|
|
t.Fatalf("mismatch")
|
|
}
|
|
|
|
err := w2.Append(makeCm(6))
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
}
|
|
|
|
func TestRemove(t *testing.T) {
|
|
w := New()
|
|
r := makeCm(1)
|
|
err := w.Remove(r.OrgId())
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
err = w.Append(r)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
err = w.Remove(r.OrgId())
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
err = w.Remove(r.OrgId())
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
}
|
|
|
|
func TestReplace(t *testing.T) {
|
|
cm5 := makeCm(5)
|
|
cm700 := makeCm(700)
|
|
otherCm5 := makeCm(5)
|
|
|
|
w := New()
|
|
doAppend(t, w, makeCm(1))
|
|
doAppend(t, w, makeCm(2))
|
|
doAppend(t, w, makeCm(3))
|
|
doAppend(t, w, makeCm(4))
|
|
doAppend(t, w, cm5)
|
|
doAppend(t, w, makeCm(6))
|
|
doAppend(t, w, makeCm(7))
|
|
|
|
oldSize := w.Size()
|
|
_, err := w.Replace(otherCm5)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if w.Size() != oldSize {
|
|
t.Fatalf("unexpected size %d", w.Size())
|
|
}
|
|
if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
|
|
t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
|
|
}
|
|
if err := w.Append(cm5); err == nil {
|
|
t.Fatalf("expected id already there error")
|
|
}
|
|
if err := w.Remove(cm5.OrgId()); err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if err := w.Append(cm700); err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
if err := w.Append(cm5); err != nil {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEncodeAsYaml(t *testing.T) {
|
|
encoded := []byte(`apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: cm1
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: cm2
|
|
`)
|
|
input := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
}).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
}).ResMap()
|
|
out, err := input.AsYaml()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !reflect.DeepEqual(out, encoded) {
|
|
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
|
}
|
|
}
|
|
|
|
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
|
cmap := resid.NewGvk("", "v1", "ConfigMap")
|
|
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "alice",
|
|
},
|
|
})
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
},
|
|
})
|
|
r3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r4 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
|
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
|
|
|
result := m.GetMatchingResourcesByCurrentId(
|
|
resid.NewResId(cmap, "alice").GvknEquals)
|
|
if len(result) != 1 {
|
|
t.Fatalf("Expected single map entry but got %v", result)
|
|
}
|
|
result = m.GetMatchingResourcesByCurrentId(
|
|
resid.NewResId(cmap, "bob").GvknEquals)
|
|
if len(result) != 2 {
|
|
t.Fatalf("Expected two, got %v", result)
|
|
}
|
|
result = m.GetMatchingResourcesByCurrentId(
|
|
resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
|
|
if len(result) != 2 {
|
|
t.Fatalf("Expected two but got %v", result)
|
|
}
|
|
result = m.GetMatchingResourcesByCurrentId(
|
|
resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
|
|
if len(result) != 1 {
|
|
t.Fatalf("Expected single map entry but got %v", result)
|
|
}
|
|
result = m.GetMatchingResourcesByCurrentId(
|
|
resid.NewResId(cmap, "charlie").GvknEquals)
|
|
if len(result) != 1 {
|
|
t.Fatalf("Expected single map entry but got %v", result)
|
|
}
|
|
|
|
//nolint:goconst
|
|
tests := []struct {
|
|
name string
|
|
matcher IdMatcher
|
|
count int
|
|
}{
|
|
{
|
|
"match everything",
|
|
func(resid.ResId) bool { return true },
|
|
5,
|
|
},
|
|
{
|
|
"match nothing",
|
|
func(resid.ResId) bool { return false },
|
|
0,
|
|
},
|
|
{
|
|
"name is alice",
|
|
func(x resid.ResId) bool { return x.Name == "alice" },
|
|
1,
|
|
},
|
|
{
|
|
"name is charlie",
|
|
func(x resid.ResId) bool { return x.Name == "charlie" },
|
|
2,
|
|
},
|
|
{
|
|
"name is bob",
|
|
func(x resid.ResId) bool { return x.Name == "bob" },
|
|
2,
|
|
},
|
|
{
|
|
"happy namespace",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy"
|
|
},
|
|
3,
|
|
},
|
|
{
|
|
"happy deployment",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy" &&
|
|
x.Gvk.Kind == "Deployment"
|
|
},
|
|
1,
|
|
},
|
|
{
|
|
"happy ConfigMap",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy" &&
|
|
x.Gvk.Kind == "ConfigMap"
|
|
},
|
|
2,
|
|
},
|
|
}
|
|
for _, tst := range tests {
|
|
result := m.GetMatchingResourcesByCurrentId(tst.matcher)
|
|
if len(result) != tst.count {
|
|
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
|
tst.name, len(result), tst.count)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGetMatchingResourcesByAnyId(t *testing.T) {
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "new-alice",
|
|
"annotations": map[string]interface{}{
|
|
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
|
|
"internal.config.kubernetes.io/previousNames": "alice",
|
|
"internal.config.kubernetes.io/previousNamespaces": "default",
|
|
},
|
|
},
|
|
})
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "new-bob",
|
|
"annotations": map[string]interface{}{
|
|
"internal.config.kubernetes.io/previousKinds": "ConfigMap,ConfigMap",
|
|
"internal.config.kubernetes.io/previousNames": "bob,bob2",
|
|
"internal.config.kubernetes.io/previousNamespaces": "default,default",
|
|
},
|
|
},
|
|
})
|
|
r3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "new-bob",
|
|
"namespace": "new-happy",
|
|
"annotations": map[string]interface{}{
|
|
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
|
|
"internal.config.kubernetes.io/previousNames": "bob",
|
|
"internal.config.kubernetes.io/previousNamespaces": "happy",
|
|
},
|
|
},
|
|
})
|
|
r4 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
"annotations": map[string]interface{}{
|
|
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
|
|
"internal.config.kubernetes.io/previousNames": "charlie",
|
|
"internal.config.kubernetes.io/previousNamespaces": "default",
|
|
},
|
|
},
|
|
})
|
|
r5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
|
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
|
|
|
tests := []struct {
|
|
name string
|
|
matcher IdMatcher
|
|
count int
|
|
}{
|
|
{
|
|
"match everything",
|
|
func(resid.ResId) bool { return true },
|
|
5,
|
|
},
|
|
{
|
|
"match nothing",
|
|
func(resid.ResId) bool { return false },
|
|
0,
|
|
},
|
|
{
|
|
"name is alice",
|
|
func(x resid.ResId) bool { return x.Name == "alice" },
|
|
1,
|
|
},
|
|
{
|
|
"name is charlie",
|
|
func(x resid.ResId) bool { return x.Name == "charlie" },
|
|
2,
|
|
},
|
|
{
|
|
"name is bob",
|
|
func(x resid.ResId) bool { return x.Name == "bob" },
|
|
2,
|
|
},
|
|
{
|
|
"happy namespace",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy"
|
|
},
|
|
3,
|
|
},
|
|
{
|
|
"happy deployment",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy" &&
|
|
x.Gvk.Kind == "Deployment"
|
|
},
|
|
1,
|
|
},
|
|
{
|
|
"happy ConfigMap",
|
|
func(x resid.ResId) bool {
|
|
return x.Namespace == "happy" &&
|
|
x.Gvk.Kind == "ConfigMap"
|
|
},
|
|
2,
|
|
},
|
|
}
|
|
for _, tst := range tests {
|
|
result := m.GetMatchingResourcesByAnyId(tst.matcher)
|
|
if len(result) != tst.count {
|
|
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
|
tst.name, len(result), tst.count)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "alice",
|
|
},
|
|
})
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
},
|
|
})
|
|
r3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r4 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r5.AddNamePrefix("little-")
|
|
r6 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "domino",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
r6.AddNamePrefix("little-")
|
|
r7 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
|
"kind": "ClusterRoleBinding",
|
|
"metadata": map[string]interface{}{
|
|
"name": "meh",
|
|
},
|
|
})
|
|
|
|
tests := map[string]struct {
|
|
filter *resource.Resource
|
|
expected ResMap
|
|
}{
|
|
"default namespace 1": {
|
|
filter: r2,
|
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
|
},
|
|
"default namespace 2": {
|
|
filter: r1,
|
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
|
},
|
|
"happy namespace no prefix": {
|
|
filter: r3,
|
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
},
|
|
"happy namespace with prefix": {
|
|
filter: r5,
|
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
},
|
|
"cluster level": {
|
|
filter: r7,
|
|
expected: resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
|
},
|
|
}
|
|
m := resmaptest_test.NewRmBuilder(t, rf).
|
|
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
|
|
for name, test := range tests {
|
|
test := test
|
|
t.Run(name, func(t *testing.T) {
|
|
got, err1 := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
|
if err1 != nil {
|
|
t.Fatalf("Expected error %v: ", err1)
|
|
}
|
|
err := test.expected.ErrorIfNotEqualLists(got)
|
|
if err != nil {
|
|
test.expected.Debug("expected")
|
|
got.Debug("actual")
|
|
t.Fatalf("Expected match")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeepCopy(t *testing.T) {
|
|
rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
}).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
}).ResMap()
|
|
|
|
rm2 := rm1.DeepCopy()
|
|
|
|
if &rm1 == &rm2 {
|
|
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
|
}
|
|
err := rm1.ErrorIfNotEqualLists(rm1)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestErrorIfNotEqualSets(t *testing.T) {
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
})
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
})
|
|
r3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
"namespace": "system",
|
|
},
|
|
})
|
|
|
|
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
if err := m1.ErrorIfNotEqualSets(m1); err != nil {
|
|
t.Fatalf("object should equal itself %v", err)
|
|
}
|
|
|
|
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
|
if err := m1.ErrorIfNotEqualSets(m2); err == nil {
|
|
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
|
}
|
|
|
|
m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
|
|
if err := m2.ErrorIfNotEqualSets(m3); err == nil {
|
|
t.Fatalf("%v should not equal %v %v", m2, m3, err)
|
|
}
|
|
|
|
m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
}}).ResMap()
|
|
if err := m2.ErrorIfNotEqualSets(m3); err != nil {
|
|
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
|
}
|
|
|
|
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
|
|
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
|
|
m4 = m1.ShallowCopy()
|
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
m4 = m1.DeepCopy()
|
|
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
}
|
|
|
|
func TestErrorIfNotEqualLists(t *testing.T) {
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
})
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
})
|
|
r3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
"namespace": "system",
|
|
},
|
|
})
|
|
|
|
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
if err := m1.ErrorIfNotEqualLists(m1); err != nil {
|
|
t.Fatalf("object should equal itself %v", err)
|
|
}
|
|
|
|
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
|
if err := m1.ErrorIfNotEqualLists(m2); err == nil {
|
|
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
|
}
|
|
|
|
m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
}}).ResMap()
|
|
if err := m2.ErrorIfNotEqualLists(m3); err != nil {
|
|
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
|
}
|
|
|
|
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
|
|
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
|
if err := m1.ErrorIfNotEqualLists(m4); err == nil {
|
|
t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
|
|
m4 = m1.ShallowCopy()
|
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
m4 = m1.DeepCopy()
|
|
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
|
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
|
}
|
|
}
|
|
|
|
func TestAppendAll(t *testing.T) {
|
|
r1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "foo-deploy1",
|
|
},
|
|
})
|
|
input1 := rmF.FromResource(r1)
|
|
r2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "StatefulSet",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bar-stateful",
|
|
},
|
|
})
|
|
input2 := rmF.FromResource(r2)
|
|
|
|
expected := New()
|
|
if err := expected.Append(r1); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := expected.Append(r2); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if err := input1.AppendAll(input2); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
|
input1.Debug("1")
|
|
expected.Debug("ex")
|
|
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
|
}
|
|
if err := input1.AppendAll(nil); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
|
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
|
}
|
|
}
|
|
|
|
func makeMap1(t *testing.T) ResMap {
|
|
t.Helper()
|
|
r, err := rf.FromMapAndOption(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cmap",
|
|
},
|
|
"data": map[string]interface{}{
|
|
"a": "x",
|
|
"b": "y",
|
|
},
|
|
}, &types.GeneratorArgs{
|
|
Behavior: "create",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected new intance with an options but got error: %v", err)
|
|
}
|
|
return rmF.FromResource(r)
|
|
}
|
|
|
|
func makeMap2(t *testing.T, b types.GenerationBehavior) ResMap {
|
|
t.Helper()
|
|
r, err := rf.FromMapAndOption(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cmap",
|
|
},
|
|
"data": map[string]interface{}{
|
|
"a": "u",
|
|
"b": "v",
|
|
"c": "w",
|
|
},
|
|
}, &types.GeneratorArgs{
|
|
Behavior: b.String(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected new intance with an options but got error: %v", err)
|
|
}
|
|
return rmF.FromResource(r)
|
|
}
|
|
|
|
func TestAbsorbAll(t *testing.T) {
|
|
metadata := map[string]interface{}{
|
|
"name": "cmap",
|
|
}
|
|
|
|
r, err := rf.FromMapAndOption(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": metadata,
|
|
"data": map[string]interface{}{
|
|
"a": "u",
|
|
"b": "v",
|
|
"c": "w",
|
|
},
|
|
},
|
|
&types.GeneratorArgs{
|
|
Behavior: "create",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("expected new intance with an options but got error: %v", err)
|
|
}
|
|
expected := rmF.FromResource(r)
|
|
w := makeMap1(t)
|
|
require.NoError(t, w.AbsorbAll(makeMap2(t, types.BehaviorMerge)))
|
|
expected.RemoveBuildAnnotations()
|
|
w.RemoveBuildAnnotations()
|
|
require.NoError(t, expected.ErrorIfNotEqualLists(w))
|
|
w = makeMap1(t)
|
|
require.NoError(t, w.AbsorbAll(nil))
|
|
require.NoError(t, w.ErrorIfNotEqualLists(makeMap1(t)))
|
|
|
|
w = makeMap1(t)
|
|
w2 := makeMap2(t, types.BehaviorReplace)
|
|
require.NoError(t, w.AbsorbAll(w2))
|
|
w2.RemoveBuildAnnotations()
|
|
require.NoError(t, w2.ErrorIfNotEqualLists(w))
|
|
err = makeMap1(t).AbsorbAll(makeMap2(t, types.BehaviorUnspecified))
|
|
require.Error(t, err)
|
|
assert.True(
|
|
t, strings.Contains(err.Error(), "behavior must be merge or replace"))
|
|
}
|
|
|
|
func TestToRNodeSlice(t *testing.T) {
|
|
input := `apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: ClusterRole
|
|
metadata:
|
|
name: namespace-reader
|
|
rules:
|
|
- apiGroups:
|
|
- ""
|
|
resources:
|
|
- namespaces
|
|
verbs:
|
|
- get
|
|
- watch
|
|
- list
|
|
`
|
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
b := bytes.NewBufferString("")
|
|
for i, n := range rm.ToRNodeSlice() {
|
|
if i != 0 {
|
|
b.WriteString("---\n")
|
|
}
|
|
s, err := n.String()
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
b.WriteString(s)
|
|
}
|
|
|
|
if !reflect.DeepEqual(input, b.String()) {
|
|
t.Fatalf("actual doesn't match expected.\nActual:\n%s\n===\nExpected:\n%s\n",
|
|
b.String(), input)
|
|
}
|
|
}
|
|
|
|
func TestDeAnchorSingleDoc(t *testing.T) {
|
|
input := `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: wildcard
|
|
data:
|
|
color: &color-used blue
|
|
feeling: *color-used
|
|
`
|
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
|
require.NoError(t, err)
|
|
require.NoError(t, rm.DeAnchor())
|
|
yaml, err := rm.AsYaml()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, strings.TrimSpace(`
|
|
apiVersion: v1
|
|
data:
|
|
color: blue
|
|
feeling: blue
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: wildcard
|
|
`), strings.TrimSpace(string(yaml)))
|
|
}
|
|
|
|
func TestDeAnchorIntoDoc(t *testing.T) {
|
|
input := `apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: probes-test
|
|
spec:
|
|
template:
|
|
spec:
|
|
readinessProbe: &probe
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
failureThreshold: 3
|
|
httpGet:
|
|
port: http
|
|
path: /health
|
|
livenessProbe:
|
|
<<: *probe
|
|
`
|
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
|
require.NoError(t, err)
|
|
require.NoError(t, rm.DeAnchor())
|
|
yaml, err := rm.AsYaml()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, strings.TrimSpace(`apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: probes-test
|
|
spec:
|
|
template:
|
|
spec:
|
|
livenessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /health
|
|
port: http
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
readinessProbe:
|
|
failureThreshold: 3
|
|
httpGet:
|
|
path: /health
|
|
port: http
|
|
periodSeconds: 5
|
|
timeoutSeconds: 3
|
|
`), strings.TrimSpace(string(yaml)))
|
|
}
|
|
|
|
// Anchor references don't cross YAML document boundaries.
|
|
func TestDeAnchorMultiDoc(t *testing.T) {
|
|
input := `apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: betty
|
|
data:
|
|
color: &color-used blue
|
|
feeling: *color-used
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: bob
|
|
data:
|
|
color: red
|
|
feeling: *color-used
|
|
`
|
|
_, err := rmF.NewResMapFromBytes([]byte(input))
|
|
require.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unknown anchor 'color-used' referenced")
|
|
}
|
|
|
|
// Anchor references cross list elements in a ResourceList.
|
|
func TestDeAnchorResourceList(t *testing.T) {
|
|
input := `apiVersion: config.kubernetes.io/v1
|
|
kind: ResourceList
|
|
metadata:
|
|
name: aShortList
|
|
items:
|
|
- apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: betty
|
|
data:
|
|
color: &color-used blue
|
|
feeling: *color-used
|
|
- apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: bob
|
|
data:
|
|
color: red
|
|
feeling: *color-used
|
|
`
|
|
rm, err := rmF.NewResMapFromBytes([]byte(input))
|
|
require.NoError(t, err)
|
|
require.NoError(t, rm.DeAnchor())
|
|
yaml, err := rm.AsYaml()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, strings.TrimSpace(`
|
|
apiVersion: v1
|
|
data:
|
|
color: blue
|
|
feeling: blue
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: betty
|
|
---
|
|
apiVersion: v1
|
|
data:
|
|
color: red
|
|
feeling: blue
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: bob
|
|
`), strings.TrimSpace(string(yaml)))
|
|
}
|
|
|
|
func TestApplySmPatch_General(t *testing.T) {
|
|
const (
|
|
myDeployment = "Deployment"
|
|
myCRD = "myCRD"
|
|
expectedResultSMP = `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
|
|
name: nginx
|
|
`
|
|
)
|
|
tests := map[string]struct {
|
|
base []string
|
|
patches []string
|
|
expected []string
|
|
errorExpected bool
|
|
errorMsg string
|
|
}{
|
|
"clown": {
|
|
base: []string{`apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: clown
|
|
spec:
|
|
numReplicas: 1
|
|
`,
|
|
},
|
|
patches: []string{`apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: clown
|
|
spec:
|
|
numReplicas: 999
|
|
`,
|
|
},
|
|
errorExpected: false,
|
|
expected: []string{
|
|
`apiVersion: v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: clown
|
|
spec:
|
|
numReplicas: 999
|
|
`,
|
|
},
|
|
},
|
|
"confusion": {
|
|
base: []string{`apiVersion: example.com/v1
|
|
kind: Foo
|
|
metadata:
|
|
name: my-foo
|
|
spec:
|
|
bar:
|
|
A: X
|
|
B: Y
|
|
`,
|
|
},
|
|
patches: []string{`apiVersion: example.com/v1
|
|
kind: Foo
|
|
metadata:
|
|
name: my-foo
|
|
spec:
|
|
bar:
|
|
B:
|
|
C: Z
|
|
`, `apiVersion: example.com/v1
|
|
kind: Foo
|
|
metadata:
|
|
name: my-foo
|
|
spec:
|
|
bar:
|
|
C: Z
|
|
D: W
|
|
baz:
|
|
hello: world
|
|
`,
|
|
},
|
|
errorExpected: false,
|
|
expected: []string{
|
|
`apiVersion: example.com/v1
|
|
kind: Foo
|
|
metadata:
|
|
name: my-foo
|
|
spec:
|
|
bar:
|
|
A: X
|
|
C: Z
|
|
D: W
|
|
baz:
|
|
hello: world
|
|
`,
|
|
},
|
|
},
|
|
"withschema-ns1-ns2-one": {
|
|
base: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
addNamespace("ns2", baseResource(myDeployment)),
|
|
},
|
|
patches: []string{
|
|
addNamespace("ns1", addLabelAndEnvPatch(myDeployment)),
|
|
addNamespace("ns2", addLabelAndEnvPatch(myDeployment)),
|
|
},
|
|
errorExpected: false,
|
|
expected: []string{
|
|
addNamespace("ns1", expectedResultSMP),
|
|
addNamespace("ns2", expectedResultSMP),
|
|
},
|
|
},
|
|
"withschema-ns1-ns2-two": {
|
|
base: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
},
|
|
patches: []string{
|
|
addNamespace("ns2", changeImagePatch(myDeployment)),
|
|
},
|
|
expected: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
},
|
|
},
|
|
"withschema-ns1-ns2-three": {
|
|
base: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
},
|
|
patches: []string{
|
|
addNamespace("ns1", changeImagePatch(myDeployment)),
|
|
},
|
|
expected: []string{
|
|
`apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: deploy1
|
|
namespace: ns1
|
|
spec:
|
|
template:
|
|
metadata:
|
|
labels:
|
|
old-label: old-value
|
|
spec:
|
|
containers:
|
|
- image: nginx:1.7.9
|
|
name: nginx
|
|
`,
|
|
},
|
|
},
|
|
"withschema-nil-ns2": {
|
|
base: []string{
|
|
baseResource(myDeployment),
|
|
},
|
|
patches: []string{
|
|
addNamespace("ns2", changeImagePatch(myDeployment)),
|
|
},
|
|
expected: []string{
|
|
baseResource(myDeployment),
|
|
},
|
|
},
|
|
"withschema-ns1-nil": {
|
|
base: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
},
|
|
patches: []string{
|
|
changeImagePatch(myDeployment),
|
|
},
|
|
expected: []string{
|
|
addNamespace("ns1", baseResource(myDeployment)),
|
|
},
|
|
},
|
|
"noschema-ns1-ns2-one": {
|
|
base: []string{
|
|
addNamespace("ns1", baseResource(myCRD)),
|
|
addNamespace("ns2", baseResource(myCRD)),
|
|
},
|
|
patches: []string{
|
|
addNamespace("ns1", addLabelAndEnvPatch(myCRD)),
|
|
addNamespace("ns2", addLabelAndEnvPatch(myCRD)),
|
|
},
|
|
errorExpected: false,
|
|
expected: []string{
|
|
addNamespace("ns1", expectedResultJMP("")),
|
|
addNamespace("ns2", expectedResultJMP("")),
|
|
},
|
|
},
|
|
"noschema-ns1-ns2-two": {
|
|
base: []string{addNamespace("ns1", baseResource(myCRD))},
|
|
patches: []string{addNamespace("ns2", changeImagePatch(myCRD))},
|
|
expected: []string{addNamespace("ns1", baseResource(myCRD))},
|
|
},
|
|
"noschema-nil-ns2": {
|
|
base: []string{baseResource(myCRD)},
|
|
patches: []string{addNamespace("ns2", changeImagePatch(myCRD))},
|
|
expected: []string{baseResource(myCRD)},
|
|
},
|
|
"noschema-ns1-nil": {
|
|
base: []string{addNamespace("ns1", baseResource(myCRD))},
|
|
patches: []string{changeImagePatch(myCRD)},
|
|
expected: []string{addNamespace("ns1", baseResource(myCRD))},
|
|
},
|
|
}
|
|
for n := range tests {
|
|
tc := tests[n]
|
|
t.Run(n, func(t *testing.T) {
|
|
m, err := rmF.NewResMapFromBytes([]byte(strings.Join(tc.base, "\n---\n")))
|
|
require.NoError(t, err)
|
|
foundError := false
|
|
for _, patch := range tc.patches {
|
|
rp, err := rf.FromBytes([]byte(patch))
|
|
require.NoError(t, err)
|
|
idSet := resource.MakeIdSet([]*resource.Resource{rp})
|
|
if err = m.ApplySmPatch(idSet, rp); err != nil {
|
|
foundError = true
|
|
break
|
|
}
|
|
}
|
|
if foundError {
|
|
assert.True(t, tc.errorExpected)
|
|
// compare error message?
|
|
return
|
|
}
|
|
assert.False(t, tc.errorExpected)
|
|
m.RemoveBuildAnnotations()
|
|
yml, err := m.AsYaml()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, strings.Join(tc.expected, "---\n"), string(yml))
|
|
})
|
|
}
|
|
}
|
|
|
|
// simple utility function to add an namespace in a resource
|
|
// used as base, patch or expected result. Simply looks
|
|
// for specs: in order to add namespace: xxxx before this line
|
|
func addNamespace(namespace string, base string) string {
|
|
res := strings.Replace(base,
|
|
"\nspec:\n",
|
|
"\n namespace: "+namespace+"\nspec:\n",
|
|
1)
|
|
return res
|
|
}
|
|
|
|
// DeleteOddsFilter deletes the odd entries, removing nodes.
|
|
// This is a ridiculous filter for testing.
|
|
type DeleteOddsFilter struct{}
|
|
|
|
func (f DeleteOddsFilter) Filter(
|
|
nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
|
|
for i := range nodes {
|
|
if i%2 == 0 {
|
|
// Keep the even entries, drop the odd entries.
|
|
result = append(result, nodes[i])
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// CloneOddsFilter deletes even entries and clones odd entries,
|
|
// making new nodes.
|
|
// This is a ridiculous filter for testing.
|
|
type CloneOddsFilter struct{}
|
|
|
|
func (f CloneOddsFilter) Filter(
|
|
nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
|
|
for i := range nodes {
|
|
if i%2 != 0 {
|
|
newNode := nodes[i].Copy()
|
|
// Add suffix to the name, so that it's unique (w/r to this test).
|
|
newNode.SetName(newNode.GetName() + "Clone")
|
|
// Return a ptr to the copy.
|
|
result = append(result, nodes[i], newNode)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func TestApplyFilter(t *testing.T) {
|
|
tests := map[string]struct {
|
|
input string
|
|
f kio.Filter
|
|
expected string
|
|
}{
|
|
"labels": {
|
|
input: `
|
|
apiVersion: example.com/v1
|
|
kind: Beans
|
|
metadata:
|
|
name: myBeans
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Franks
|
|
metadata:
|
|
name: myFranks
|
|
`,
|
|
f: labels.Filter{
|
|
Labels: map[string]string{
|
|
"a": "foo",
|
|
"b": "bar",
|
|
},
|
|
FsSlice: types.FsSlice{
|
|
{
|
|
Gvk: resid.NewGvk("example.com", "v1", "Beans"),
|
|
Path: "metadata/labels",
|
|
CreateIfNotPresent: true,
|
|
},
|
|
},
|
|
},
|
|
expected: `
|
|
apiVersion: example.com/v1
|
|
kind: Beans
|
|
metadata:
|
|
labels:
|
|
a: foo
|
|
b: bar
|
|
name: myBeans
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Franks
|
|
metadata:
|
|
name: myFranks
|
|
`,
|
|
},
|
|
"deleteOddNodes": {
|
|
input: `
|
|
apiVersion: example.com/v1
|
|
kind: Zero
|
|
metadata:
|
|
name: r0
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: One
|
|
metadata:
|
|
name: r1
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Two
|
|
metadata:
|
|
name: r2
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Three
|
|
metadata:
|
|
name: r3
|
|
`,
|
|
f: DeleteOddsFilter{},
|
|
expected: `
|
|
apiVersion: example.com/v1
|
|
kind: Zero
|
|
metadata:
|
|
name: r0
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Two
|
|
metadata:
|
|
name: r2
|
|
`,
|
|
},
|
|
"cloneOddNodes": {
|
|
// input list has five entries
|
|
input: `
|
|
apiVersion: example.com/v1
|
|
kind: Zero
|
|
metadata:
|
|
name: r0
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: One
|
|
metadata:
|
|
name: r1
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Two
|
|
metadata:
|
|
name: r2
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Three
|
|
metadata:
|
|
name: r3
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Four
|
|
metadata:
|
|
name: r4
|
|
`,
|
|
f: CloneOddsFilter{},
|
|
// output has four, but half are newly created nodes.
|
|
expected: `
|
|
apiVersion: example.com/v1
|
|
kind: One
|
|
metadata:
|
|
name: r1
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: One
|
|
metadata:
|
|
name: r1Clone
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Three
|
|
metadata:
|
|
name: r3
|
|
---
|
|
apiVersion: example.com/v1
|
|
kind: Three
|
|
metadata:
|
|
name: r3Clone
|
|
`,
|
|
},
|
|
}
|
|
for name := range tests {
|
|
tc := tests[name]
|
|
t.Run(name, func(t *testing.T) {
|
|
m, err := rmF.NewResMapFromBytes([]byte(tc.input))
|
|
require.NoError(t, err)
|
|
require.NoError(t, m.ApplyFilter(tc.f))
|
|
kusttest_test.AssertActualEqualsExpectedWithTweak(
|
|
t, m, nil, tc.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestApplySmPatch_Deletion(t *testing.T) {
|
|
target := `
|
|
apiVersion: apps/v1
|
|
metadata:
|
|
name: myDeploy
|
|
kind: Deployment
|
|
spec:
|
|
replica: 2
|
|
template:
|
|
metadata:
|
|
labels:
|
|
old-label: old-value
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: nginx
|
|
`
|
|
tests := map[string]struct {
|
|
patch string
|
|
expected string
|
|
finalMapSize int
|
|
}{
|
|
"delete1": {
|
|
patch: `apiVersion: apps/v1
|
|
metadata:
|
|
name: myDeploy
|
|
kind: Deployment
|
|
spec:
|
|
replica: 2
|
|
template:
|
|
$patch: delete
|
|
metadata:
|
|
labels:
|
|
old-label: old-value
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: nginx
|
|
`,
|
|
expected: `apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: myDeploy
|
|
spec:
|
|
replica: 2
|
|
`,
|
|
finalMapSize: 1,
|
|
},
|
|
"delete2": {
|
|
patch: `apiVersion: apps/v1
|
|
metadata:
|
|
name: myDeploy
|
|
kind: Deployment
|
|
spec:
|
|
$patch: delete
|
|
replica: 2
|
|
template:
|
|
metadata:
|
|
labels:
|
|
old-label: old-value
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: nginx
|
|
`,
|
|
expected: `apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: myDeploy
|
|
`,
|
|
finalMapSize: 1,
|
|
},
|
|
"delete3": {
|
|
patch: `apiVersion: apps/v1
|
|
metadata:
|
|
name: myDeploy
|
|
kind: Deployment
|
|
$patch: delete
|
|
`,
|
|
expected: "",
|
|
finalMapSize: 0,
|
|
},
|
|
}
|
|
for name := range tests {
|
|
tc := tests[name]
|
|
t.Run(name, func(t *testing.T) {
|
|
m, err := rmF.NewResMapFromBytes([]byte(target))
|
|
require.NoError(t, err, name)
|
|
idSet := resource.MakeIdSet(m.Resources())
|
|
assert.Equal(t, 1, idSet.Size(), name)
|
|
p, err := rf.FromBytes([]byte(tc.patch))
|
|
require.NoError(t, err, name)
|
|
require.NoError(t, m.ApplySmPatch(idSet, p), name)
|
|
assert.Equal(t, tc.finalMapSize, m.Size(), name)
|
|
m.RemoveBuildAnnotations()
|
|
yml, err := m.AsYaml()
|
|
require.NoError(t, err, name)
|
|
assert.Equal(t, tc.expected, string(yml), name)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOriginAnnotations(t *testing.T) {
|
|
w := New()
|
|
for i := 0; i < 3; i++ {
|
|
require.NoError(t, w.Append(makeCm(i)))
|
|
}
|
|
// this should add an origin annotation to every resource
|
|
require.NoError(t, w.AddOriginAnnotation(origin1))
|
|
resources := w.Resources()
|
|
for _, res := range resources {
|
|
or, err := res.GetOrigin()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, origin1, or)
|
|
}
|
|
// this should not overwrite the existing origin annotations
|
|
require.NoError(t, w.AddOriginAnnotation(origin2))
|
|
for _, res := range resources {
|
|
or, err := res.GetOrigin()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, origin1, or)
|
|
}
|
|
// this should remove origin annotations from all resources
|
|
require.NoError(t, w.RemoveOriginAnnotations())
|
|
for _, res := range resources {
|
|
or, err := res.GetOrigin()
|
|
require.NoError(t, err)
|
|
assert.Nil(t, or)
|
|
}
|
|
}
|
|
|
|
func TestTransformerAnnotations(t *testing.T) {
|
|
w := New()
|
|
for i := 0; i < 3; i++ {
|
|
require.NoError(t, w.Append(makeCm(i)))
|
|
}
|
|
// this should add an origin annotation to every resource
|
|
require.NoError(t, w.AddTransformerAnnotation(origin1))
|
|
resources := w.Resources()
|
|
for _, res := range resources {
|
|
or, err := res.GetOrigin()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, origin1, or)
|
|
}
|
|
// this should add a transformer annotation to every resource
|
|
require.NoError(t, w.AddTransformerAnnotation(origin2))
|
|
for _, res := range resources {
|
|
or, err := res.GetOrigin()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, origin1, or)
|
|
tr, err := res.GetTransformations()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, resource.Transformations{origin2}, tr)
|
|
}
|
|
// remove transformer annotations from all resources
|
|
require.NoError(t, w.RemoveTransformerAnnotations())
|
|
for _, res := range resources {
|
|
tr, err := res.GetTransformations()
|
|
require.NoError(t, err)
|
|
assert.Nil(t, tr)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return fmt.Sprintf(`apiVersion: apps/v1
|
|
kind: %s
|
|
metadata:
|
|
name: deploy1
|
|
spec:
|
|
template:
|
|
metadata:
|
|
labels:
|
|
old-label: old-value
|
|
spec:
|
|
containers:
|
|
- image: nginx
|
|
name: nginx
|
|
`, 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)
|
|
}
|
|
|
|
// changeImagePatch 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) string {
|
|
return fmt.Sprintf(`apiVersion: apps/v1
|
|
kind: %s
|
|
metadata:
|
|
name: deploy1
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: nginx
|
|
image: "nginx:1.7.9"`, kind)
|
|
}
|
|
|
|
// 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)
|
|
}
|