...

Source file src/sigs.k8s.io/kustomize/api/internal/accumulator/resaccumulator_test.go

Documentation: sigs.k8s.io/kustomize/api/internal/accumulator

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package accumulator_test
     5  
     6  import (
     7  	"bytes"
     8  	"log"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/stretchr/testify/require"
    14  	. "sigs.k8s.io/kustomize/api/internal/accumulator"
    15  	"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
    16  	"sigs.k8s.io/kustomize/api/provider"
    17  	"sigs.k8s.io/kustomize/api/resmap"
    18  	"sigs.k8s.io/kustomize/api/resource"
    19  	resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
    20  	"sigs.k8s.io/kustomize/api/types"
    21  	"sigs.k8s.io/kustomize/kyaml/resid"
    22  )
    23  
    24  func makeResAccumulator(t *testing.T) *ResAccumulator {
    25  	t.Helper()
    26  	ra := MakeEmptyAccumulator()
    27  	err := ra.MergeConfig(builtinconfig.MakeDefaultConfig())
    28  	if err != nil {
    29  		t.Fatalf("unexpected err: %v", err)
    30  	}
    31  	err = ra.AppendAll(
    32  		resmaptest_test.NewRmBuilderDefault(t).
    33  			Add(map[string]interface{}{
    34  				"apiVersion": "apps/v1",
    35  				"kind":       "Deployment",
    36  				"metadata": map[string]interface{}{
    37  					"name": "deploy1",
    38  				},
    39  				"spec": map[string]interface{}{
    40  					"template": map[string]interface{}{
    41  						"spec": map[string]interface{}{
    42  							"containers": []interface{}{
    43  								map[string]interface{}{
    44  									"command": []interface{}{
    45  										"myserver",
    46  										"--somebackendService $(SERVICE_ONE)",
    47  										"--yetAnother $(SERVICE_TWO)",
    48  									},
    49  								},
    50  							},
    51  						},
    52  					},
    53  				}}).
    54  			Add(map[string]interface{}{
    55  				"apiVersion": "v1",
    56  				"kind":       "Service",
    57  				"metadata": map[string]interface{}{
    58  					"name": "backendOne",
    59  				}}).
    60  			Add(map[string]interface{}{
    61  				"apiVersion": "v1",
    62  				"kind":       "Service",
    63  				"metadata": map[string]interface{}{
    64  					"name": "backendTwo",
    65  				}}).ResMap())
    66  	if err != nil {
    67  		t.Fatalf("unexpected err: %v", err)
    68  	}
    69  	return ra
    70  }
    71  
    72  func TestResolveVarsHappy(t *testing.T) {
    73  	ra := makeResAccumulator(t)
    74  	err := ra.MergeVars([]types.Var{
    75  		{
    76  			Name: "SERVICE_ONE",
    77  			ObjRef: types.Target{
    78  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
    79  				Name: "backendOne"},
    80  		},
    81  		{
    82  			Name: "SERVICE_TWO",
    83  			ObjRef: types.Target{
    84  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
    85  				Name: "backendTwo"},
    86  		},
    87  	})
    88  	if err != nil {
    89  		t.Fatalf("unexpected err: %v", err)
    90  	}
    91  	err = ra.ResolveVars()
    92  	if err != nil {
    93  		t.Fatalf("unexpected err: %v", err)
    94  	}
    95  	c := getCommand(find("deploy1", ra.ResMap()))
    96  	if c != "myserver --somebackendService backendOne --yetAnother backendTwo" {
    97  		t.Fatalf("unexpected command: %s", c)
    98  	}
    99  }
   100  
   101  func TestResolveVarsOneUnused(t *testing.T) {
   102  	ra := makeResAccumulator(t)
   103  	err := ra.MergeVars([]types.Var{
   104  		{
   105  			Name: "SERVICE_ONE",
   106  			ObjRef: types.Target{
   107  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   108  				Name: "backendOne"},
   109  		},
   110  		{
   111  			Name: "SERVICE_UNUSED",
   112  			ObjRef: types.Target{
   113  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   114  				Name: "backendTwo"},
   115  		},
   116  	})
   117  	if err != nil {
   118  		t.Fatalf("unexpected err: %v", err)
   119  	}
   120  	var buf bytes.Buffer
   121  	log.SetOutput(&buf)
   122  	defer func() {
   123  		log.SetOutput(os.Stderr)
   124  	}()
   125  	err = ra.ResolveVars()
   126  	if err != nil {
   127  		t.Fatalf("unexpected err: %v", err)
   128  	}
   129  	expectLog(t, buf, "well-defined vars that were never replaced: SERVICE_UNUSED")
   130  	c := getCommand(find("deploy1", ra.ResMap()))
   131  	if c != "myserver --somebackendService backendOne --yetAnother $(SERVICE_TWO)" {
   132  		t.Fatalf("unexpected command: %s", c)
   133  	}
   134  }
   135  
   136  func expectLog(t *testing.T, log bytes.Buffer, expect string) {
   137  	t.Helper()
   138  	if !strings.Contains(log.String(), expect) {
   139  		t.Fatalf("expected log containing '%s', got '%s'", expect, log.String())
   140  	}
   141  }
   142  
   143  func TestResolveVarsVarNeedsDisambiguation(t *testing.T) {
   144  	ra := makeResAccumulator(t)
   145  	rm0 := resmap.New()
   146  	err := rm0.Append(
   147  		provider.NewDefaultDepProvider().GetResourceFactory().FromMap(
   148  			map[string]interface{}{
   149  				"apiVersion": "v1",
   150  				"kind":       "Service",
   151  				"metadata": map[string]interface{}{
   152  					"name":      "backendOne",
   153  					"namespace": "fooNamespace",
   154  				},
   155  			}))
   156  	if err != nil {
   157  		t.Fatalf("unexpected err: %v", err)
   158  	}
   159  	err = ra.AppendAll(rm0)
   160  	if err != nil {
   161  		t.Fatalf("unexpected err: %v", err)
   162  	}
   163  
   164  	err = ra.MergeVars([]types.Var{
   165  		{
   166  			Name: "SERVICE_ONE",
   167  			ObjRef: types.Target{
   168  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   169  				Name: "backendOne",
   170  			},
   171  		},
   172  	})
   173  	if err == nil {
   174  		t.Fatalf("expected error")
   175  	}
   176  	if !strings.Contains(
   177  		err.Error(), "unable to disambiguate") {
   178  		t.Fatalf("unexpected err: %v", err)
   179  	}
   180  }
   181  
   182  func makeNamespacedConfigMapWithDataProviderValue(
   183  	namespace string,
   184  	value string,
   185  ) map[string]interface{} {
   186  	return map[string]interface{}{
   187  		"apiVersion": "v1",
   188  		"kind":       "ConfigMap",
   189  		"metadata": map[string]interface{}{
   190  			"name":      "environment",
   191  			"namespace": namespace,
   192  		},
   193  		"data": map[string]interface{}{
   194  			"provider": value,
   195  		},
   196  	}
   197  }
   198  
   199  func makeVarToNamepaceAndPath(
   200  	name string,
   201  	namespace string,
   202  	path string,
   203  ) types.Var {
   204  	return types.Var{
   205  		Name: name,
   206  		ObjRef: types.Target{
   207  			Gvk:       resid.Gvk{Version: "v1", Kind: "ConfigMap"},
   208  			Name:      "environment",
   209  			Namespace: namespace,
   210  		},
   211  		FieldRef: types.FieldSelector{FieldPath: path},
   212  	}
   213  }
   214  
   215  func TestResolveVarConflicts(t *testing.T) {
   216  	rf := provider.NewDefaultDepProvider().GetResourceFactory()
   217  	// create configmaps in foo and bar namespaces with `data.provider` values.
   218  	fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
   219  	barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
   220  	barGcp := makeNamespacedConfigMapWithDataProviderValue("bar", "gcp")
   221  
   222  	// create two variables with (apparently) conflicting names that point to
   223  	// fieldpaths that could be generalized.
   224  	varFoo := makeVarToNamepaceAndPath("PROVIDER", "foo", "data.provider")
   225  	varBar := makeVarToNamepaceAndPath("PROVIDER", "bar", "data.provider")
   226  
   227  	// create accumulators holding apparently conflicting vars that are not
   228  	// actually in conflict because they point to the same concrete value.
   229  	rm0 := resmap.New()
   230  	err := rm0.Append(rf.FromMap(fooAws))
   231  	require.NoError(t, err)
   232  	ac0 := MakeEmptyAccumulator()
   233  	err = ac0.AppendAll(rm0)
   234  	require.NoError(t, err)
   235  	err = ac0.MergeVars([]types.Var{varFoo})
   236  	require.NoError(t, err)
   237  
   238  	rm1 := resmap.New()
   239  	err = rm1.Append(rf.FromMap(barAws))
   240  	require.NoError(t, err)
   241  	ac1 := MakeEmptyAccumulator()
   242  	err = ac1.AppendAll(rm1)
   243  	require.NoError(t, err)
   244  	err = ac1.MergeVars([]types.Var{varBar})
   245  	require.NoError(t, err)
   246  
   247  	// validate that two vars of the same name which reference the same concrete
   248  	// value do not produce a conflict.
   249  	err = ac0.MergeAccumulator(ac1)
   250  	if err == nil {
   251  		t.Fatalf("see bug gh-1600")
   252  	}
   253  
   254  	// create an accumulator will have an actually conflicting value with the
   255  	// two above (because it contains a variable whose name is used in the other
   256  	// accumulators AND whose concrete values are different).
   257  	rm2 := resmap.New()
   258  	err = rm2.Append(rf.FromMap(barGcp))
   259  	require.NoError(t, err)
   260  	ac2 := MakeEmptyAccumulator()
   261  	err = ac2.AppendAll(rm2)
   262  	require.NoError(t, err)
   263  	err = ac2.MergeVars([]types.Var{varBar})
   264  	require.NoError(t, err)
   265  	err = ac1.MergeAccumulator(ac2)
   266  	if err == nil {
   267  		t.Fatalf("dupe vars w/ different concrete values should conflict")
   268  	}
   269  }
   270  
   271  func TestResolveVarsGoodResIdBadField(t *testing.T) {
   272  	ra := makeResAccumulator(t)
   273  	err := ra.MergeVars([]types.Var{
   274  		{
   275  			Name: "SERVICE_ONE",
   276  			ObjRef: types.Target{
   277  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   278  				Name: "backendOne"},
   279  			FieldRef: types.FieldSelector{FieldPath: "nope_nope_nope"},
   280  		},
   281  	})
   282  	if err != nil {
   283  		t.Fatalf("unexpected err: %v", err)
   284  	}
   285  	err = ra.ResolveVars()
   286  	if err == nil {
   287  		t.Fatalf("expected error")
   288  	}
   289  	if !strings.Contains(
   290  		err.Error(),
   291  		"not found in corresponding resource") {
   292  		t.Fatalf("unexpected err: %v", err)
   293  	}
   294  }
   295  
   296  func TestResolveVarsUnmappableVar(t *testing.T) {
   297  	ra := makeResAccumulator(t)
   298  	err := ra.MergeVars([]types.Var{
   299  		{
   300  			Name: "SERVICE_THREE",
   301  			ObjRef: types.Target{
   302  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   303  				Name: "doesNotExist"},
   304  		},
   305  	})
   306  	if err != nil {
   307  		t.Fatalf("unexpected err: %v", err)
   308  	}
   309  	err = ra.ResolveVars()
   310  	if err == nil {
   311  		t.Fatalf("expected error")
   312  	}
   313  	if !strings.Contains(
   314  		err.Error(),
   315  		"cannot be mapped to a field in the set of known resources") {
   316  		t.Fatalf("unexpected err: %v", err)
   317  	}
   318  }
   319  
   320  func TestResolveVarsWithNoambiguation(t *testing.T) {
   321  	ra1 := makeResAccumulator(t)
   322  	err := ra1.MergeVars([]types.Var{
   323  		{
   324  			Name: "SERVICE_ONE",
   325  			ObjRef: types.Target{
   326  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   327  				Name: "backendOne",
   328  			},
   329  		},
   330  	})
   331  	if err != nil {
   332  		t.Fatalf("unexpected err: %v", err)
   333  	}
   334  
   335  	// Create another accumulator having a resource with different prefix
   336  	ra2 := MakeEmptyAccumulator()
   337  
   338  	m := resmaptest_test.NewRmBuilderDefault(t).
   339  		Add(map[string]interface{}{
   340  			"apiVersion": "apps/v1",
   341  			"kind":       "Deployment",
   342  			"metadata": map[string]interface{}{
   343  				"name": "deploy2",
   344  			},
   345  			"spec": map[string]interface{}{
   346  				"template": map[string]interface{}{
   347  					"spec": map[string]interface{}{
   348  						"containers": []interface{}{
   349  							map[string]interface{}{
   350  								"command": []interface{}{
   351  									"myserver",
   352  									"--somebackendService $(SUB_SERVICE_ONE)",
   353  								},
   354  							},
   355  						},
   356  					},
   357  				},
   358  			}}).
   359  		// Make it seem like this resource
   360  		// went through a prefix transformer.
   361  		Add(map[string]interface{}{
   362  			"apiVersion": "v1",
   363  			"kind":       "Service",
   364  			"metadata": map[string]interface{}{
   365  				"name": "sub-backendOne",
   366  				"annotations": map[string]interface{}{
   367  					"internal.config.kubernetes.io/previousKinds":      "Service",
   368  					"internal.config.kubernetes.io/previousNames":      "backendOne",
   369  					"internal.config.kubernetes.io/previousNamespaces": "default",
   370  					"internal.config.kubernetes.io/prefixes":           "sub-",
   371  				},
   372  			}}).ResMap()
   373  
   374  	err = ra2.AppendAll(m)
   375  	if err != nil {
   376  		t.Fatalf("unexpected err: %v", err)
   377  	}
   378  
   379  	err = ra2.MergeVars([]types.Var{
   380  		{
   381  			Name: "SUB_SERVICE_ONE",
   382  			ObjRef: types.Target{
   383  				Gvk:  resid.Gvk{Version: "v1", Kind: "Service"},
   384  				Name: "backendOne",
   385  			},
   386  		},
   387  	})
   388  	if err != nil {
   389  		t.Fatalf("unexpected err: %v", err)
   390  	}
   391  	err = ra1.MergeAccumulator(ra2)
   392  	if err != nil {
   393  		t.Fatalf("unexpected err: %v", err)
   394  	}
   395  
   396  	err = ra1.ResolveVars()
   397  	if err != nil {
   398  		t.Fatalf("unexpected err: %v", err)
   399  	}
   400  }
   401  
   402  func find(name string, resMap resmap.ResMap) *resource.Resource {
   403  	for _, r := range resMap.Resources() {
   404  		if r.GetName() == name {
   405  			return r
   406  		}
   407  	}
   408  	return nil
   409  }
   410  
   411  // Assumes arg is a deployment, returns the command of first container.
   412  func getCommand(r *resource.Resource) string {
   413  	var m map[string]interface{}
   414  	var c []interface{}
   415  	resourceMap, _ := r.Map()
   416  	m, _ = resourceMap["spec"].(map[string]interface{})
   417  	m, _ = m["template"].(map[string]interface{})
   418  	m, _ = m["spec"].(map[string]interface{})
   419  	c, _ = m["containers"].([]interface{})
   420  	m, _ = c[0].(map[string]interface{})
   421  
   422  	cmd, _ := m["command"].([]interface{})
   423  	n := make([]string, len(cmd))
   424  	for i, v := range cmd {
   425  		n[i] = v.(string)
   426  	}
   427  	return strings.Join(n, " ")
   428  }
   429  

View as plain text