...

Source file src/sigs.k8s.io/kustomize/api/resmap/reswrangler_test.go

Documentation: sigs.k8s.io/kustomize/api/resmap

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package resmap_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  	"sigs.k8s.io/kustomize/api/filters/labels"
    16  	"sigs.k8s.io/kustomize/api/provider"
    17  	. "sigs.k8s.io/kustomize/api/resmap"
    18  	"sigs.k8s.io/kustomize/api/resource"
    19  	kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
    20  	resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
    21  	"sigs.k8s.io/kustomize/api/types"
    22  	"sigs.k8s.io/kustomize/kyaml/kio"
    23  	"sigs.k8s.io/kustomize/kyaml/resid"
    24  	"sigs.k8s.io/kustomize/kyaml/yaml"
    25  )
    26  
    27  var depProvider = provider.NewDefaultDepProvider()
    28  var rf = depProvider.GetResourceFactory()
    29  var rmF = NewFactory(rf)
    30  var origin1 = &resource.Origin{
    31  	Repo:         "github.com/myrepo",
    32  	Ref:          "master",
    33  	ConfiguredIn: "config.yaml",
    34  	ConfiguredBy: yaml.ResourceIdentifier{
    35  		TypeMeta: yaml.TypeMeta{
    36  			APIVersion: "builtin",
    37  			Kind:       "Generator",
    38  		},
    39  		NameMeta: yaml.NameMeta{
    40  			Name:      "my-name",
    41  			Namespace: "my-namespace",
    42  		},
    43  	},
    44  }
    45  var origin2 = &resource.Origin{
    46  	ConfiguredIn: "../base/config.yaml",
    47  	ConfiguredBy: yaml.ResourceIdentifier{
    48  		TypeMeta: yaml.TypeMeta{
    49  			APIVersion: "builtin",
    50  			Kind:       "Generator",
    51  		},
    52  		NameMeta: yaml.NameMeta{
    53  			Name:      "my-name",
    54  			Namespace: "my-namespace",
    55  		},
    56  	},
    57  }
    58  
    59  func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
    60  	t.Helper()
    61  	err := w.Append(r)
    62  	if err != nil {
    63  		t.Fatalf("append error: %v", err)
    64  	}
    65  }
    66  func doRemove(t *testing.T, w ResMap, id resid.ResId) {
    67  	t.Helper()
    68  	err := w.Remove(id)
    69  	if err != nil {
    70  		t.Fatalf("remove error: %v", err)
    71  	}
    72  }
    73  
    74  // Make a resource with a predictable name.
    75  func makeCm(i int) *resource.Resource {
    76  	return rf.FromMap(
    77  		map[string]interface{}{
    78  			"apiVersion": "v1",
    79  			"kind":       "ConfigMap",
    80  			"metadata": map[string]interface{}{
    81  				"name": fmt.Sprintf("cm%03d", i),
    82  			},
    83  		})
    84  }
    85  
    86  // Maintain the class invariant that no two
    87  // resources can have the same CurId().
    88  func TestAppendRejectsDuplicateResId(t *testing.T) {
    89  	w := New()
    90  	if err := w.Append(makeCm(1)); err != nil {
    91  		t.Fatalf("append error: %v", err)
    92  	}
    93  	err := w.Append(makeCm(1))
    94  	if err == nil {
    95  		t.Fatalf("expected append error")
    96  	}
    97  	if !strings.Contains(
    98  		err.Error(),
    99  		"may not add resource with an already registered id") {
   100  		t.Fatalf("unexpected error: %v", err)
   101  	}
   102  }
   103  
   104  func TestAppendRemove(t *testing.T) {
   105  	w1 := New()
   106  	doAppend(t, w1, makeCm(1))
   107  	doAppend(t, w1, makeCm(2))
   108  	doAppend(t, w1, makeCm(3))
   109  	doAppend(t, w1, makeCm(4))
   110  	doAppend(t, w1, makeCm(5))
   111  	doAppend(t, w1, makeCm(6))
   112  	doAppend(t, w1, makeCm(7))
   113  	doRemove(t, w1, makeCm(1).OrgId())
   114  	doRemove(t, w1, makeCm(3).OrgId())
   115  	doRemove(t, w1, makeCm(5).OrgId())
   116  	doRemove(t, w1, makeCm(7).OrgId())
   117  
   118  	w2 := New()
   119  	doAppend(t, w2, makeCm(2))
   120  	doAppend(t, w2, makeCm(4))
   121  	doAppend(t, w2, makeCm(6))
   122  	if !reflect.DeepEqual(w1, w2) {
   123  		w1.Debug("w1")
   124  		w2.Debug("w2")
   125  		t.Fatalf("mismatch")
   126  	}
   127  
   128  	err := w2.Append(makeCm(6))
   129  	if err == nil {
   130  		t.Fatalf("expected error")
   131  	}
   132  }
   133  
   134  func TestRemove(t *testing.T) {
   135  	w := New()
   136  	r := makeCm(1)
   137  	err := w.Remove(r.OrgId())
   138  	if err == nil {
   139  		t.Fatalf("expected error")
   140  	}
   141  	err = w.Append(r)
   142  	if err != nil {
   143  		t.Fatalf("unexpected error: %v", err)
   144  	}
   145  	err = w.Remove(r.OrgId())
   146  	if err != nil {
   147  		t.Fatalf("unexpected error: %v", err)
   148  	}
   149  	err = w.Remove(r.OrgId())
   150  	if err == nil {
   151  		t.Fatalf("expected error")
   152  	}
   153  }
   154  
   155  func TestReplace(t *testing.T) {
   156  	cm5 := makeCm(5)
   157  	cm700 := makeCm(700)
   158  	otherCm5 := makeCm(5)
   159  
   160  	w := New()
   161  	doAppend(t, w, makeCm(1))
   162  	doAppend(t, w, makeCm(2))
   163  	doAppend(t, w, makeCm(3))
   164  	doAppend(t, w, makeCm(4))
   165  	doAppend(t, w, cm5)
   166  	doAppend(t, w, makeCm(6))
   167  	doAppend(t, w, makeCm(7))
   168  
   169  	oldSize := w.Size()
   170  	_, err := w.Replace(otherCm5)
   171  	if err != nil {
   172  		t.Fatalf("unexpected error: %v", err)
   173  	}
   174  	if w.Size() != oldSize {
   175  		t.Fatalf("unexpected size %d", w.Size())
   176  	}
   177  	if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
   178  		t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
   179  	}
   180  	if err := w.Append(cm5); err == nil {
   181  		t.Fatalf("expected id already there error")
   182  	}
   183  	if err := w.Remove(cm5.OrgId()); err != nil {
   184  		t.Fatalf("unexpected err: %v", err)
   185  	}
   186  	if err := w.Append(cm700); err != nil {
   187  		t.Fatalf("unexpected err: %v", err)
   188  	}
   189  	if err := w.Append(cm5); err != nil {
   190  		t.Fatalf("unexpected err: %v", err)
   191  	}
   192  }
   193  
   194  func TestEncodeAsYaml(t *testing.T) {
   195  	encoded := []byte(`apiVersion: v1
   196  kind: ConfigMap
   197  metadata:
   198    name: cm1
   199  ---
   200  apiVersion: v1
   201  kind: ConfigMap
   202  metadata:
   203    name: cm2
   204  `)
   205  	input := resmaptest_test.NewRmBuilder(t, rf).Add(
   206  		map[string]interface{}{
   207  			"apiVersion": "v1",
   208  			"kind":       "ConfigMap",
   209  			"metadata": map[string]interface{}{
   210  				"name": "cm1",
   211  			},
   212  		}).Add(
   213  		map[string]interface{}{
   214  			"apiVersion": "v1",
   215  			"kind":       "ConfigMap",
   216  			"metadata": map[string]interface{}{
   217  				"name": "cm2",
   218  			},
   219  		}).ResMap()
   220  	out, err := input.AsYaml()
   221  	if err != nil {
   222  		t.Fatalf("unexpected error: %v", err)
   223  	}
   224  	if !reflect.DeepEqual(out, encoded) {
   225  		t.Fatalf("%s doesn't match expected %s", out, encoded)
   226  	}
   227  }
   228  
   229  func TestGetMatchingResourcesByCurrentId(t *testing.T) {
   230  	cmap := resid.NewGvk("", "v1", "ConfigMap")
   231  
   232  	r1 := rf.FromMap(
   233  		map[string]interface{}{
   234  			"apiVersion": "v1",
   235  			"kind":       "ConfigMap",
   236  			"metadata": map[string]interface{}{
   237  				"name": "alice",
   238  			},
   239  		})
   240  	r2 := rf.FromMap(
   241  		map[string]interface{}{
   242  			"apiVersion": "v1",
   243  			"kind":       "ConfigMap",
   244  			"metadata": map[string]interface{}{
   245  				"name": "bob",
   246  			},
   247  		})
   248  	r3 := rf.FromMap(
   249  		map[string]interface{}{
   250  			"apiVersion": "v1",
   251  			"kind":       "ConfigMap",
   252  			"metadata": map[string]interface{}{
   253  				"name":      "bob",
   254  				"namespace": "happy",
   255  			},
   256  		})
   257  	r4 := rf.FromMap(
   258  		map[string]interface{}{
   259  			"apiVersion": "v1",
   260  			"kind":       "ConfigMap",
   261  			"metadata": map[string]interface{}{
   262  				"name":      "charlie",
   263  				"namespace": "happy",
   264  			},
   265  		})
   266  	r5 := rf.FromMap(
   267  		map[string]interface{}{
   268  			"apiVersion": "v1",
   269  			"kind":       "Deployment",
   270  			"metadata": map[string]interface{}{
   271  				"name":      "charlie",
   272  				"namespace": "happy",
   273  			},
   274  		})
   275  
   276  	m := resmaptest_test.NewRmBuilder(t, rf).
   277  		AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
   278  
   279  	result := m.GetMatchingResourcesByCurrentId(
   280  		resid.NewResId(cmap, "alice").GvknEquals)
   281  	if len(result) != 1 {
   282  		t.Fatalf("Expected single map entry but got %v", result)
   283  	}
   284  	result = m.GetMatchingResourcesByCurrentId(
   285  		resid.NewResId(cmap, "bob").GvknEquals)
   286  	if len(result) != 2 {
   287  		t.Fatalf("Expected two, got %v", result)
   288  	}
   289  	result = m.GetMatchingResourcesByCurrentId(
   290  		resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
   291  	if len(result) != 2 {
   292  		t.Fatalf("Expected two but got %v", result)
   293  	}
   294  	result = m.GetMatchingResourcesByCurrentId(
   295  		resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
   296  	if len(result) != 1 {
   297  		t.Fatalf("Expected single map entry but got %v", result)
   298  	}
   299  	result = m.GetMatchingResourcesByCurrentId(
   300  		resid.NewResId(cmap, "charlie").GvknEquals)
   301  	if len(result) != 1 {
   302  		t.Fatalf("Expected single map entry but got %v", result)
   303  	}
   304  
   305  	//nolint:goconst
   306  	tests := []struct {
   307  		name    string
   308  		matcher IdMatcher
   309  		count   int
   310  	}{
   311  		{
   312  			"match everything",
   313  			func(resid.ResId) bool { return true },
   314  			5,
   315  		},
   316  		{
   317  			"match nothing",
   318  			func(resid.ResId) bool { return false },
   319  			0,
   320  		},
   321  		{
   322  			"name is alice",
   323  			func(x resid.ResId) bool { return x.Name == "alice" },
   324  			1,
   325  		},
   326  		{
   327  			"name is charlie",
   328  			func(x resid.ResId) bool { return x.Name == "charlie" },
   329  			2,
   330  		},
   331  		{
   332  			"name is bob",
   333  			func(x resid.ResId) bool { return x.Name == "bob" },
   334  			2,
   335  		},
   336  		{
   337  			"happy namespace",
   338  			func(x resid.ResId) bool {
   339  				return x.Namespace == "happy"
   340  			},
   341  			3,
   342  		},
   343  		{
   344  			"happy deployment",
   345  			func(x resid.ResId) bool {
   346  				return x.Namespace == "happy" &&
   347  					x.Gvk.Kind == "Deployment"
   348  			},
   349  			1,
   350  		},
   351  		{
   352  			"happy ConfigMap",
   353  			func(x resid.ResId) bool {
   354  				return x.Namespace == "happy" &&
   355  					x.Gvk.Kind == "ConfigMap"
   356  			},
   357  			2,
   358  		},
   359  	}
   360  	for _, tst := range tests {
   361  		result := m.GetMatchingResourcesByCurrentId(tst.matcher)
   362  		if len(result) != tst.count {
   363  			t.Fatalf("test '%s';  actual: %d, expected: %d",
   364  				tst.name, len(result), tst.count)
   365  		}
   366  	}
   367  }
   368  
   369  func TestGetMatchingResourcesByAnyId(t *testing.T) {
   370  	r1 := rf.FromMap(
   371  		map[string]interface{}{
   372  			"apiVersion": "v1",
   373  			"kind":       "ConfigMap",
   374  			"metadata": map[string]interface{}{
   375  				"name": "new-alice",
   376  				"annotations": map[string]interface{}{
   377  					"internal.config.kubernetes.io/previousKinds":      "ConfigMap",
   378  					"internal.config.kubernetes.io/previousNames":      "alice",
   379  					"internal.config.kubernetes.io/previousNamespaces": "default",
   380  				},
   381  			},
   382  		})
   383  	r2 := rf.FromMap(
   384  		map[string]interface{}{
   385  			"apiVersion": "v1",
   386  			"kind":       "ConfigMap",
   387  			"metadata": map[string]interface{}{
   388  				"name": "new-bob",
   389  				"annotations": map[string]interface{}{
   390  					"internal.config.kubernetes.io/previousKinds":      "ConfigMap,ConfigMap",
   391  					"internal.config.kubernetes.io/previousNames":      "bob,bob2",
   392  					"internal.config.kubernetes.io/previousNamespaces": "default,default",
   393  				},
   394  			},
   395  		})
   396  	r3 := rf.FromMap(
   397  		map[string]interface{}{
   398  			"apiVersion": "v1",
   399  			"kind":       "ConfigMap",
   400  			"metadata": map[string]interface{}{
   401  				"name":      "new-bob",
   402  				"namespace": "new-happy",
   403  				"annotations": map[string]interface{}{
   404  					"internal.config.kubernetes.io/previousKinds":      "ConfigMap",
   405  					"internal.config.kubernetes.io/previousNames":      "bob",
   406  					"internal.config.kubernetes.io/previousNamespaces": "happy",
   407  				},
   408  			},
   409  		})
   410  	r4 := rf.FromMap(
   411  		map[string]interface{}{
   412  			"apiVersion": "v1",
   413  			"kind":       "ConfigMap",
   414  			"metadata": map[string]interface{}{
   415  				"name":      "charlie",
   416  				"namespace": "happy",
   417  				"annotations": map[string]interface{}{
   418  					"internal.config.kubernetes.io/previousKinds":      "ConfigMap",
   419  					"internal.config.kubernetes.io/previousNames":      "charlie",
   420  					"internal.config.kubernetes.io/previousNamespaces": "default",
   421  				},
   422  			},
   423  		})
   424  	r5 := rf.FromMap(
   425  		map[string]interface{}{
   426  			"apiVersion": "v1",
   427  			"kind":       "Deployment",
   428  			"metadata": map[string]interface{}{
   429  				"name":      "charlie",
   430  				"namespace": "happy",
   431  			},
   432  		})
   433  
   434  	m := resmaptest_test.NewRmBuilder(t, rf).
   435  		AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
   436  
   437  	tests := []struct {
   438  		name    string
   439  		matcher IdMatcher
   440  		count   int
   441  	}{
   442  		{
   443  			"match everything",
   444  			func(resid.ResId) bool { return true },
   445  			5,
   446  		},
   447  		{
   448  			"match nothing",
   449  			func(resid.ResId) bool { return false },
   450  			0,
   451  		},
   452  		{
   453  			"name is alice",
   454  			func(x resid.ResId) bool { return x.Name == "alice" },
   455  			1,
   456  		},
   457  		{
   458  			"name is charlie",
   459  			func(x resid.ResId) bool { return x.Name == "charlie" },
   460  			2,
   461  		},
   462  		{
   463  			"name is bob",
   464  			func(x resid.ResId) bool { return x.Name == "bob" },
   465  			2,
   466  		},
   467  		{
   468  			"happy namespace",
   469  			func(x resid.ResId) bool {
   470  				return x.Namespace == "happy"
   471  			},
   472  			3,
   473  		},
   474  		{
   475  			"happy deployment",
   476  			func(x resid.ResId) bool {
   477  				return x.Namespace == "happy" &&
   478  					x.Gvk.Kind == "Deployment"
   479  			},
   480  			1,
   481  		},
   482  		{
   483  			"happy ConfigMap",
   484  			func(x resid.ResId) bool {
   485  				return x.Namespace == "happy" &&
   486  					x.Gvk.Kind == "ConfigMap"
   487  			},
   488  			2,
   489  		},
   490  	}
   491  	for _, tst := range tests {
   492  		result := m.GetMatchingResourcesByAnyId(tst.matcher)
   493  		if len(result) != tst.count {
   494  			t.Fatalf("test '%s';  actual: %d, expected: %d",
   495  				tst.name, len(result), tst.count)
   496  		}
   497  	}
   498  }
   499  
   500  func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
   501  	r1 := rf.FromMap(
   502  		map[string]interface{}{
   503  			"apiVersion": "v1",
   504  			"kind":       "ConfigMap",
   505  			"metadata": map[string]interface{}{
   506  				"name": "alice",
   507  			},
   508  		})
   509  	r2 := rf.FromMap(
   510  		map[string]interface{}{
   511  			"apiVersion": "v1",
   512  			"kind":       "ConfigMap",
   513  			"metadata": map[string]interface{}{
   514  				"name": "bob",
   515  			},
   516  		})
   517  	r3 := rf.FromMap(
   518  		map[string]interface{}{
   519  			"apiVersion": "v1",
   520  			"kind":       "ConfigMap",
   521  			"metadata": map[string]interface{}{
   522  				"name":      "bob",
   523  				"namespace": "happy",
   524  			},
   525  		})
   526  	r4 := rf.FromMap(
   527  		map[string]interface{}{
   528  			"apiVersion": "apps/v1",
   529  			"kind":       "Deployment",
   530  			"metadata": map[string]interface{}{
   531  				"name":      "charlie",
   532  				"namespace": "happy",
   533  			},
   534  		})
   535  	r5 := rf.FromMap(
   536  		map[string]interface{}{
   537  			"apiVersion": "v1",
   538  			"kind":       "ConfigMap",
   539  			"metadata": map[string]interface{}{
   540  				"name":      "charlie",
   541  				"namespace": "happy",
   542  			},
   543  		})
   544  	r5.AddNamePrefix("little-")
   545  	r6 := rf.FromMap(
   546  		map[string]interface{}{
   547  			"apiVersion": "apps/v1",
   548  			"kind":       "Deployment",
   549  			"metadata": map[string]interface{}{
   550  				"name":      "domino",
   551  				"namespace": "happy",
   552  			},
   553  		})
   554  	r6.AddNamePrefix("little-")
   555  	r7 := rf.FromMap(
   556  		map[string]interface{}{
   557  			"apiVersion": "rbac.authorization.k8s.io/v1",
   558  			"kind":       "ClusterRoleBinding",
   559  			"metadata": map[string]interface{}{
   560  				"name": "meh",
   561  			},
   562  		})
   563  
   564  	tests := map[string]struct {
   565  		filter   *resource.Resource
   566  		expected ResMap
   567  	}{
   568  		"default namespace 1": {
   569  			filter: r2,
   570  			expected: resmaptest_test.NewRmBuilder(t, rf).
   571  				AddR(r1).AddR(r2).AddR(r7).ResMap(),
   572  		},
   573  		"default namespace 2": {
   574  			filter: r1,
   575  			expected: resmaptest_test.NewRmBuilder(t, rf).
   576  				AddR(r1).AddR(r2).AddR(r7).ResMap(),
   577  		},
   578  		"happy namespace no prefix": {
   579  			filter: r3,
   580  			expected: resmaptest_test.NewRmBuilder(t, rf).
   581  				AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
   582  		},
   583  		"happy namespace with prefix": {
   584  			filter: r5,
   585  			expected: resmaptest_test.NewRmBuilder(t, rf).
   586  				AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
   587  		},
   588  		"cluster level": {
   589  			filter: r7,
   590  			expected: resmaptest_test.NewRmBuilder(t, rf).
   591  				AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
   592  		},
   593  	}
   594  	m := resmaptest_test.NewRmBuilder(t, rf).
   595  		AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
   596  	for name, test := range tests {
   597  		test := test
   598  		t.Run(name, func(t *testing.T) {
   599  			got, err1 := m.SubsetThatCouldBeReferencedByResource(test.filter)
   600  			if err1 != nil {
   601  				t.Fatalf("Expected error %v: ", err1)
   602  			}
   603  			err := test.expected.ErrorIfNotEqualLists(got)
   604  			if err != nil {
   605  				test.expected.Debug("expected")
   606  				got.Debug("actual")
   607  				t.Fatalf("Expected match")
   608  			}
   609  		})
   610  	}
   611  }
   612  
   613  func TestDeepCopy(t *testing.T) {
   614  	rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
   615  		map[string]interface{}{
   616  			"apiVersion": "v1",
   617  			"kind":       "ConfigMap",
   618  			"metadata": map[string]interface{}{
   619  				"name": "cm1",
   620  			},
   621  		}).Add(
   622  		map[string]interface{}{
   623  			"apiVersion": "v1",
   624  			"kind":       "ConfigMap",
   625  			"metadata": map[string]interface{}{
   626  				"name": "cm2",
   627  			},
   628  		}).ResMap()
   629  
   630  	rm2 := rm1.DeepCopy()
   631  
   632  	if &rm1 == &rm2 {
   633  		t.Fatal("DeepCopy returned a reference to itself instead of a copy")
   634  	}
   635  	err := rm1.ErrorIfNotEqualLists(rm1)
   636  	if err != nil {
   637  		t.Fatal(err)
   638  	}
   639  }
   640  
   641  func TestErrorIfNotEqualSets(t *testing.T) {
   642  	r1 := rf.FromMap(
   643  		map[string]interface{}{
   644  			"apiVersion": "v1",
   645  			"kind":       "ConfigMap",
   646  			"metadata": map[string]interface{}{
   647  				"name": "cm1",
   648  			},
   649  		})
   650  	r2 := rf.FromMap(
   651  		map[string]interface{}{
   652  			"apiVersion": "v1",
   653  			"kind":       "ConfigMap",
   654  			"metadata": map[string]interface{}{
   655  				"name": "cm2",
   656  			},
   657  		})
   658  	r3 := rf.FromMap(
   659  		map[string]interface{}{
   660  			"apiVersion": "v1",
   661  			"kind":       "ConfigMap",
   662  			"metadata": map[string]interface{}{
   663  				"name":      "cm2",
   664  				"namespace": "system",
   665  			},
   666  		})
   667  
   668  	m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
   669  	if err := m1.ErrorIfNotEqualSets(m1); err != nil {
   670  		t.Fatalf("object should equal itself %v", err)
   671  	}
   672  
   673  	m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
   674  	if err := m1.ErrorIfNotEqualSets(m2); err == nil {
   675  		t.Fatalf("%v should not equal %v %v", m1, m2, err)
   676  	}
   677  
   678  	m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
   679  	if err := m2.ErrorIfNotEqualSets(m3); err == nil {
   680  		t.Fatalf("%v should not equal %v %v", m2, m3, err)
   681  	}
   682  
   683  	m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
   684  		map[string]interface{}{
   685  			"apiVersion": "v1",
   686  			"kind":       "ConfigMap",
   687  			"metadata": map[string]interface{}{
   688  				"name": "cm1",
   689  			}}).ResMap()
   690  	if err := m2.ErrorIfNotEqualSets(m3); err != nil {
   691  		t.Fatalf("%v should equal %v %v", m2, m3, err)
   692  	}
   693  
   694  	m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
   695  	if err := m1.ErrorIfNotEqualSets(m4); err != nil {
   696  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   697  	}
   698  
   699  	m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
   700  	if err := m1.ErrorIfNotEqualSets(m4); err != nil {
   701  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   702  	}
   703  
   704  	m4 = m1.ShallowCopy()
   705  	if err := m1.ErrorIfNotEqualSets(m4); err != nil {
   706  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   707  	}
   708  	m4 = m1.DeepCopy()
   709  	if err := m1.ErrorIfNotEqualSets(m4); err != nil {
   710  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   711  	}
   712  }
   713  
   714  func TestErrorIfNotEqualLists(t *testing.T) {
   715  	r1 := rf.FromMap(
   716  		map[string]interface{}{
   717  			"apiVersion": "v1",
   718  			"kind":       "ConfigMap",
   719  			"metadata": map[string]interface{}{
   720  				"name": "cm1",
   721  			},
   722  		})
   723  	r2 := rf.FromMap(
   724  		map[string]interface{}{
   725  			"apiVersion": "v1",
   726  			"kind":       "ConfigMap",
   727  			"metadata": map[string]interface{}{
   728  				"name": "cm2",
   729  			},
   730  		})
   731  	r3 := rf.FromMap(
   732  		map[string]interface{}{
   733  			"apiVersion": "v1",
   734  			"kind":       "ConfigMap",
   735  			"metadata": map[string]interface{}{
   736  				"name":      "cm2",
   737  				"namespace": "system",
   738  			},
   739  		})
   740  
   741  	m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
   742  	if err := m1.ErrorIfNotEqualLists(m1); err != nil {
   743  		t.Fatalf("object should equal itself %v", err)
   744  	}
   745  
   746  	m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
   747  	if err := m1.ErrorIfNotEqualLists(m2); err == nil {
   748  		t.Fatalf("%v should not equal %v %v", m1, m2, err)
   749  	}
   750  
   751  	m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
   752  		map[string]interface{}{
   753  			"apiVersion": "v1",
   754  			"kind":       "ConfigMap",
   755  			"metadata": map[string]interface{}{
   756  				"name": "cm1",
   757  			}}).ResMap()
   758  	if err := m2.ErrorIfNotEqualLists(m3); err != nil {
   759  		t.Fatalf("%v should equal %v %v", m2, m3, err)
   760  	}
   761  
   762  	m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
   763  	if err := m1.ErrorIfNotEqualLists(m4); err != nil {
   764  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   765  	}
   766  
   767  	m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
   768  	if err := m1.ErrorIfNotEqualLists(m4); err == nil {
   769  		t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
   770  	}
   771  
   772  	m4 = m1.ShallowCopy()
   773  	if err := m1.ErrorIfNotEqualLists(m4); err != nil {
   774  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   775  	}
   776  	m4 = m1.DeepCopy()
   777  	if err := m1.ErrorIfNotEqualLists(m4); err != nil {
   778  		t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
   779  	}
   780  }
   781  
   782  func TestAppendAll(t *testing.T) {
   783  	r1 := rf.FromMap(
   784  		map[string]interface{}{
   785  			"apiVersion": "apps/v1",
   786  			"kind":       "Deployment",
   787  			"metadata": map[string]interface{}{
   788  				"name": "foo-deploy1",
   789  			},
   790  		})
   791  	input1 := rmF.FromResource(r1)
   792  	r2 := rf.FromMap(
   793  		map[string]interface{}{
   794  			"apiVersion": "apps/v1",
   795  			"kind":       "StatefulSet",
   796  			"metadata": map[string]interface{}{
   797  				"name": "bar-stateful",
   798  			},
   799  		})
   800  	input2 := rmF.FromResource(r2)
   801  
   802  	expected := New()
   803  	if err := expected.Append(r1); err != nil {
   804  		t.Fatalf("unexpected error: %v", err)
   805  	}
   806  	if err := expected.Append(r2); err != nil {
   807  		t.Fatalf("unexpected error: %v", err)
   808  	}
   809  
   810  	if err := input1.AppendAll(input2); err != nil {
   811  		t.Fatalf("unexpected error: %v", err)
   812  	}
   813  	if err := expected.ErrorIfNotEqualLists(input1); err != nil {
   814  		input1.Debug("1")
   815  		expected.Debug("ex")
   816  		t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
   817  	}
   818  	if err := input1.AppendAll(nil); err != nil {
   819  		t.Fatalf("unexpected error: %v", err)
   820  	}
   821  	if err := expected.ErrorIfNotEqualLists(input1); err != nil {
   822  		t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
   823  	}
   824  }
   825  
   826  func makeMap1(t *testing.T) ResMap {
   827  	t.Helper()
   828  	r, err := rf.FromMapAndOption(
   829  		map[string]interface{}{
   830  			"apiVersion": "apps/v1",
   831  			"kind":       "ConfigMap",
   832  			"metadata": map[string]interface{}{
   833  				"name": "cmap",
   834  			},
   835  			"data": map[string]interface{}{
   836  				"a": "x",
   837  				"b": "y",
   838  			},
   839  		}, &types.GeneratorArgs{
   840  			Behavior: "create",
   841  		})
   842  	if err != nil {
   843  		t.Fatalf("expected new intance with an options but got error: %v", err)
   844  	}
   845  	return rmF.FromResource(r)
   846  }
   847  
   848  func makeMap2(t *testing.T, b types.GenerationBehavior) ResMap {
   849  	t.Helper()
   850  	r, err := rf.FromMapAndOption(
   851  		map[string]interface{}{
   852  			"apiVersion": "apps/v1",
   853  			"kind":       "ConfigMap",
   854  			"metadata": map[string]interface{}{
   855  				"name": "cmap",
   856  			},
   857  			"data": map[string]interface{}{
   858  				"a": "u",
   859  				"b": "v",
   860  				"c": "w",
   861  			},
   862  		}, &types.GeneratorArgs{
   863  			Behavior: b.String(),
   864  		})
   865  	if err != nil {
   866  		t.Fatalf("expected new intance with an options but got error: %v", err)
   867  	}
   868  	return rmF.FromResource(r)
   869  }
   870  
   871  func TestAbsorbAll(t *testing.T) {
   872  	metadata := map[string]interface{}{
   873  		"name": "cmap",
   874  	}
   875  
   876  	r, err := rf.FromMapAndOption(
   877  		map[string]interface{}{
   878  			"apiVersion": "apps/v1",
   879  			"kind":       "ConfigMap",
   880  			"metadata":   metadata,
   881  			"data": map[string]interface{}{
   882  				"a": "u",
   883  				"b": "v",
   884  				"c": "w",
   885  			},
   886  		},
   887  		&types.GeneratorArgs{
   888  			Behavior: "create",
   889  		})
   890  	if err != nil {
   891  		t.Fatalf("expected new intance with an options but got error: %v", err)
   892  	}
   893  	expected := rmF.FromResource(r)
   894  	w := makeMap1(t)
   895  	require.NoError(t, w.AbsorbAll(makeMap2(t, types.BehaviorMerge)))
   896  	expected.RemoveBuildAnnotations()
   897  	w.RemoveBuildAnnotations()
   898  	require.NoError(t, expected.ErrorIfNotEqualLists(w))
   899  	w = makeMap1(t)
   900  	require.NoError(t, w.AbsorbAll(nil))
   901  	require.NoError(t, w.ErrorIfNotEqualLists(makeMap1(t)))
   902  
   903  	w = makeMap1(t)
   904  	w2 := makeMap2(t, types.BehaviorReplace)
   905  	require.NoError(t, w.AbsorbAll(w2))
   906  	w2.RemoveBuildAnnotations()
   907  	require.NoError(t, w2.ErrorIfNotEqualLists(w))
   908  	err = makeMap1(t).AbsorbAll(makeMap2(t, types.BehaviorUnspecified))
   909  	require.Error(t, err)
   910  	assert.True(
   911  		t, strings.Contains(err.Error(), "behavior must be merge or replace"))
   912  }
   913  
   914  func TestToRNodeSlice(t *testing.T) {
   915  	input := `apiVersion: rbac.authorization.k8s.io/v1
   916  kind: ClusterRole
   917  metadata:
   918    name: namespace-reader
   919  rules:
   920  - apiGroups:
   921    - ""
   922    resources:
   923    - namespaces
   924    verbs:
   925    - get
   926    - watch
   927    - list
   928  `
   929  	rm, err := rmF.NewResMapFromBytes([]byte(input))
   930  	if err != nil {
   931  		t.Fatalf("unexpected error: %v", err)
   932  	}
   933  	b := bytes.NewBufferString("")
   934  	for i, n := range rm.ToRNodeSlice() {
   935  		if i != 0 {
   936  			b.WriteString("---\n")
   937  		}
   938  		s, err := n.String()
   939  		if err != nil {
   940  			t.Fatalf("unexpected error: %v", err)
   941  		}
   942  		b.WriteString(s)
   943  	}
   944  
   945  	if !reflect.DeepEqual(input, b.String()) {
   946  		t.Fatalf("actual doesn't match expected.\nActual:\n%s\n===\nExpected:\n%s\n",
   947  			b.String(), input)
   948  	}
   949  }
   950  
   951  func TestDeAnchorSingleDoc(t *testing.T) {
   952  	input := `apiVersion: v1
   953  kind: ConfigMap
   954  metadata:
   955    name: wildcard
   956  data:
   957    color: &color-used blue
   958    feeling: *color-used
   959  `
   960  	rm, err := rmF.NewResMapFromBytes([]byte(input))
   961  	require.NoError(t, err)
   962  	require.NoError(t, rm.DeAnchor())
   963  	yaml, err := rm.AsYaml()
   964  	require.NoError(t, err)
   965  	assert.Equal(t, strings.TrimSpace(`
   966  apiVersion: v1
   967  data:
   968    color: blue
   969    feeling: blue
   970  kind: ConfigMap
   971  metadata:
   972    name: wildcard
   973  `), strings.TrimSpace(string(yaml)))
   974  }
   975  
   976  func TestDeAnchorIntoDoc(t *testing.T) {
   977  	input := `apiVersion: apps/v1
   978  kind: Deployment
   979  metadata:
   980    name: probes-test
   981  spec:
   982    template:
   983      spec:
   984        readinessProbe: &probe
   985          periodSeconds: 5
   986          timeoutSeconds: 3
   987          failureThreshold: 3
   988          httpGet:
   989            port: http
   990            path: /health
   991        livenessProbe:
   992          <<: *probe
   993  `
   994  	rm, err := rmF.NewResMapFromBytes([]byte(input))
   995  	require.NoError(t, err)
   996  	require.NoError(t, rm.DeAnchor())
   997  	yaml, err := rm.AsYaml()
   998  	require.NoError(t, err)
   999  	assert.Equal(t, strings.TrimSpace(`apiVersion: apps/v1
  1000  kind: Deployment
  1001  metadata:
  1002    name: probes-test
  1003  spec:
  1004    template:
  1005      spec:
  1006        livenessProbe:
  1007          failureThreshold: 3
  1008          httpGet:
  1009            path: /health
  1010            port: http
  1011          periodSeconds: 5
  1012          timeoutSeconds: 3
  1013        readinessProbe:
  1014          failureThreshold: 3
  1015          httpGet:
  1016            path: /health
  1017            port: http
  1018          periodSeconds: 5
  1019          timeoutSeconds: 3
  1020  `), strings.TrimSpace(string(yaml)))
  1021  }
  1022  
  1023  // Anchor references don't cross YAML document boundaries.
  1024  func TestDeAnchorMultiDoc(t *testing.T) {
  1025  	input := `apiVersion: v1
  1026  kind: ConfigMap
  1027  metadata:
  1028    name: betty
  1029  data:
  1030    color: &color-used blue
  1031    feeling: *color-used
  1032  ---
  1033  apiVersion: v1
  1034  kind: ConfigMap
  1035  metadata:
  1036    name: bob
  1037  data:
  1038    color: red
  1039    feeling: *color-used
  1040  `
  1041  	_, err := rmF.NewResMapFromBytes([]byte(input))
  1042  	require.Error(t, err)
  1043  	assert.Contains(t, err.Error(), "unknown anchor 'color-used' referenced")
  1044  }
  1045  
  1046  // Anchor references cross list elements in a ResourceList.
  1047  func TestDeAnchorResourceList(t *testing.T) {
  1048  	input := `apiVersion: config.kubernetes.io/v1
  1049  kind: ResourceList
  1050  metadata:
  1051    name: aShortList
  1052  items:
  1053  - apiVersion: v1
  1054    kind: ConfigMap
  1055    metadata:
  1056      name: betty
  1057    data:
  1058      color: &color-used blue
  1059      feeling: *color-used
  1060  - apiVersion: v1
  1061    kind: ConfigMap
  1062    metadata:
  1063      name: bob
  1064    data:
  1065      color: red
  1066      feeling: *color-used
  1067  `
  1068  	rm, err := rmF.NewResMapFromBytes([]byte(input))
  1069  	require.NoError(t, err)
  1070  	require.NoError(t, rm.DeAnchor())
  1071  	yaml, err := rm.AsYaml()
  1072  	require.NoError(t, err)
  1073  	assert.Equal(t, strings.TrimSpace(`
  1074  apiVersion: v1
  1075  data:
  1076    color: blue
  1077    feeling: blue
  1078  kind: ConfigMap
  1079  metadata:
  1080    name: betty
  1081  ---
  1082  apiVersion: v1
  1083  data:
  1084    color: red
  1085    feeling: blue
  1086  kind: ConfigMap
  1087  metadata:
  1088    name: bob
  1089  `), strings.TrimSpace(string(yaml)))
  1090  }
  1091  
  1092  func TestApplySmPatch_General(t *testing.T) {
  1093  	const (
  1094  		myDeployment      = "Deployment"
  1095  		myCRD             = "myCRD"
  1096  		expectedResultSMP = `apiVersion: apps/v1
  1097  kind: Deployment
  1098  metadata:
  1099    name: deploy1
  1100  spec:
  1101    template:
  1102      metadata:
  1103        labels:
  1104          old-label: old-value
  1105          some-label: some-value
  1106      spec:
  1107        containers:
  1108        - env:
  1109          - name: SOMEENV
  1110            value: SOMEVALUE
  1111          image: nginx
  1112          name: nginx
  1113  `
  1114  	)
  1115  	tests := map[string]struct {
  1116  		base          []string
  1117  		patches       []string
  1118  		expected      []string
  1119  		errorExpected bool
  1120  		errorMsg      string
  1121  	}{
  1122  		"clown": {
  1123  			base: []string{`apiVersion: v1
  1124  kind: Deployment
  1125  metadata:
  1126    name: clown
  1127  spec:
  1128    numReplicas: 1
  1129  `,
  1130  			},
  1131  			patches: []string{`apiVersion: v1
  1132  kind: Deployment
  1133  metadata:
  1134    name: clown
  1135  spec:
  1136    numReplicas: 999
  1137  `,
  1138  			},
  1139  			errorExpected: false,
  1140  			expected: []string{
  1141  				`apiVersion: v1
  1142  kind: Deployment
  1143  metadata:
  1144    name: clown
  1145  spec:
  1146    numReplicas: 999
  1147  `,
  1148  			},
  1149  		},
  1150  		"confusion": {
  1151  			base: []string{`apiVersion: example.com/v1
  1152  kind: Foo
  1153  metadata:
  1154    name: my-foo
  1155  spec:
  1156    bar:
  1157      A: X
  1158      B: Y
  1159  `,
  1160  			},
  1161  			patches: []string{`apiVersion: example.com/v1
  1162  kind: Foo
  1163  metadata:
  1164    name: my-foo
  1165  spec:
  1166    bar:
  1167      B:
  1168      C: Z
  1169  `, `apiVersion: example.com/v1
  1170  kind: Foo
  1171  metadata:
  1172    name: my-foo
  1173  spec:
  1174    bar:
  1175      C: Z
  1176      D: W
  1177    baz:
  1178      hello: world
  1179  `,
  1180  			},
  1181  			errorExpected: false,
  1182  			expected: []string{
  1183  				`apiVersion: example.com/v1
  1184  kind: Foo
  1185  metadata:
  1186    name: my-foo
  1187  spec:
  1188    bar:
  1189      A: X
  1190      C: Z
  1191      D: W
  1192    baz:
  1193      hello: world
  1194  `,
  1195  			},
  1196  		},
  1197  		"withschema-ns1-ns2-one": {
  1198  			base: []string{
  1199  				addNamespace("ns1", baseResource(myDeployment)),
  1200  				addNamespace("ns2", baseResource(myDeployment)),
  1201  			},
  1202  			patches: []string{
  1203  				addNamespace("ns1", addLabelAndEnvPatch(myDeployment)),
  1204  				addNamespace("ns2", addLabelAndEnvPatch(myDeployment)),
  1205  			},
  1206  			errorExpected: false,
  1207  			expected: []string{
  1208  				addNamespace("ns1", expectedResultSMP),
  1209  				addNamespace("ns2", expectedResultSMP),
  1210  			},
  1211  		},
  1212  		"withschema-ns1-ns2-two": {
  1213  			base: []string{
  1214  				addNamespace("ns1", baseResource(myDeployment)),
  1215  			},
  1216  			patches: []string{
  1217  				addNamespace("ns2", changeImagePatch(myDeployment)),
  1218  			},
  1219  			expected: []string{
  1220  				addNamespace("ns1", baseResource(myDeployment)),
  1221  			},
  1222  		},
  1223  		"withschema-ns1-ns2-three": {
  1224  			base: []string{
  1225  				addNamespace("ns1", baseResource(myDeployment)),
  1226  			},
  1227  			patches: []string{
  1228  				addNamespace("ns1", changeImagePatch(myDeployment)),
  1229  			},
  1230  			expected: []string{
  1231  				`apiVersion: apps/v1
  1232  kind: Deployment
  1233  metadata:
  1234    name: deploy1
  1235    namespace: ns1
  1236  spec:
  1237    template:
  1238      metadata:
  1239        labels:
  1240          old-label: old-value
  1241      spec:
  1242        containers:
  1243        - image: nginx:1.7.9
  1244          name: nginx
  1245  `,
  1246  			},
  1247  		},
  1248  		"withschema-nil-ns2": {
  1249  			base: []string{
  1250  				baseResource(myDeployment),
  1251  			},
  1252  			patches: []string{
  1253  				addNamespace("ns2", changeImagePatch(myDeployment)),
  1254  			},
  1255  			expected: []string{
  1256  				baseResource(myDeployment),
  1257  			},
  1258  		},
  1259  		"withschema-ns1-nil": {
  1260  			base: []string{
  1261  				addNamespace("ns1", baseResource(myDeployment)),
  1262  			},
  1263  			patches: []string{
  1264  				changeImagePatch(myDeployment),
  1265  			},
  1266  			expected: []string{
  1267  				addNamespace("ns1", baseResource(myDeployment)),
  1268  			},
  1269  		},
  1270  		"noschema-ns1-ns2-one": {
  1271  			base: []string{
  1272  				addNamespace("ns1", baseResource(myCRD)),
  1273  				addNamespace("ns2", baseResource(myCRD)),
  1274  			},
  1275  			patches: []string{
  1276  				addNamespace("ns1", addLabelAndEnvPatch(myCRD)),
  1277  				addNamespace("ns2", addLabelAndEnvPatch(myCRD)),
  1278  			},
  1279  			errorExpected: false,
  1280  			expected: []string{
  1281  				addNamespace("ns1", expectedResultJMP("")),
  1282  				addNamespace("ns2", expectedResultJMP("")),
  1283  			},
  1284  		},
  1285  		"noschema-ns1-ns2-two": {
  1286  			base:     []string{addNamespace("ns1", baseResource(myCRD))},
  1287  			patches:  []string{addNamespace("ns2", changeImagePatch(myCRD))},
  1288  			expected: []string{addNamespace("ns1", baseResource(myCRD))},
  1289  		},
  1290  		"noschema-nil-ns2": {
  1291  			base:     []string{baseResource(myCRD)},
  1292  			patches:  []string{addNamespace("ns2", changeImagePatch(myCRD))},
  1293  			expected: []string{baseResource(myCRD)},
  1294  		},
  1295  		"noschema-ns1-nil": {
  1296  			base:     []string{addNamespace("ns1", baseResource(myCRD))},
  1297  			patches:  []string{changeImagePatch(myCRD)},
  1298  			expected: []string{addNamespace("ns1", baseResource(myCRD))},
  1299  		},
  1300  	}
  1301  	for n := range tests {
  1302  		tc := tests[n]
  1303  		t.Run(n, func(t *testing.T) {
  1304  			m, err := rmF.NewResMapFromBytes([]byte(strings.Join(tc.base, "\n---\n")))
  1305  			require.NoError(t, err)
  1306  			foundError := false
  1307  			for _, patch := range tc.patches {
  1308  				rp, err := rf.FromBytes([]byte(patch))
  1309  				require.NoError(t, err)
  1310  				idSet := resource.MakeIdSet([]*resource.Resource{rp})
  1311  				if err = m.ApplySmPatch(idSet, rp); err != nil {
  1312  					foundError = true
  1313  					break
  1314  				}
  1315  			}
  1316  			if foundError {
  1317  				assert.True(t, tc.errorExpected)
  1318  				// compare error message?
  1319  				return
  1320  			}
  1321  			assert.False(t, tc.errorExpected)
  1322  			m.RemoveBuildAnnotations()
  1323  			yml, err := m.AsYaml()
  1324  			require.NoError(t, err)
  1325  			assert.Equal(t, strings.Join(tc.expected, "---\n"), string(yml))
  1326  		})
  1327  	}
  1328  }
  1329  
  1330  // simple utility function to add an namespace in a resource
  1331  // used as base, patch or expected result. Simply looks
  1332  // for specs: in order to add namespace: xxxx before this line
  1333  func addNamespace(namespace string, base string) string {
  1334  	res := strings.Replace(base,
  1335  		"\nspec:\n",
  1336  		"\n  namespace: "+namespace+"\nspec:\n",
  1337  		1)
  1338  	return res
  1339  }
  1340  
  1341  // DeleteOddsFilter deletes the odd entries, removing nodes.
  1342  // This is a ridiculous filter for testing.
  1343  type DeleteOddsFilter struct{}
  1344  
  1345  func (f DeleteOddsFilter) Filter(
  1346  	nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
  1347  	for i := range nodes {
  1348  		if i%2 == 0 {
  1349  			// Keep the even entries, drop the odd entries.
  1350  			result = append(result, nodes[i])
  1351  		}
  1352  	}
  1353  	return
  1354  }
  1355  
  1356  // CloneOddsFilter deletes even entries and clones odd entries,
  1357  // making new nodes.
  1358  // This is a ridiculous filter for testing.
  1359  type CloneOddsFilter struct{}
  1360  
  1361  func (f CloneOddsFilter) Filter(
  1362  	nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
  1363  	for i := range nodes {
  1364  		if i%2 != 0 {
  1365  			newNode := nodes[i].Copy()
  1366  			// Add suffix to the name, so that it's unique (w/r to this test).
  1367  			newNode.SetName(newNode.GetName() + "Clone")
  1368  			// Return a ptr to the copy.
  1369  			result = append(result, nodes[i], newNode)
  1370  		}
  1371  	}
  1372  	return
  1373  }
  1374  
  1375  func TestApplyFilter(t *testing.T) {
  1376  	tests := map[string]struct {
  1377  		input    string
  1378  		f        kio.Filter
  1379  		expected string
  1380  	}{
  1381  		"labels": {
  1382  			input: `
  1383  apiVersion: example.com/v1
  1384  kind: Beans
  1385  metadata:
  1386    name: myBeans
  1387  ---
  1388  apiVersion: example.com/v1
  1389  kind: Franks
  1390  metadata:
  1391    name: myFranks
  1392  `,
  1393  			f: labels.Filter{
  1394  				Labels: map[string]string{
  1395  					"a": "foo",
  1396  					"b": "bar",
  1397  				},
  1398  				FsSlice: types.FsSlice{
  1399  					{
  1400  						Gvk:                resid.NewGvk("example.com", "v1", "Beans"),
  1401  						Path:               "metadata/labels",
  1402  						CreateIfNotPresent: true,
  1403  					},
  1404  				},
  1405  			},
  1406  			expected: `
  1407  apiVersion: example.com/v1
  1408  kind: Beans
  1409  metadata:
  1410    labels:
  1411      a: foo
  1412      b: bar
  1413    name: myBeans
  1414  ---
  1415  apiVersion: example.com/v1
  1416  kind: Franks
  1417  metadata:
  1418    name: myFranks
  1419  `,
  1420  		},
  1421  		"deleteOddNodes": {
  1422  			input: `
  1423  apiVersion: example.com/v1
  1424  kind: Zero
  1425  metadata:
  1426    name: r0
  1427  ---
  1428  apiVersion: example.com/v1
  1429  kind: One
  1430  metadata:
  1431    name: r1
  1432  ---
  1433  apiVersion: example.com/v1
  1434  kind: Two
  1435  metadata:
  1436    name: r2
  1437  ---
  1438  apiVersion: example.com/v1
  1439  kind: Three
  1440  metadata:
  1441    name: r3
  1442  `,
  1443  			f: DeleteOddsFilter{},
  1444  			expected: `
  1445  apiVersion: example.com/v1
  1446  kind: Zero
  1447  metadata:
  1448    name: r0
  1449  ---
  1450  apiVersion: example.com/v1
  1451  kind: Two
  1452  metadata:
  1453    name: r2
  1454  `,
  1455  		},
  1456  		"cloneOddNodes": {
  1457  			// input list has five entries
  1458  			input: `
  1459  apiVersion: example.com/v1
  1460  kind: Zero
  1461  metadata:
  1462    name: r0
  1463  ---
  1464  apiVersion: example.com/v1
  1465  kind: One
  1466  metadata:
  1467    name: r1
  1468  ---
  1469  apiVersion: example.com/v1
  1470  kind: Two
  1471  metadata:
  1472    name: r2
  1473  ---
  1474  apiVersion: example.com/v1
  1475  kind: Three
  1476  metadata:
  1477    name: r3
  1478  ---
  1479  apiVersion: example.com/v1
  1480  kind: Four
  1481  metadata:
  1482    name: r4
  1483  `,
  1484  			f: CloneOddsFilter{},
  1485  			// output has four, but half are newly created nodes.
  1486  			expected: `
  1487  apiVersion: example.com/v1
  1488  kind: One
  1489  metadata:
  1490    name: r1
  1491  ---
  1492  apiVersion: example.com/v1
  1493  kind: One
  1494  metadata:
  1495    name: r1Clone
  1496  ---
  1497  apiVersion: example.com/v1
  1498  kind: Three
  1499  metadata:
  1500    name: r3
  1501  ---
  1502  apiVersion: example.com/v1
  1503  kind: Three
  1504  metadata:
  1505    name: r3Clone
  1506  `,
  1507  		},
  1508  	}
  1509  	for name := range tests {
  1510  		tc := tests[name]
  1511  		t.Run(name, func(t *testing.T) {
  1512  			m, err := rmF.NewResMapFromBytes([]byte(tc.input))
  1513  			require.NoError(t, err)
  1514  			require.NoError(t, m.ApplyFilter(tc.f))
  1515  			kusttest_test.AssertActualEqualsExpectedWithTweak(
  1516  				t, m, nil, tc.expected)
  1517  		})
  1518  	}
  1519  }
  1520  
  1521  func TestApplySmPatch_Deletion(t *testing.T) {
  1522  	target := `
  1523  apiVersion: apps/v1
  1524  metadata:
  1525    name: myDeploy
  1526  kind: Deployment
  1527  spec:
  1528    replica: 2
  1529    template:
  1530      metadata:
  1531        labels:
  1532          old-label: old-value
  1533      spec:
  1534        containers:
  1535        - name: nginx
  1536          image: nginx
  1537  `
  1538  	tests := map[string]struct {
  1539  		patch        string
  1540  		expected     string
  1541  		finalMapSize int
  1542  	}{
  1543  		"delete1": {
  1544  			patch: `apiVersion: apps/v1
  1545  metadata:
  1546    name: myDeploy
  1547  kind: Deployment
  1548  spec:
  1549    replica: 2
  1550    template:
  1551      $patch: delete
  1552      metadata:
  1553        labels:
  1554          old-label: old-value
  1555      spec:
  1556        containers:
  1557        - name: nginx
  1558          image: nginx
  1559  `,
  1560  			expected: `apiVersion: apps/v1
  1561  kind: Deployment
  1562  metadata:
  1563    name: myDeploy
  1564  spec:
  1565    replica: 2
  1566  `,
  1567  			finalMapSize: 1,
  1568  		},
  1569  		"delete2": {
  1570  			patch: `apiVersion: apps/v1
  1571  metadata:
  1572    name: myDeploy
  1573  kind: Deployment
  1574  spec:
  1575    $patch: delete
  1576    replica: 2
  1577    template:
  1578      metadata:
  1579        labels:
  1580          old-label: old-value
  1581      spec:
  1582        containers:
  1583        - name: nginx
  1584          image: nginx
  1585  `,
  1586  			expected: `apiVersion: apps/v1
  1587  kind: Deployment
  1588  metadata:
  1589    name: myDeploy
  1590  `,
  1591  			finalMapSize: 1,
  1592  		},
  1593  		"delete3": {
  1594  			patch: `apiVersion: apps/v1
  1595  metadata:
  1596    name: myDeploy
  1597  kind: Deployment
  1598  $patch: delete
  1599  `,
  1600  			expected:     "",
  1601  			finalMapSize: 0,
  1602  		},
  1603  	}
  1604  	for name := range tests {
  1605  		tc := tests[name]
  1606  		t.Run(name, func(t *testing.T) {
  1607  			m, err := rmF.NewResMapFromBytes([]byte(target))
  1608  			require.NoError(t, err, name)
  1609  			idSet := resource.MakeIdSet(m.Resources())
  1610  			assert.Equal(t, 1, idSet.Size(), name)
  1611  			p, err := rf.FromBytes([]byte(tc.patch))
  1612  			require.NoError(t, err, name)
  1613  			require.NoError(t, m.ApplySmPatch(idSet, p), name)
  1614  			assert.Equal(t, tc.finalMapSize, m.Size(), name)
  1615  			m.RemoveBuildAnnotations()
  1616  			yml, err := m.AsYaml()
  1617  			require.NoError(t, err, name)
  1618  			assert.Equal(t, tc.expected, string(yml), name)
  1619  		})
  1620  	}
  1621  }
  1622  
  1623  func TestOriginAnnotations(t *testing.T) {
  1624  	w := New()
  1625  	for i := 0; i < 3; i++ {
  1626  		require.NoError(t, w.Append(makeCm(i)))
  1627  	}
  1628  	// this should add an origin annotation to every resource
  1629  	require.NoError(t, w.AddOriginAnnotation(origin1))
  1630  	resources := w.Resources()
  1631  	for _, res := range resources {
  1632  		or, err := res.GetOrigin()
  1633  		require.NoError(t, err)
  1634  		assert.Equal(t, origin1, or)
  1635  	}
  1636  	// this should not overwrite the existing origin annotations
  1637  	require.NoError(t, w.AddOriginAnnotation(origin2))
  1638  	for _, res := range resources {
  1639  		or, err := res.GetOrigin()
  1640  		require.NoError(t, err)
  1641  		assert.Equal(t, origin1, or)
  1642  	}
  1643  	// this should remove origin annotations from all resources
  1644  	require.NoError(t, w.RemoveOriginAnnotations())
  1645  	for _, res := range resources {
  1646  		or, err := res.GetOrigin()
  1647  		require.NoError(t, err)
  1648  		assert.Nil(t, or)
  1649  	}
  1650  }
  1651  
  1652  func TestTransformerAnnotations(t *testing.T) {
  1653  	w := New()
  1654  	for i := 0; i < 3; i++ {
  1655  		require.NoError(t, w.Append(makeCm(i)))
  1656  	}
  1657  	// this should add an origin annotation to every resource
  1658  	require.NoError(t, w.AddTransformerAnnotation(origin1))
  1659  	resources := w.Resources()
  1660  	for _, res := range resources {
  1661  		or, err := res.GetOrigin()
  1662  		require.NoError(t, err)
  1663  		assert.Equal(t, origin1, or)
  1664  	}
  1665  	// this should add a transformer annotation to every resource
  1666  	require.NoError(t, w.AddTransformerAnnotation(origin2))
  1667  	for _, res := range resources {
  1668  		or, err := res.GetOrigin()
  1669  		require.NoError(t, err)
  1670  		assert.Equal(t, origin1, or)
  1671  		tr, err := res.GetTransformations()
  1672  		require.NoError(t, err)
  1673  		assert.Equal(t, resource.Transformations{origin2}, tr)
  1674  	}
  1675  	// remove transformer annotations from all resources
  1676  	require.NoError(t, w.RemoveTransformerAnnotations())
  1677  	for _, res := range resources {
  1678  		tr, err := res.GetTransformations()
  1679  		require.NoError(t, err)
  1680  		assert.Nil(t, tr)
  1681  	}
  1682  }
  1683  
  1684  // baseResource produces a base object which used to test
  1685  // patch transformation
  1686  // Also the structure is matching the Deployment syntax
  1687  // the kind can be replaced to allow testing using CRD
  1688  // without access to the schema
  1689  func baseResource(kind string) string {
  1690  	return fmt.Sprintf(`apiVersion: apps/v1
  1691  kind: %s
  1692  metadata:
  1693    name: deploy1
  1694  spec:
  1695    template:
  1696      metadata:
  1697        labels:
  1698          old-label: old-value
  1699      spec:
  1700        containers:
  1701        - image: nginx
  1702          name: nginx
  1703  `, kind)
  1704  }
  1705  
  1706  // addContainerAndEnvPatch produces a patch object which adds
  1707  // an entry in the env slice of the first/nginx container
  1708  // as well as adding a label in the metadata
  1709  // Note that for SMP/WithSchema merge, the name:nginx entry
  1710  // is mandatory
  1711  func addLabelAndEnvPatch(kind string) string {
  1712  	return fmt.Sprintf(`apiVersion: apps/v1
  1713  kind: %s
  1714  metadata:
  1715    name: deploy1
  1716  spec:
  1717    template:
  1718      metadata:
  1719        labels:
  1720          some-label: some-value
  1721      spec:
  1722        containers:
  1723         - name: nginx
  1724           env:
  1725           - name: SOMEENV
  1726             value: SOMEVALUE`, kind)
  1727  }
  1728  
  1729  // changeImagePatch produces a patch object which replaces
  1730  // the value of the image field in the first/nginx container
  1731  // Note that for SMP/WithSchema merge, the name:nginx entry
  1732  // is mandatory
  1733  func changeImagePatch(kind string) string {
  1734  	return fmt.Sprintf(`apiVersion: apps/v1
  1735  kind: %s
  1736  metadata:
  1737    name: deploy1
  1738  spec:
  1739    template:
  1740      spec:
  1741        containers:
  1742        - name: nginx
  1743          image: "nginx:1.7.9"`, kind)
  1744  }
  1745  
  1746  // utility method building the expected output of a JMP.
  1747  // imagename parameter allows to build a result consistent
  1748  // with the JMP behavior which basically overrides the
  1749  // entire "containers" list.
  1750  func expectedResultJMP(imagename string) string {
  1751  	if imagename == "" {
  1752  		return `apiVersion: apps/v1
  1753  kind: myCRD
  1754  metadata:
  1755    name: deploy1
  1756  spec:
  1757    template:
  1758      metadata:
  1759        labels:
  1760          old-label: old-value
  1761          some-label: some-value
  1762      spec:
  1763        containers:
  1764        - env:
  1765          - name: SOMEENV
  1766            value: SOMEVALUE
  1767          name: nginx
  1768  `
  1769  	}
  1770  	return fmt.Sprintf(`apiVersion: apps/v1
  1771  kind: myCRD
  1772  metadata:
  1773    name: deploy1
  1774  spec:
  1775    template:
  1776      metadata:
  1777        labels:
  1778          old-label: old-value
  1779          some-label: some-value
  1780      spec:
  1781        containers:
  1782        - image: %s
  1783          name: nginx
  1784  `, imagename)
  1785  }
  1786  

View as plain text