mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
* fix: return error instead of log.Fatalf() * chore: add meaningful message to error output * chore: add meaningful message to fatal function
1865 lines
42 KiB
Go
1865 lines
42 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 {
|
|
r, err := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": fmt.Sprintf("cm%03d", i),
|
|
},
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// 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, err1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "alice",
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
r2, err2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
r3, err3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err3 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err3)
|
|
}
|
|
r4, err4 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err4 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err4)
|
|
}
|
|
r5, err5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err5 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err5)
|
|
}
|
|
|
|
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, err1 := 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",
|
|
},
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
r2, err2 := 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",
|
|
},
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
r3, err3 := 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",
|
|
},
|
|
},
|
|
})
|
|
if err3 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err3)
|
|
}
|
|
r4, err4 := 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",
|
|
},
|
|
},
|
|
})
|
|
if err4 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err4)
|
|
}
|
|
r5, err5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err5 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err5)
|
|
}
|
|
|
|
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, err1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "alice",
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
r2, err2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
r3, err3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bob",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err3 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err3)
|
|
}
|
|
r4, err4 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err4 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err4)
|
|
}
|
|
r5, err5 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "charlie",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err5 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err5)
|
|
}
|
|
r5.AddNamePrefix("little-")
|
|
r6, err6 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "domino",
|
|
"namespace": "happy",
|
|
},
|
|
})
|
|
if err6 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err6)
|
|
}
|
|
r6.AddNamePrefix("little-")
|
|
r7, err7 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "rbac.authorization.k8s.io/v1",
|
|
"kind": "ClusterRoleBinding",
|
|
"metadata": map[string]interface{}{
|
|
"name": "meh",
|
|
},
|
|
})
|
|
if err7 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err7)
|
|
}
|
|
|
|
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, err1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
r2, err2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
r3, err3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
"namespace": "system",
|
|
},
|
|
})
|
|
if err3 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err3)
|
|
}
|
|
|
|
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, err1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm1",
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
r2, err2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
r3, err3 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "v1",
|
|
"kind": "ConfigMap",
|
|
"metadata": map[string]interface{}{
|
|
"name": "cm2",
|
|
"namespace": "system",
|
|
},
|
|
})
|
|
if err3 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err3)
|
|
}
|
|
|
|
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, err1 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "Deployment",
|
|
"metadata": map[string]interface{}{
|
|
"name": "foo-deploy1",
|
|
},
|
|
})
|
|
if err1 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err1)
|
|
}
|
|
input1 := rmF.FromResource(r1)
|
|
r2, err2 := rf.FromMap(
|
|
map[string]interface{}{
|
|
"apiVersion": "apps/v1",
|
|
"kind": "StatefulSet",
|
|
"metadata": map[string]interface{}{
|
|
"name": "bar-stateful",
|
|
},
|
|
})
|
|
if err2 != nil {
|
|
t.Fatalf("failed to get new instance: %v", err2)
|
|
}
|
|
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)
|
|
}
|