1
2
3
4 package manifestreader
5
6 import (
7 "fmt"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 "k8s.io/apimachinery/pkg/api/meta"
13 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
14 "k8s.io/apimachinery/pkg/runtime/schema"
15 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
16 "sigs.k8s.io/cli-utils/pkg/object"
17 "sigs.k8s.io/kustomize/kyaml/kio/filters"
18 "sigs.k8s.io/kustomize/kyaml/kio/kioutil"
19 "sigs.k8s.io/kustomize/kyaml/yaml"
20 )
21
22 func TestSetNamespaces(t *testing.T) {
23 testCases := map[string]struct {
24 objs []*unstructured.Unstructured
25 defaultNamespace string
26 enforceNamespace bool
27
28 expectedNamespaces []string
29 expectedErr error
30 }{
31 "resources already have namespace": {
32 objs: []*unstructured.Unstructured{
33 toUnstructured(schema.GroupVersionKind{
34 Group: "apps",
35 Version: "v1",
36 Kind: "Deployment",
37 }, "default"),
38 toUnstructured(schema.GroupVersionKind{
39 Group: "policy",
40 Version: "v1beta1",
41 Kind: "PodDisruptionBudget",
42 }, "default"),
43 },
44 defaultNamespace: "foo",
45 enforceNamespace: false,
46 expectedNamespaces: []string{
47 "default",
48 "default",
49 },
50 },
51 "resources without namespace and mapping in RESTMapper": {
52 objs: []*unstructured.Unstructured{
53 toUnstructured(schema.GroupVersionKind{
54 Group: "apps",
55 Version: "v1",
56 Kind: "Deployment",
57 }, ""),
58 },
59 defaultNamespace: "foo",
60 enforceNamespace: false,
61 expectedNamespaces: []string{"foo"},
62 },
63 "resource with namespace that does match enforced ns": {
64 objs: []*unstructured.Unstructured{
65 toUnstructured(schema.GroupVersionKind{
66 Group: "apps",
67 Version: "v1",
68 Kind: "Deployment",
69 }, "bar"),
70 },
71 defaultNamespace: "bar",
72 enforceNamespace: true,
73 expectedNamespaces: []string{"bar"},
74 },
75 "resource with namespace that doesn't match enforced ns": {
76 objs: []*unstructured.Unstructured{
77 toUnstructured(schema.GroupVersionKind{
78 Group: "apps",
79 Version: "v1",
80 Kind: "Deployment",
81 }, "foo"),
82 },
83 defaultNamespace: "bar",
84 enforceNamespace: true,
85 expectedErr: &NamespaceMismatchError{
86 RequiredNamespace: "bar",
87 Namespace: "foo",
88 },
89 },
90 "cluster-scoped CR with CRD": {
91 objs: []*unstructured.Unstructured{
92 toUnstructured(schema.GroupVersionKind{
93 Group: "custom.io",
94 Version: "v1",
95 Kind: "Custom",
96 }, ""),
97 toCRDUnstructured(schema.GroupVersionKind{
98 Group: "apiextensions.k8s.io",
99 Version: "v1",
100 Kind: "CustomResourceDefinition",
101 }, schema.GroupVersionKind{
102 Group: "custom.io",
103 Version: "v1",
104 Kind: "Custom",
105 }, "Cluster"),
106 },
107 defaultNamespace: "bar",
108 enforceNamespace: true,
109 expectedNamespaces: []string{"", ""},
110 },
111 "namespace-scoped CR with CRD": {
112 objs: []*unstructured.Unstructured{
113 toCRDUnstructured(schema.GroupVersionKind{
114 Group: "apiextensions.k8s.io",
115 Version: "v1",
116 Kind: "CustomResourceDefinition",
117 }, schema.GroupVersionKind{
118 Group: "custom.io",
119 Version: "v1",
120 Kind: "Custom",
121 }, "Namespaced"),
122 toUnstructured(schema.GroupVersionKind{
123 Group: "custom.io",
124 Version: "v1",
125 Kind: "Custom",
126 }, ""),
127 },
128 defaultNamespace: "bar",
129 enforceNamespace: true,
130 expectedNamespaces: []string{"", "bar"},
131 },
132 "unknown types in CRs": {
133 objs: []*unstructured.Unstructured{
134 toUnstructured(schema.GroupVersionKind{
135 Group: "custom.io",
136 Version: "v1",
137 Kind: "Custom",
138 }, ""),
139 toUnstructured(schema.GroupVersionKind{
140 Group: "custom.io",
141 Version: "v1",
142 Kind: "AnotherCustom",
143 }, ""),
144 },
145 expectedErr: &UnknownTypesError{
146 GroupVersionKinds: []schema.GroupVersionKind{
147 {
148 Group: "custom.io",
149 Version: "v1",
150 Kind: "Custom",
151 },
152 {
153 Group: "custom.io",
154 Version: "v1",
155 Kind: "AnotherCustom",
156 },
157 },
158 },
159 },
160 }
161
162 for tn, tc := range testCases {
163 t.Run(tn, func(t *testing.T) {
164 tf := cmdtesting.NewTestFactory().WithNamespace("namespace")
165 defer tf.Cleanup()
166
167 mapper, err := tf.ToRESTMapper()
168 require.NoError(t, err)
169 crdGV := schema.GroupVersion{Group: "apiextensions.k8s.io", Version: "v1"}
170 crdMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{crdGV})
171 crdMapper.AddSpecific(crdGV.WithKind("CustomResourceDefinition"),
172 crdGV.WithResource("customresourcedefinitions"),
173 crdGV.WithResource("customresourcedefinition"), meta.RESTScopeRoot)
174 mapper = meta.MultiRESTMapper([]meta.RESTMapper{mapper, crdMapper})
175
176 err = SetNamespaces(mapper, tc.objs, tc.defaultNamespace, tc.enforceNamespace)
177
178 if tc.expectedErr != nil {
179 require.Error(t, err)
180 assert.Equal(t, tc.expectedErr, err)
181 return
182 }
183
184 require.NoError(t, err)
185
186 for i, obj := range tc.objs {
187 assert.Equal(t, tc.expectedNamespaces[i], obj.GetNamespace())
188 }
189 })
190 }
191 }
192
193 var (
194 depID = object.ObjMetadata{
195 GroupKind: schema.GroupKind{
196 Group: "apps",
197 Kind: "Deployment",
198 },
199 Namespace: "default",
200 Name: "foo",
201 }
202
203 clusterRoleID = object.ObjMetadata{
204 GroupKind: schema.GroupKind{
205 Group: "rbac.authorization.k8s.io",
206 Kind: "ClusterRole",
207 },
208 Name: "bar",
209 }
210 )
211
212 func TestFilterLocalConfigs(t *testing.T) {
213 testCases := map[string]struct {
214 input []*unstructured.Unstructured
215 expected []string
216 }{
217 "don't filter if no annotation": {
218 input: []*unstructured.Unstructured{
219 objMetaToUnstructured(depID),
220 objMetaToUnstructured(clusterRoleID),
221 },
222 expected: []string{
223 depID.Name,
224 clusterRoleID.Name,
225 },
226 },
227 "filter all if all have annotation": {
228 input: []*unstructured.Unstructured{
229 addAnnotation(t, objMetaToUnstructured(depID), filters.LocalConfigAnnotation, "true"),
230 addAnnotation(t, objMetaToUnstructured(clusterRoleID), filters.LocalConfigAnnotation, "false"),
231 },
232 expected: []string{},
233 },
234 "filter even if resource have other annotations": {
235 input: []*unstructured.Unstructured{
236 addAnnotation(t,
237 addAnnotation(
238 t, objMetaToUnstructured(depID),
239 filters.LocalConfigAnnotation, "true"),
240 "my-annotation", "foo"),
241 },
242 expected: []string{},
243 },
244 }
245
246 for tn, tc := range testCases {
247 t.Run(tn, func(t *testing.T) {
248 res := FilterLocalConfig(tc.input)
249
250 var names []string
251 for _, obj := range res {
252 names = append(names, obj.GetName())
253 }
254
255
256
257 if len(tc.expected) == 0 && len(names) == 0 {
258 return
259 }
260 assert.Equal(t, tc.expected, names)
261 })
262 }
263 }
264
265 func TestRemoveAnnotations(t *testing.T) {
266 testCases := map[string]struct {
267 node *yaml.RNode
268 removeAnnotations []kioutil.AnnotationKey
269 expectedAnnotations []kioutil.AnnotationKey
270 }{
271 "filter both kioutil annotations": {
272 node: yaml.MustParse(`
273 apiVersion: apps/v1
274 kind: Deployment
275 metadata:
276 name: foo
277 annotations:
278 config.kubernetes.io/path: deployment.yaml
279 config.kubernetes.io/index: 0
280 `),
281 removeAnnotations: []kioutil.AnnotationKey{
282 kioutil.PathAnnotation,
283 kioutil.IndexAnnotation,
284 },
285 },
286 "filter only a subset of the annotations": {
287 node: yaml.MustParse(`
288 apiVersion: apps/v1
289 kind: Deployment
290 metadata:
291 name: foo
292 annotations:
293 internal.config.kubernetes.io/path: deployment.yaml
294 internal.config.kubernetes.io/index: 0
295 `),
296 removeAnnotations: []kioutil.AnnotationKey{
297 kioutil.IndexAnnotation,
298 },
299 expectedAnnotations: []kioutil.AnnotationKey{
300 kioutil.PathAnnotation,
301 },
302 },
303 "filter none of the annotations": {
304 node: yaml.MustParse(`
305 apiVersion: apps/v1
306 kind: Deployment
307 metadata:
308 name: foo
309 annotations:
310 internal.config.kubernetes.io/path: deployment.yaml
311 internal.config.kubernetes.io/index: 0
312 `),
313 removeAnnotations: []kioutil.AnnotationKey{},
314 expectedAnnotations: []kioutil.AnnotationKey{
315 kioutil.PathAnnotation,
316 kioutil.IndexAnnotation,
317 },
318 },
319 }
320
321 for tn, tc := range testCases {
322 t.Run(tn, func(t *testing.T) {
323 node := tc.node
324 err := RemoveAnnotations(node, tc.removeAnnotations...)
325 if !assert.NoError(t, err) {
326 t.FailNow()
327 }
328
329 for _, anno := range tc.removeAnnotations {
330 n, err := node.Pipe(yaml.GetAnnotation(anno))
331 if !assert.NoError(t, err) {
332 t.FailNow()
333 }
334 assert.Nil(t, n)
335 }
336 for _, anno := range tc.expectedAnnotations {
337 n, err := node.Pipe(yaml.GetAnnotation(anno))
338 if !assert.NoError(t, err) {
339 t.FailNow()
340 }
341 assert.NotNil(t, n)
342 }
343 })
344 }
345 }
346
347 func toUnstructured(gvk schema.GroupVersionKind, namespace string) *unstructured.Unstructured {
348 return &unstructured.Unstructured{
349 Object: map[string]interface{}{
350 "apiVersion": gvk.GroupVersion().String(),
351 "kind": gvk.Kind,
352 "metadata": map[string]interface{}{
353 "namespace": namespace,
354 },
355 },
356 }
357 }
358
359 func toCRDUnstructured(crdGvk schema.GroupVersionKind, crGvk schema.GroupVersionKind,
360 scope string) *unstructured.Unstructured {
361 return &unstructured.Unstructured{
362 Object: map[string]interface{}{
363 "apiVersion": crdGvk.GroupVersion().String(),
364 "kind": crdGvk.Kind,
365 "spec": map[string]interface{}{
366 "group": crGvk.Group,
367 "names": map[string]interface{}{
368 "kind": crGvk.Kind,
369 },
370 "scope": scope,
371 "versions": []interface{}{
372 map[string]interface{}{
373 "name": crGvk.Version,
374 },
375 },
376 },
377 },
378 }
379 }
380
381 func objMetaToUnstructured(id object.ObjMetadata) *unstructured.Unstructured {
382 return &unstructured.Unstructured{
383 Object: map[string]interface{}{
384 "apiVersion": fmt.Sprintf("%s/v1", id.GroupKind.Group),
385 "kind": id.GroupKind.Kind,
386 "metadata": map[string]interface{}{
387 "namespace": id.Namespace,
388 "name": id.Name,
389 },
390 },
391 }
392 }
393
394 func addAnnotation(t *testing.T, u *unstructured.Unstructured, name, val string) *unstructured.Unstructured {
395 annos, found, err := unstructured.NestedStringMap(u.Object, "metadata", "annotations")
396 if err != nil {
397 t.Fatal(err)
398 }
399 if !found {
400 annos = make(map[string]string)
401 }
402 annos[name] = val
403 err = unstructured.SetNestedStringMap(u.Object, annos, "metadata", "annotations")
404 if err != nil {
405 t.Fatal(err)
406 }
407 return u
408 }
409
View as plain text