1
2
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
218 fooAws := makeNamespacedConfigMapWithDataProviderValue("foo", "aws")
219 barAws := makeNamespacedConfigMapWithDataProviderValue("bar", "aws")
220 barGcp := makeNamespacedConfigMapWithDataProviderValue("bar", "gcp")
221
222
223
224 varFoo := makeVarToNamepaceAndPath("PROVIDER", "foo", "data.provider")
225 varBar := makeVarToNamepaceAndPath("PROVIDER", "bar", "data.provider")
226
227
228
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
248
249 err = ac0.MergeAccumulator(ac1)
250 if err == nil {
251 t.Fatalf("see bug gh-1600")
252 }
253
254
255
256
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
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
360
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
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