kubelatte-ce

Форк
2
Форк от sbertech/kubelatte-ce
/
matcher_test.go 
2411 строк · 53.6 Кб
1
package match
2

3
import (
4
	"context"
5
	"github.com/stretchr/testify/assert"
6
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
7
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
8
	"gitverse.ru/synapse/kubelatte/pkg/observability/logger/lib"
9
	"gitverse.ru/synapse/kubelatte/pkg/storage"
10
	"go.uber.org/zap"
11
	"k8s.io/api/admission/v1beta1"
12
	corev1 "k8s.io/api/core/v1"
13
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
	"testing"
15
)
16

17
func TestMatchCompatibility(t *testing.T) {
18
	type args struct {
19
		data  common.ARFields
20
		match v1alpha1.Match
21
	}
22
	tests := []struct {
23
		name string
24
		args args
25
		want bool
26
	}{
27
		{
28
			name: "full_match",
29
			args: args{
30
				data: common.ARFields{
31
					Kind: v1.GroupVersionKind{
32
						Group:   "ApiGroup",
33
						Version: "v1",
34
						Kind:    "Pod",
35
					},
36
					Namespace: "incl_ns",
37
					UserInfo:  common.ARUserInfo{Username: "kate"},
38
					Operation: v1beta1.Create,
39
					OldObject: nil,
40
					Object: map[string]interface{}{
41
						"metadata": map[string]interface{}{
42
							"name":        "some_name",
43
							"namespace":   "incl_ns",
44
							"annotations": map[string]interface{}{"annotNS/annotTr": Enabled},
45
							"labels":      map[string]interface{}{"key": "val"},
46
						},
47
					},
48
				},
49
				match: v1alpha1.Match{
50
					Kinds: []v1alpha1.Kind{
51
						{
52
							Kind:      []string{"Pod"},
53
							ApiGroups: []string{"ApiGroup"},
54
						},
55
					},
56
					UserInfo: v1alpha1.UserInfo{Username: "kate"},
57
					Name: v1alpha1.Name{
58
						Value: "some_name",
59
					},
60
					Scope: scopeNamespaced,
61
					NamespaceSelector: v1alpha1.NamespaceSelector{MatchExpressions: []v1alpha1.MatchExpression{
62
						{
63
							Key:      "matching Key",
64
							Operator: string(In),
65
							Values:   []string{"matching Val"},
66
						}, {
67
							Key:      "matching Key",
68
							Operator: string(NotIn),
69
							Values:   []string{"mismatching Val"},
70
						}, {
71

72
							Key:      "matching Key",
73
							Operator: string(Exists),
74
						}, {
75
							Key:      "Another key",
76
							Operator: string(DoesNotExist),
77
						}}},
78
					ExcludedNamespaces: v1alpha1.Namespace{
79
						Values: []string{"excl_ns"},
80
					},
81
					IncludedNamespaces: v1alpha1.Namespace{
82
						Values: []string{"incl_ns"},
83
					},
84
					Operations: []string{"CREATE"},
85
					AnnotationSelector: v1alpha1.AnnotationSelector{
86
						MatchExpressions: []v1alpha1.MatchExpression{
87
							{
88
								Key:      "annotNS/annotTr",
89
								Operator: "In",
90
								Values:   []string{Enabled},
91
							},
92
						},
93
					},
94
					LabelSelector: v1alpha1.LabelSelector{
95
						MatchExpressions: []v1alpha1.MatchExpression{{
96
							Key:      "key",
97
							Operator: string(In),
98
							Values:   []string{"val"},
99
						}, {
100
							Key:      "Key",
101
							Operator: string(NotIn),
102
							Values:   []string{"another Val"},
103
						}, {
104

105
							Key:      "key",
106
							Operator: string(Exists),
107
						}, {
108
							Key:      "Another key",
109
							Operator: string(DoesNotExist),
110
						},
111
						},
112
					},
113
				},
114
			},
115
			want: true,
116
		},
117
		{
118
			name: "not matched",
119
			args: args{
120
				data: common.ARFields{
121
					Kind: v1.GroupVersionKind{
122
						Group:   "ApiGroup",
123
						Version: "v1",
124
						Kind:    "Deployment",
125
					},
126
					Namespace: "incl_ns",
127
					UserInfo:  common.ARUserInfo{Username: "kate"},
128
					Operation: v1beta1.Create,
129
					OldObject: nil,
130
					Object: map[string]interface{}{
131
						"metadata": map[string]interface{}{
132
							"name":        "some_name",
133
							"namespace":   "incl_ns",
134
							"annotations": map[string]interface{}{"annotNS/annotTr": Enabled},
135
							"labels":      map[string]interface{}{"key": "val"},
136
						},
137
					},
138
				},
139
				match: v1alpha1.Match{
140
					Kinds: []v1alpha1.Kind{
141
						{
142
							Kind:      []string{"Pod"},
143
							ApiGroups: []string{"ApiGroup"},
144
						},
145
					},
146
					UserInfo: v1alpha1.UserInfo{Username: "kate"},
147
					Name: v1alpha1.Name{
148
						Value: "some_name",
149
					},
150
					Scope:             scopeNamespaced,
151
					NamespaceSelector: v1alpha1.NamespaceSelector{MatchExpressions: []v1alpha1.MatchExpression{}},
152
					ExcludedNamespaces: v1alpha1.Namespace{
153
						Values: []string{"excl_ns"},
154
					},
155
					IncludedNamespaces: v1alpha1.Namespace{
156
						Values: []string{"incl_ns"},
157
					},
158
					Operations: []string{"CREATE"},
159
					AnnotationSelector: v1alpha1.AnnotationSelector{
160
						MatchExpressions: []v1alpha1.MatchExpression{
161
							{
162
								Key:      "annotNS/annotTr",
163
								Operator: "In",
164
								Values:   []string{Enabled},
165
							},
166
						},
167
					},
168
					LabelSelector: v1alpha1.LabelSelector{
169
						MatchExpressions: []v1alpha1.MatchExpression{{
170
							Key:      "key",
171
							Operator: "In",
172
							Values:   []string{"val"},
173
						}},
174
					},
175
				},
176
			},
177
			want: false,
178
		},
179
		{
180
			name: "empty match section",
181
			args: args{
182
				data: common.ARFields{
183
					Namespace: "incl_ns",
184
					Operation: v1beta1.Create,
185
				},
186
				match: v1alpha1.Match{},
187
			},
188
			want: true,
189
		},
190
	}
191
	for _, tt := range tests {
192
		t.Run(tt.name, func(t *testing.T) {
193
			lib.ZapLogger = zap.NewNop()
194
			storage.Storage = &storage.StorageController{}
195
			storage.Storage.Start(false, false)
196
			storage.Storage.UpdateNamespace(corev1.Namespace{
197
				TypeMeta: v1.TypeMeta{},
198
				ObjectMeta: v1.ObjectMeta{
199
					Namespace: "incl_ns",
200
					Name:      "incl_ns",
201
					Labels:    map[string]string{"matching Key": "matching Val"},
202
				},
203
				Spec:   corev1.NamespaceSpec{},
204
				Status: corev1.NamespaceStatus{},
205
			})
206

207
			m := Matcher{}
208
			if got, _ := m.Match(context.Background(), tt.args.data, tt.args.match); got != tt.want {
209
				t.Errorf("CheckMatching() = %v, want %v", got, tt.want)
210
			}
211
		})
212
	}
213
}
214

215
func Test_doesAnnotationSelectorMatches(t *testing.T) {
216
	type args struct {
217
		fields common.ARFields
218
		match  v1alpha1.Match
219
	}
220
	tests := []struct {
221
		name        string
222
		args        args
223
		wantMatches bool
224
		wantReason  string
225
	}{
226
		{
227
			name: "match annotations",
228
			args: args{
229
				fields: common.ARFields{
230
					Operation: v1beta1.Create,
231
					Object: map[string]interface{}{
232
						"metadata": map[string]interface{}{
233
							"name":         "name-obj",
234
							"generateName": "generateName-obj",
235
							"annotations": map[string]interface{}{
236
								"kblt/key": Enabled,
237
							},
238
						},
239
					},
240
				},
241
				match: v1alpha1.Match{
242
					AnnotationSelector: v1alpha1.AnnotationSelector{
243
						MatchExpressions: []v1alpha1.MatchExpression{
244
							{
245
								Key:      "kblt/key",
246
								Operator: "In",
247
								Values:   []string{Enabled, "true"},
248
							},
249
						},
250
					},
251
				},
252
			},
253
			wantMatches: true,
254
			wantReason:  "annotation selector",
255
		},
256
		{
257
			name: "empty match section",
258
			args: args{
259
				fields: common.ARFields{
260
					Operation: v1beta1.Create,
261
					Object: map[string]interface{}{
262
						"metadata": map[string]interface{}{
263
							"name":         "name-obj",
264
							"generateName": "generateName-obj",
265
							"annotations": map[string]interface{}{
266
								"kblt/key": Enabled,
267
							},
268
						},
269
					},
270
				},
271
				match: v1alpha1.Match{
272
					AnnotationSelector: v1alpha1.AnnotationSelector{},
273
				},
274
			},
275
			wantMatches: true,
276
			wantReason:  "",
277
		},
278
		{
279
			name: "wrong annotations format",
280
			args: args{
281
				fields: common.ARFields{
282
					Operation: v1beta1.Create,
283
					Object: map[string]interface{}{
284
						"metadata": map[string]interface{}{
285
							"name":         "name-obj",
286
							"generateName": "generateName-obj",
287
							"annotations": map[bool]interface{}{
288
								true: Enabled,
289
							},
290
						},
291
					},
292
				},
293
				match: v1alpha1.Match{
294
					AnnotationSelector: v1alpha1.AnnotationSelector{
295
						MatchExpressions: []v1alpha1.MatchExpression{
296
							{
297
								Key:      "kblt/key",
298
								Operator: "In",
299
								Values:   []string{Enabled, "true"},
300
							},
301
						},
302
					},
303
				},
304
			},
305
			wantMatches: false,
306
			wantReason:  "request.metadata.annotations",
307
		},
308
	}
309
	for _, tt := range tests {
310
		t.Run(tt.name, func(t *testing.T) {
311
			lib.ZapLogger = zap.NewNop()
312
			m := Matcher{}
313
			gotMatches, gotReason := m.doesAnnotationSelectorMatches(context.Background(), tt.args.fields, tt.args.match)
314
			if gotMatches != tt.wantMatches {
315
				t.Errorf("doesAnnotationSelectorMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
316
			}
317
			if gotReason != tt.wantReason {
318
				t.Errorf("doesAnnotationSelectorMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
319
			}
320
		})
321
	}
322
}
323

324
func Test_doesIncludeExcludeNssMatch(t *testing.T) {
325
	type args struct {
326
		fields common.ARFields
327
		match  v1alpha1.Match
328
	}
329
	tests := []struct {
330
		name  string
331
		args  args
332
		want  bool
333
		want1 string
334
	}{
335
		{
336
			name: "included match",
337
			args: args{
338
				fields: common.ARFields{
339
					Operation: v1beta1.Create,
340
					Namespace: "kblt",
341
					Object: map[string]interface{}{
342
						"metadata": map[string]interface{}{},
343
					},
344
				},
345
				match: v1alpha1.Match{
346
					IncludedNamespaces: v1alpha1.Namespace{
347
						Values: []string{"kblt"},
348
					},
349
				},
350
			},
351
			want:  true,
352
			want1: "",
353
		},
354
		{
355
			name: "included not match",
356
			args: args{
357
				fields: common.ARFields{
358
					Operation: v1beta1.Create,
359
					Namespace: "kblt",
360
					Object: map[string]interface{}{
361
						"metadata": map[string]interface{}{},
362
					},
363
				},
364
				match: v1alpha1.Match{
365
					IncludedNamespaces: v1alpha1.Namespace{
366
						Values: []string{"test"},
367
					},
368
				},
369
			},
370
			want:  false,
371
			want1: "included namespace value",
372
		},
373
		{
374
			name: "included empty section match",
375
			args: args{
376
				fields: common.ARFields{
377
					Operation: v1beta1.Create,
378
					Namespace: "kblt",
379
					Object: map[string]interface{}{
380
						"metadata": map[string]interface{}{},
381
					},
382
				},
383
				match: v1alpha1.Match{
384
					IncludedNamespaces: v1alpha1.Namespace{},
385
				},
386
			},
387
			want:  true,
388
			want1: "",
389
		},
390
		{
391
			name: "included regex match",
392
			args: args{
393
				fields: common.ARFields{
394
					Operation: v1beta1.Create,
395
					Namespace: "kblt",
396
				},
397
				match: v1alpha1.Match{
398
					IncludedNamespaces: v1alpha1.Namespace{
399
						Regex: []string{"kbl.*"},
400
					},
401
				},
402
			},
403
			want:  true,
404
			want1: "",
405
		},
406
		{
407
			name: "included regex not match",
408
			args: args{
409
				fields: common.ARFields{
410
					Operation: v1beta1.Create,
411
					Namespace: "test",
412
				},
413
				match: v1alpha1.Match{
414
					IncludedNamespaces: v1alpha1.Namespace{
415
						Regex: []string{"kbl.*"},
416
					},
417
				},
418
			},
419
			want:  false,
420
			want1: "included namespace regex",
421
		},
422
		{
423
			name: "included wrong regex",
424
			args: args{
425
				fields: common.ARFields{
426
					Operation: v1beta1.Create,
427
					Namespace: "kblt",
428
				},
429
				match: v1alpha1.Match{
430
					IncludedNamespaces: v1alpha1.Namespace{
431
						Regex: []string{"a(b"},
432
					},
433
				},
434
			},
435
			want:  false,
436
			want1: "namespace invalid regex",
437
		},
438
		{
439
			name: "excluded regex not match",
440
			args: args{
441
				fields: common.ARFields{
442
					Operation: v1beta1.Create,
443
					Namespace: "kblt",
444
				},
445
				match: v1alpha1.Match{
446
					ExcludedNamespaces: v1alpha1.Namespace{
447
						Regex: []string{"kbl.*"},
448
					},
449
				},
450
			},
451
			want:  false,
452
			want1: "excluded namespace regex",
453
		},
454
		{
455
			name: "empty namespace admission review",
456
			args: args{
457
				fields: common.ARFields{
458
					Operation: v1beta1.Create,
459
				},
460
				match: v1alpha1.Match{
461
					ExcludedNamespaces: v1alpha1.Namespace{
462
						Regex: []string{"kbl.*"},
463
					},
464
				},
465
			},
466
			want:  true,
467
			want1: "",
468
		},
469
	}
470
	for _, tt := range tests {
471
		t.Run(tt.name, func(t *testing.T) {
472
			lib.ZapLogger = zap.NewNop()
473
			m := Matcher{}
474
			got, got1 := m.doesIncludeExcludeNssMatch(context.Background(), tt.args.fields, tt.args.match)
475
			if got != tt.want {
476
				t.Errorf("doesIncludeExcludeNssMatch() got = %v, want %v", got, tt.want)
477
			}
478
			if got1 != tt.want1 {
479
				t.Errorf("doesIncludeExcludeNssMatch() got1 = %v, want %v", got1, tt.want1)
480
			}
481
		})
482
	}
483
}
484

485
func Test_doesKindsMatches(t *testing.T) {
486
	type args struct {
487
		fields common.ARFields
488
		match  v1alpha1.Match
489
	}
490
	tests := []struct {
491
		name        string
492
		args        args
493
		wantMatches bool
494
		wantReason  string
495
	}{
496
		{
497
			name: "empty match kind",
498
			args: args{
499
				fields: common.ARFields{
500
					Kind: v1.GroupVersionKind{
501
						Group:   "",
502
						Version: "v1",
503
						Kind:    "Pod",
504
					},
505
					Operation: v1beta1.Create,
506
				},
507
				match: v1alpha1.Match{
508
					Kinds: []v1alpha1.Kind{},
509
				},
510
			},
511
			wantMatches: false,
512
			wantReason:  "request.kind.group",
513
		},
514
		{
515
			name: "match kind and version",
516
			args: args{
517
				fields: common.ARFields{
518
					Kind: v1.GroupVersionKind{
519
						Group:   "",
520
						Version: "v1",
521
						Kind:    "Pod",
522
					},
523
					Operation: v1beta1.Create,
524
				},
525
				match: v1alpha1.Match{
526
					Kinds: []v1alpha1.Kind{
527
						{
528
							Kind:      []string{"Pod"},
529
							ApiGroups: []string{""},
530
						},
531
					},
532
				},
533
			},
534
			wantMatches: true,
535
			wantReason:  "",
536
		},
537
		{
538
			name: "match kind and not match version",
539
			args: args{
540
				fields: common.ARFields{
541
					Kind: v1.GroupVersionKind{
542
						Group:   "2",
543
						Version: "v1",
544
						Kind:    "Pod",
545
					},
546
					Operation: v1beta1.Create,
547
				},
548
				match: v1alpha1.Match{
549
					Kinds: []v1alpha1.Kind{
550
						{
551
							Kind:      []string{"Pod"},
552
							ApiGroups: []string{"3"},
553
						},
554
					},
555
				},
556
			},
557
			wantMatches: false,
558
			wantReason:  "request.kind.group",
559
		},
560
		{
561
			name: "match version and not match kind",
562
			args: args{
563
				fields: common.ARFields{
564
					Kind: v1.GroupVersionKind{
565
						Group:   "3",
566
						Version: "v1",
567
						Kind:    "Deployment",
568
					},
569
					Operation: v1beta1.Create,
570
				},
571
				match: v1alpha1.Match{
572
					Kinds: []v1alpha1.Kind{
573
						{
574
							Kind:      []string{"Pod"},
575
							ApiGroups: []string{"3"},
576
						},
577
					},
578
				},
579
			},
580
			wantMatches: false,
581
			wantReason:  "request.kind.kind",
582
		},
583
	}
584
	for _, tt := range tests {
585
		t.Run(tt.name, func(t *testing.T) {
586
			m := Matcher{}
587
			gotMatches, gotReason := m.doesKindsMatches(context.Background(), tt.args.fields, tt.args.match)
588
			if gotMatches != tt.wantMatches {
589
				t.Errorf("doesKindsMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
590
			}
591
			if gotReason != tt.wantReason {
592
				t.Errorf("doesKindsMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
593
			}
594
		})
595
	}
596
}
597

598
func Test_doesLabelSelectorMatches(t *testing.T) {
599
	type args struct {
600
		fields common.ARFields
601
		match  v1alpha1.Match
602
	}
603
	tests := []struct {
604
		name        string
605
		args        args
606
		wantMatches bool
607
		wantReason  string
608
	}{
609
		{
610
			name: "empty match.labelSelector",
611
			args: args{
612
				fields: common.ARFields{
613
					Operation: v1beta1.Create,
614
					Object: map[string]interface{}{
615
						"metadata": map[string]interface{}{
616
							"name":         "name-obj",
617
							"generateName": "generateName-obj",
618
						},
619
					},
620
				},
621
				match: v1alpha1.Match{
622
					LabelSelector: v1alpha1.LabelSelector{},
623
				},
624
			},
625
			wantMatches: true,
626
			wantReason:  "",
627
		},
628
		{
629
			name: "wrong labels format",
630
			args: args{
631
				fields: common.ARFields{
632
					Operation: v1beta1.Create,
633
					Object: map[string]interface{}{
634
						"metadata": map[string]interface{}{
635
							"name":         "name-obj",
636
							"generateName": "generateName-obj",
637
							"labels": map[bool]interface{}{
638
								true: map[string]interface{}{
639
									"test": map[string]interface{}{
640
										"test": "test",
641
									},
642
								},
643
							},
644
						},
645
					},
646
				},
647
				match: v1alpha1.Match{
648
					LabelSelector: v1alpha1.LabelSelector{
649
						MatchExpressions: []v1alpha1.MatchExpression{
650
							{
651
								Key:      "key",
652
								Operator: "In",
653
								Values:   []string{"value"},
654
							},
655
						},
656
					},
657
				},
658
			},
659
			wantMatches: false,
660
			wantReason:  "wrong metadata.labels format",
661
		},
662
		{
663
			name: "match labels",
664
			args: args{
665
				fields: common.ARFields{
666
					Operation: v1beta1.Create,
667
					Object: map[string]interface{}{
668
						"metadata": map[string]interface{}{
669
							"name":         "name-obj",
670
							"generateName": "generateName-obj",
671
							"labels": map[string]interface{}{
672
								"key": "value",
673
							},
674
						},
675
					},
676
				},
677
				match: v1alpha1.Match{
678
					LabelSelector: v1alpha1.LabelSelector{
679
						MatchExpressions: []v1alpha1.MatchExpression{
680
							{
681
								Key:      "key",
682
								Operator: "In",
683
								Values:   []string{"value", "not-value"},
684
							},
685
						},
686
					},
687
				},
688
			},
689
			wantMatches: true,
690
			wantReason:  "metadata.labels",
691
		},
692
		{
693
			name: "not matched labels",
694
			args: args{
695
				fields: common.ARFields{
696
					Operation: v1beta1.Create,
697
					Object: map[string]interface{}{
698
						"metadata": map[string]interface{}{
699
							"name":         "name-obj",
700
							"generateName": "generateName-obj",
701
							"labels": map[string]interface{}{
702
								"key": "value",
703
							},
704
						},
705
					},
706
				},
707
				match: v1alpha1.Match{
708
					LabelSelector: v1alpha1.LabelSelector{
709
						MatchExpressions: []v1alpha1.MatchExpression{
710
							{
711
								Key:      "key",
712
								Operator: "NotIn",
713
								Values:   []string{"value", "not-value"},
714
							},
715
						},
716
					},
717
				},
718
			},
719
			wantMatches: false,
720
			wantReason:  "metadata.labels",
721
		},
722
	}
723
	for _, tt := range tests {
724
		t.Run(tt.name, func(t *testing.T) {
725
			lib.ZapLogger = zap.NewNop()
726
			m := Matcher{}
727
			gotMatches, gotReason := m.doesLabelSelectorMatches(context.Background(), tt.args.fields, tt.args.match)
728
			if gotMatches != tt.wantMatches {
729
				t.Errorf("doesLabelSelectorMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
730
			}
731
			if gotReason != tt.wantReason {
732
				t.Errorf("doesLabelSelectorMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
733
			}
734
		})
735
	}
736
}
737

738
func Test_doesMatchConditionsMatches(t *testing.T) {
739
	type args struct {
740
		fields common.ARFields
741
		match  v1alpha1.Match
742
	}
743
	tests := []struct {
744
		name  string
745
		args  args
746
		want  bool
747
		want1 string
748
	}{
749
		// Create
750
		{
751
			name: "empty match section",
752
			args: args{
753
				fields: common.ARFields{
754
					Operation: v1beta1.Create,
755
					Object: map[string]interface{}{
756
						"metadata": map[string]interface{}{
757
							"name":         "name-obj",
758
							"generateName": "generateName-obj",
759
						},
760
					},
761
				},
762
				match: v1alpha1.Match{
763
					MatchConditions: v1alpha1.MatchConditions{},
764
				},
765
			},
766
			want:  true,
767
			want1: "",
768
		},
769
		{
770
			name: "object mus be matched mustExist",
771
			args: args{
772
				fields: common.ARFields{
773
					Operation: v1beta1.Create,
774
					Object: map[string]interface{}{
775
						"metadata": map[string]interface{}{
776
							"name":         "name-obj",
777
							"generateName": "generateName-obj",
778
						},
779
					},
780
				},
781
				match: v1alpha1.Match{
782
					MatchConditions: v1alpha1.MatchConditions{
783
						Object: []v1alpha1.Obj{{
784
							Path:      "metadata.name",
785
							Condition: mustExist,
786
						}},
787
					},
788
				},
789
			},
790
			want:  true,
791
			want1: "",
792
		},
793
		{
794
			name: "object mus be matched mustNotExist",
795
			args: args{
796
				fields: common.ARFields{
797
					Operation: v1beta1.Create,
798
					Object: map[string]interface{}{
799
						"metadata": map[string]interface{}{
800
							"name":         "name-obj",
801
							"generateName": "generateName-obj",
802
						},
803
					},
804
				},
805
				match: v1alpha1.Match{
806
					MatchConditions: v1alpha1.MatchConditions{
807
						Object: []v1alpha1.Obj{{
808
							Path:      "metadata.annotations",
809
							Condition: mustNotExist,
810
						}},
811
					},
812
				},
813
			},
814
			want:  true,
815
			want1: "",
816
		},
817
		{
818
			name: "object mus be not matched mustNotExist",
819
			args: args{
820
				fields: common.ARFields{
821
					Operation: v1beta1.Create,
822
					Object: map[string]interface{}{
823
						"metadata": map[string]interface{}{
824
							"name":         "name-obj",
825
							"generateName": "generateName-obj",
826
						},
827
					},
828
				},
829
				match: v1alpha1.Match{
830
					MatchConditions: v1alpha1.MatchConditions{
831
						Object: []v1alpha1.Obj{{
832
							Path:      "metadata.annotations",
833
							Condition: mustExist,
834
						}},
835
					},
836
				},
837
			},
838
			want:  false,
839
			want1: "match conditions",
840
		},
841
		{
842
			name: "object mus be not matched mustExist",
843
			args: args{
844
				fields: common.ARFields{
845
					Operation: v1beta1.Create,
846
					Object: map[string]interface{}{
847
						"metadata": map[string]interface{}{
848
							"name":         "name-obj",
849
							"generateName": "generateName-obj",
850
						},
851
					},
852
				},
853
				match: v1alpha1.Match{
854
					MatchConditions: v1alpha1.MatchConditions{
855
						Object: []v1alpha1.Obj{{
856
							Path:      "metadata.name",
857
							Condition: mustNotExist,
858
						}},
859
					},
860
				},
861
			},
862
			want:  false,
863
			want1: "match conditions",
864
		},
865
		// Delete
866
		{
867
			name: "OldObject mus be matched mustNotExist",
868
			args: args{
869
				fields: common.ARFields{
870
					Operation: v1beta1.Delete,
871
					OldObject: map[string]interface{}{
872
						"metadata": map[string]interface{}{
873
							"name":         "name-obj",
874
							"generateName": "generateName-obj",
875
						},
876
					},
877
				},
878
				match: v1alpha1.Match{
879
					MatchConditions: v1alpha1.MatchConditions{
880
						OldObject: []v1alpha1.Obj{{
881
							Path:      "metadata.annotations",
882
							Condition: mustNotExist,
883
						}},
884
					},
885
				},
886
			},
887
			want:  true,
888
			want1: "",
889
		},
890
		{
891
			name: "OldObject mus be matched mustExist",
892
			args: args{
893
				fields: common.ARFields{
894
					Operation: v1beta1.Delete,
895
					OldObject: map[string]interface{}{
896
						"metadata": map[string]interface{}{
897
							"name":         "name-obj",
898
							"generateName": "generateName-obj",
899
						},
900
					},
901
				},
902
				match: v1alpha1.Match{
903
					MatchConditions: v1alpha1.MatchConditions{
904
						OldObject: []v1alpha1.Obj{{
905
							Path:      "metadata.name",
906
							Condition: mustExist,
907
						}},
908
					},
909
				},
910
			},
911
			want:  true,
912
			want1: "",
913
		},
914
		{
915
			name: "OldObject mus be not matched mustNotExist",
916
			args: args{
917
				fields: common.ARFields{
918
					Operation: v1beta1.Delete,
919
					OldObject: map[string]interface{}{
920
						"metadata": map[string]interface{}{
921
							"name":         "name-obj",
922
							"generateName": "generateName-obj",
923
						},
924
					},
925
				},
926
				match: v1alpha1.Match{
927
					MatchConditions: v1alpha1.MatchConditions{
928
						OldObject: []v1alpha1.Obj{{
929
							Path:      "metadata.name",
930
							Condition: mustNotExist,
931
						}},
932
					},
933
				},
934
			},
935
			want:  false,
936
			want1: "match conditions",
937
		},
938
		{
939
			name: "OldObject mus be not matched mustExist",
940
			args: args{
941
				fields: common.ARFields{
942
					Operation: v1beta1.Delete,
943
					OldObject: map[string]interface{}{
944
						"metadata": map[string]interface{}{
945
							"name":         "name-obj",
946
							"generateName": "generateName-obj",
947
						},
948
					},
949
				},
950
				match: v1alpha1.Match{
951
					MatchConditions: v1alpha1.MatchConditions{
952
						OldObject: []v1alpha1.Obj{{
953
							Path:      "metadata.annotations",
954
							Condition: mustExist,
955
						}},
956
					},
957
				},
958
			},
959
			want:  false,
960
			want1: "match conditions",
961
		},
962
		// Update
963
		{
964
			name: "both must be matched mustExist",
965
			args: args{
966
				fields: common.ARFields{
967
					Operation: v1beta1.Update,
968
					OldObject: map[string]interface{}{
969
						"metadata": map[string]interface{}{
970
							"name":         "name-obj",
971
							"generateName": "generateName-obj",
972
						},
973
					},
974
					Object: map[string]interface{}{
975
						"metadata": map[string]interface{}{
976
							"name":         "name-obj",
977
							"generateName": "generateName-obj",
978
						},
979
					},
980
				},
981
				match: v1alpha1.Match{
982
					MatchConditions: v1alpha1.MatchConditions{
983
						OldObject: []v1alpha1.Obj{{
984
							Path:      "metadata.name",
985
							Condition: mustExist,
986
						}},
987
						Object: []v1alpha1.Obj{{
988
							Path:      "metadata.name",
989
							Condition: mustExist,
990
						}},
991
					},
992
				},
993
			},
994
			want:  true,
995
			want1: "",
996
		},
997
		{
998
			name: "both: Object must be not matched mustExist",
999
			args: args{
1000
				fields: common.ARFields{
1001
					Operation: v1beta1.Update,
1002
					OldObject: map[string]interface{}{
1003
						"metadata": map[string]interface{}{
1004
							"name":         "name-obj",
1005
							"generateName": "generateName-obj",
1006
						},
1007
					},
1008
					Object: map[string]interface{}{
1009
						"metadata": map[string]interface{}{
1010
							"name":         "name-obj",
1011
							"generateName": "generateName-obj",
1012
						},
1013
					},
1014
				},
1015
				match: v1alpha1.Match{
1016
					MatchConditions: v1alpha1.MatchConditions{
1017
						OldObject: []v1alpha1.Obj{{
1018
							Path:      "metadata.name",
1019
							Condition: mustExist,
1020
						}},
1021
						Object: []v1alpha1.Obj{{
1022
							Path:      "metadata.annotations",
1023
							Condition: mustExist,
1024
						}},
1025
					},
1026
				},
1027
			},
1028
			want:  false,
1029
			want1: "match conditions",
1030
		},
1031
		{
1032
			name: "both: OldObject must be not matched mustExist",
1033
			args: args{
1034
				fields: common.ARFields{
1035
					Operation: v1beta1.Update,
1036
					OldObject: map[string]interface{}{
1037
						"metadata": map[string]interface{}{
1038
							"name":         "name-obj",
1039
							"generateName": "generateName-obj",
1040
						},
1041
					},
1042
					Object: map[string]interface{}{
1043
						"metadata": map[string]interface{}{
1044
							"name":         "name-obj",
1045
							"generateName": "generateName-obj",
1046
						},
1047
					},
1048
				},
1049
				match: v1alpha1.Match{
1050
					MatchConditions: v1alpha1.MatchConditions{
1051
						OldObject: []v1alpha1.Obj{{
1052
							Path:      "metadata.annotations",
1053
							Condition: mustExist,
1054
						}},
1055
						Object: []v1alpha1.Obj{{
1056
							Path:      "metadata.name",
1057
							Condition: mustExist,
1058
						}},
1059
					},
1060
				},
1061
			},
1062
			want:  false,
1063
			want1: "match conditions",
1064
		},
1065
	}
1066
	for _, tt := range tests {
1067
		t.Run(tt.name, func(t *testing.T) {
1068
			m := Matcher{}
1069
			got, got1 := m.doesMatchConditionsMatches(context.Background(), tt.args.fields, tt.args.match)
1070
			if got != tt.want {
1071
				t.Errorf("doesMatchConditionsMatches() got = %v, want %v", got, tt.want)
1072
			}
1073
			if got1 != tt.want1 {
1074
				t.Errorf("doesMatchConditionsMatches() got1 = %v, want %v", got1, tt.want1)
1075
			}
1076
		})
1077
	}
1078
}
1079

1080
func Test_doesNameMatches(t *testing.T) {
1081
	type args struct {
1082
		fields common.ARFields
1083
		match  v1alpha1.Match
1084
	}
1085
	tests := []struct {
1086
		name        string
1087
		args        args
1088
		wantMatches bool
1089
		wantReason  string
1090
	}{
1091
		{
1092
			name: "without metadata.name",
1093
			args: args{
1094
				fields: common.ARFields{
1095
					Operation: v1beta1.Create,
1096
					Object: map[string]interface{}{
1097
						"metadata": map[string]interface{}{
1098
							"namespace":   "incl_ns",
1099
							"annotations": map[string]interface{}{"annotNS/annotTr": Enabled},
1100
							"labels":      map[string]interface{}{"key": "val"},
1101
						},
1102
					},
1103
				},
1104
				match: v1alpha1.Match{
1105
					Name: v1alpha1.Name{
1106
						Value: "some_name",
1107
					},
1108
				},
1109
			},
1110
			wantMatches: false,
1111
			wantReason:  "request.oldObject.metadata.name",
1112
		},
1113
		{
1114
			name: "empty match name",
1115
			args: args{
1116
				fields: common.ARFields{
1117
					Operation: v1beta1.Create,
1118
					Object: map[string]interface{}{
1119
						"metadata": map[string]interface{}{
1120
							"namespace": "incl_ns",
1121
							"name":      "incl_ns",
1122
						},
1123
					},
1124
				},
1125
				match: v1alpha1.Match{
1126
					Name: v1alpha1.Name{
1127
						Value: "",
1128
						Regex: "",
1129
					},
1130
				},
1131
			},
1132
			wantMatches: true,
1133
			wantReason:  "",
1134
		},
1135
		{
1136
			name: "match name value",
1137
			args: args{
1138
				fields: common.ARFields{
1139
					Operation: v1beta1.Create,
1140
					Object: map[string]interface{}{
1141
						"metadata": map[string]interface{}{
1142
							"name": "test-nane-value",
1143
						},
1144
					},
1145
				},
1146
				match: v1alpha1.Match{
1147
					Name: v1alpha1.Name{
1148
						Value: "test-nane-value",
1149
					},
1150
				},
1151
			},
1152
			wantMatches: true,
1153
			wantReason:  "",
1154
		},
1155
		{
1156
			name: "match name regex",
1157
			args: args{
1158
				fields: common.ARFields{
1159
					Operation: v1beta1.Create,
1160
					Object: map[string]interface{}{
1161
						"metadata": map[string]interface{}{
1162
							"name": "test-name-value",
1163
						},
1164
					},
1165
				},
1166
				match: v1alpha1.Match{
1167
					Name: v1alpha1.Name{
1168
						Regex: ".*-name-.*",
1169
					},
1170
				},
1171
			},
1172
			wantMatches: true,
1173
			wantReason:  "request.oldObject.metadata.name",
1174
		},
1175
		{
1176
			name: "not matched name regex",
1177
			args: args{
1178
				fields: common.ARFields{
1179
					Operation: v1beta1.Create,
1180
					Object: map[string]interface{}{
1181
						"metadata": map[string]interface{}{
1182
							"name": "test-name-value",
1183
						},
1184
					},
1185
				},
1186
				match: v1alpha1.Match{
1187
					Name: v1alpha1.Name{
1188
						Regex: ".*-nane-.*",
1189
					},
1190
				},
1191
			},
1192
			wantMatches: false,
1193
			wantReason:  "request.oldObject.metadata.name",
1194
		},
1195
		{
1196
			name: "wrong name regex",
1197
			args: args{
1198
				fields: common.ARFields{
1199
					Operation: v1beta1.Create,
1200
					Object: map[string]interface{}{
1201
						"metadata": map[string]interface{}{
1202
							"name": "test-name-value",
1203
						},
1204
					},
1205
				},
1206
				match: v1alpha1.Match{
1207
					Name: v1alpha1.Name{
1208
						Regex: `a(b`,
1209
					},
1210
				},
1211
			},
1212
			wantMatches: false,
1213
			wantReason:  "wrong match.name.regex: error parsing regexp: missing closing ): `a(b`",
1214
		},
1215
	}
1216
	for _, tt := range tests {
1217
		t.Run(tt.name, func(t *testing.T) {
1218
			m := Matcher{}
1219
			gotMatches, gotReason := m.doesNameMatches(context.Background(), tt.args.fields, tt.args.match)
1220
			assert.Equalf(t, tt.wantMatches, gotMatches, "doesNameMatches(%v, %v)", tt.args.fields, tt.args.match)
1221
			assert.Equalf(t, tt.wantReason, gotReason, "doesNameMatches(%v, %v)", tt.args.fields, tt.args.match)
1222
		})
1223
	}
1224
}
1225

1226
func Test_doesNamespaceSelectorMatches(t *testing.T) {
1227
	type args struct {
1228
		fields common.ARFields
1229
		match  v1alpha1.Match
1230
	}
1231
	tests := []struct {
1232
		name  string
1233
		args  args
1234
		want  bool
1235
		want1 string
1236
	}{
1237
		{
1238
			name: "empty match section",
1239
			args: args{
1240
				fields: common.ARFields{
1241
					Operation: v1beta1.Update,
1242
					OldObject: map[string]interface{}{
1243
						"metadata": map[string]interface{}{
1244
							"name":         "name-obj",
1245
							"generateName": "generateName-obj",
1246
						},
1247
					},
1248
					Object: map[string]interface{}{
1249
						"metadata": map[string]interface{}{
1250
							"name":         "name-obj",
1251
							"generateName": "generateName-obj",
1252
						},
1253
					},
1254
				},
1255
				match: v1alpha1.Match{
1256
					NamespaceSelector: v1alpha1.NamespaceSelector{},
1257
				},
1258
			},
1259
			want:  true,
1260
			want1: "namespace selector",
1261
		},
1262
		{
1263
			name: "cluster objet",
1264
			args: args{
1265
				fields: common.ARFields{
1266
					Namespace: "",
1267
					Operation: v1beta1.Create,
1268
					Object: map[string]interface{}{
1269
						"metadata": map[string]interface{}{
1270
							"name":         "name-obj",
1271
							"generateName": "generateName-obj",
1272
						},
1273
					},
1274
				},
1275
				match: v1alpha1.Match{
1276
					NamespaceSelector: v1alpha1.NamespaceSelector{
1277
						MatchExpressions: []v1alpha1.MatchExpression{
1278
							{
1279
								Key:      "matchingKey",
1280
								Operator: "In",
1281
								Values:   []string{"matchingVal"},
1282
							},
1283
						},
1284
					},
1285
				},
1286
			},
1287
			want:  true,
1288
			want1: "namespace selector",
1289
		},
1290
		{
1291
			name: "match namespace",
1292
			args: args{
1293
				fields: common.ARFields{
1294
					Namespace: "ns-test",
1295
					Operation: v1beta1.Create,
1296
					Object: map[string]interface{}{
1297
						"metadata": map[string]interface{}{
1298
							"name":         "name-obj",
1299
							"generateName": "generateName-obj",
1300
						},
1301
					},
1302
				},
1303
				match: v1alpha1.Match{
1304
					NamespaceSelector: v1alpha1.NamespaceSelector{
1305
						MatchExpressions: []v1alpha1.MatchExpression{
1306
							{
1307
								Key:      "different-ns",
1308
								Operator: "In",
1309
								Values:   []string{"matchingVal"},
1310
							},
1311
						},
1312
					},
1313
				},
1314
			},
1315
			want:  false,
1316
			want1: "namespace selector",
1317
		},
1318
	}
1319
	for _, tt := range tests {
1320
		t.Run(tt.name, func(t *testing.T) {
1321
			lib.ZapLogger = zap.NewNop()
1322
			storage.Storage = &storage.StorageController{}
1323
			storage.Storage.Start(false, false)
1324
			storage.Storage.UpdateNamespace(corev1.Namespace{
1325
				TypeMeta: v1.TypeMeta{},
1326
				ObjectMeta: v1.ObjectMeta{
1327
					Namespace: "incl_ns",
1328
					Name:      "incl_ns",
1329
					Labels:    map[string]string{"matchingKey": "matchingVal"},
1330
				},
1331
				Spec:   corev1.NamespaceSpec{},
1332
				Status: corev1.NamespaceStatus{},
1333
			})
1334
			m := Matcher{}
1335
			got, got1 := m.doesNamespaceSelectorMatches(context.Background(), tt.args.fields, tt.args.match)
1336
			if got != tt.want {
1337
				t.Errorf("doesNamespaceSelectorMatches() got = %v, want %v", got, tt.want)
1338
			}
1339
			if got1 != tt.want1 {
1340
				t.Errorf("doesNamespaceSelectorMatches() got1 = %v, want %v", got1, tt.want1)
1341
			}
1342
		})
1343
	}
1344
}
1345

1346
func Test_doesNsNamesMatch(t *testing.T) {
1347
	type args struct {
1348
		matchNs v1alpha1.Namespace
1349
		ns      string
1350
		include bool
1351
	}
1352
	tests := []struct {
1353
		name  string
1354
		args  args
1355
		want  bool
1356
		want1 string
1357
	}{
1358
		{
1359
			name: "include namespace value must be matched",
1360
			args: args{
1361
				matchNs: v1alpha1.Namespace{
1362
					Values: []string{"kblt-test"},
1363
				},
1364
				ns:      "kblt-test",
1365
				include: true,
1366
			},
1367
			want:  true,
1368
			want1: "included namespace value",
1369
		},
1370
		{
1371
			name: "include namespace value must be not matched",
1372
			args: args{
1373
				matchNs: v1alpha1.Namespace{
1374
					Values: []string{"test"},
1375
				},
1376
				ns:      "kblt-test",
1377
				include: true,
1378
			},
1379
			want:  false,
1380
			want1: "included namespace value",
1381
		},
1382
		{
1383
			name: "exclude namespace value must be matched",
1384
			args: args{
1385
				matchNs: v1alpha1.Namespace{
1386
					Values: []string{"test"},
1387
				},
1388
				ns:      "kblt-test",
1389
				include: false,
1390
			},
1391
			want:  true,
1392
			want1: "excluded namespace value",
1393
		},
1394
		{
1395
			name: "exclude namespace value must be not matched",
1396
			args: args{
1397
				matchNs: v1alpha1.Namespace{
1398
					Values: []string{"kblt-test"},
1399
				},
1400
				ns:      "kblt-test",
1401
				include: false,
1402
			},
1403
			want:  false,
1404
			want1: "excluded namespace value",
1405
		},
1406
		{
1407
			name: "exclude namespace regex must be not matched",
1408
			args: args{
1409
				matchNs: v1alpha1.Namespace{
1410
					Regex: []string{"kblt-.*"},
1411
				},
1412
				ns:      "kblt-test",
1413
				include: false,
1414
			},
1415
			want:  false,
1416
			want1: "excluded namespace regex",
1417
		},
1418
		{
1419
			name: "exclude namespace value must be not matched",
1420
			args: args{
1421
				matchNs: v1alpha1.Namespace{
1422
					Regex: []string{".*-test"},
1423
				},
1424
				ns:      "kblt",
1425
				include: false,
1426
			},
1427
			want:  true,
1428
			want1: "excluded namespace regex",
1429
		},
1430
		{
1431
			name: "exclude namespace invalid regex",
1432
			args: args{
1433
				matchNs: v1alpha1.Namespace{
1434
					Regex: []string{"a(b"},
1435
				},
1436
				ns:      "kblt",
1437
				include: false,
1438
			},
1439
			want:  false,
1440
			want1: "namespace invalid regex",
1441
		},
1442
	}
1443
	for _, tt := range tests {
1444
		t.Run(tt.name, func(t *testing.T) {
1445
			lib.ZapLogger = zap.NewNop()
1446
			m := Matcher{}
1447
			got, got1 := m.doesNsNamesMatch(tt.args.matchNs, tt.args.ns, tt.args.include)
1448
			if got != tt.want {
1449
				t.Errorf("doesNsNamesMatch() got = %v, want %v", got, tt.want)
1450
			}
1451
			if got1 != tt.want1 {
1452
				t.Errorf("doesNsNamesMatch() got1 = %v, want %v", got1, tt.want1)
1453
			}
1454
		})
1455
	}
1456
}
1457

1458
func Test_doesObjectSectionMatches(t *testing.T) {
1459
	type args struct {
1460
		obj       map[string]interface{}
1461
		matchObjs []v1alpha1.Obj
1462
	}
1463
	tests := []struct {
1464
		name  string
1465
		args  args
1466
		want  bool
1467
		want1 string
1468
	}{
1469
		{
1470
			name: "Empty Match",
1471
			args: args{
1472
				obj: map[string]interface{}{
1473
					"metadata": map[string]interface{}{
1474
						"name":         "name-obj",
1475
						"generateName": "generateName-obj",
1476
					},
1477
				},
1478
				matchObjs: nil,
1479
			},
1480
			want:  false,
1481
			want1: "match conditions doesn't have enough of arguments",
1482
		},
1483
		{
1484
			name: "MustExist Exist",
1485
			args: args{
1486
				obj: map[string]interface{}{
1487
					"metadata": map[string]interface{}{
1488
						"name":         "name-obj",
1489
						"generateName": "generateName-obj",
1490
					},
1491
				},
1492
				matchObjs: []v1alpha1.Obj{
1493
					{
1494
						Path:      "metadata",
1495
						Condition: mustExist,
1496
					},
1497
				},
1498
			},
1499
			want:  true,
1500
			want1: "",
1501
		},
1502
		{
1503
			name: "MustExist NotExist",
1504
			args: args{
1505
				obj: map[string]interface{}{
1506
					"metadata": map[string]interface{}{
1507
						"name":         "name-obj",
1508
						"generateName": "generateName-obj",
1509
					},
1510
				},
1511
				matchObjs: []v1alpha1.Obj{
1512
					{
1513
						Path:      "test",
1514
						Condition: mustExist,
1515
					},
1516
				},
1517
			},
1518
			want:  false,
1519
			want1: "match conditions",
1520
		},
1521
		{
1522
			name: "MustNotExist NotExist",
1523
			args: args{
1524
				obj: map[string]interface{}{
1525
					"metadata": map[string]interface{}{
1526
						"name":         "name-obj",
1527
						"generateName": "generateName-obj",
1528
					},
1529
				},
1530
				matchObjs: []v1alpha1.Obj{
1531
					{
1532
						Path:      "test",
1533
						Condition: mustNotExist,
1534
					},
1535
				},
1536
			},
1537
			want:  true,
1538
			want1: "",
1539
		},
1540
		{
1541
			name: "MustNotExist Exist",
1542
			args: args{
1543
				obj: map[string]interface{}{
1544
					"metadata": map[string]interface{}{
1545
						"name":         "name-obj",
1546
						"generateName": "generateName-obj",
1547
					},
1548
				},
1549
				matchObjs: []v1alpha1.Obj{
1550
					{
1551
						Path:      "metadata",
1552
						Condition: mustNotExist,
1553
					},
1554
				},
1555
			},
1556
			want:  false,
1557
			want1: "match conditions",
1558
		},
1559
		{
1560
			name: "Wrong path",
1561
			args: args{
1562
				obj: map[string]interface{}{
1563
					"metadata": map[string]interface{}{
1564
						"name":         "name-obj",
1565
						"generateName": "generateName-obj",
1566
					},
1567
					"spec": map[string]interface{}{
1568
						"containers": []map[string]string{
1569
							{
1570
								"name":  "istio-proxy",
1571
								"value": "val",
1572
							},
1573
						},
1574
						"generateName": "generateName-obj",
1575
					},
1576
				},
1577
				matchObjs: []v1alpha1.Obj{
1578
					{
1579
						Path:      "spec.containers[?@name == `istio-proxy`]",
1580
						Condition: mustExist,
1581
					},
1582
				},
1583
			},
1584
			want:  false,
1585
			want1: "match conditions jmespath processing error",
1586
		},
1587
		{
1588
			name: "slice matched",
1589
			args: args{
1590
				obj: map[string]interface{}{
1591
					"metadata": map[string]interface{}{
1592
						"name":         "name-obj",
1593
						"generateName": "generateName-obj",
1594
					},
1595
					"spec": map[string]interface{}{
1596
						"containers": []map[string]string{
1597
							{
1598
								"name":  "istio-proxy",
1599
								"value": "match",
1600
								"test":  "val",
1601
							},
1602
							{
1603
								"name":  "test3",
1604
								"value": "val",
1605
								"match": "test",
1606
							},
1607
						},
1608
					},
1609
				},
1610
				matchObjs: []v1alpha1.Obj{
1611
					{
1612
						Path:      "spec.containers[? @.name == 'test1']",
1613
						Condition: mustExist,
1614
					},
1615
				},
1616
			},
1617
			want:  false,
1618
			want1: "match conditions",
1619
		},
1620
		{
1621
			name: "slice not matched",
1622
			args: args{
1623
				obj: map[string]interface{}{
1624
					"metadata": map[string]interface{}{
1625
						"name":         "name-obj",
1626
						"generateName": "generateName-obj",
1627
					},
1628
					"spec": map[string]interface{}{
1629
						"containers": []map[string]string{
1630
							{
1631
								"name":  "istio-proxy",
1632
								"value": "match",
1633
								"test":  "val",
1634
							},
1635
							{
1636
								"name":  "test3",
1637
								"value": "val",
1638
								"match": "test",
1639
							},
1640
						},
1641
					},
1642
				},
1643
				matchObjs: []v1alpha1.Obj{
1644
					{
1645
						Path:      "spec.containers[? @.name == 'test1']",
1646
						Condition: mustNotExist,
1647
					},
1648
				},
1649
			},
1650
			want:  true,
1651
			want1: "",
1652
		},
1653
		{
1654
			name: "boolean result",
1655
			args: args{
1656
				obj: map[string]interface{}{
1657
					"metadata": map[string]interface{}{
1658
						"name":         "name-obj",
1659
						"generateName": "generateName-obj",
1660
					},
1661
					"spec": map[string]interface{}{
1662
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1663
							{
1664
								"name":  "istio-proxy",
1665
								"value": "match",
1666
								"test":  "val",
1667
							},
1668
							{
1669
								"name":  "test3",
1670
								"value": "val",
1671
								"match": "test3",
1672
								"image": "val",
1673
							},
1674
						},
1675
					},
1676
				},
1677
				matchObjs: []v1alpha1.Obj{
1678
					{
1679
						Path:      "contains(spec.containers[? @.name==`test3`].image, `val`)",
1680
						Condition: mustExist,
1681
					},
1682
				},
1683
			},
1684
			want:  true,
1685
			want1: "",
1686
		},
1687
		{
1688
			name: "boolean result not matched",
1689
			args: args{
1690
				obj: map[string]interface{}{
1691
					"metadata": map[string]interface{}{
1692
						"name":         "name-obj",
1693
						"generateName": "generateName-obj",
1694
					},
1695
					"spec": map[string]interface{}{
1696
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1697
							{
1698
								"name":  "istio-proxy",
1699
								"value": "match",
1700
								"test":  "val",
1701
							},
1702
							{
1703
								"name":  "test3",
1704
								"value": "val",
1705
								"match": "test3",
1706
								"image": "val",
1707
							},
1708
						},
1709
					},
1710
				},
1711
				matchObjs: []v1alpha1.Obj{
1712
					{
1713
						Path:      "contains(spec.containers[? @.name==`test3`].image, `not mathced`)",
1714
						Condition: mustExist,
1715
					},
1716
				},
1717
			},
1718
			want:  false,
1719
			want1: "match conditions",
1720
		},
1721
		{
1722
			name: "boolean result not exist true",
1723
			args: args{
1724
				obj: map[string]interface{}{
1725
					"metadata": map[string]interface{}{
1726
						"name":         "name-obj",
1727
						"generateName": "generateName-obj",
1728
					},
1729
					"spec": map[string]interface{}{
1730
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1731
							{
1732
								"name":  "istio-proxy",
1733
								"value": "match",
1734
								"test":  "val",
1735
							},
1736
							{
1737
								"name":  "test3",
1738
								"value": "val",
1739
								"match": "test3",
1740
								"image": "val",
1741
							},
1742
						},
1743
					},
1744
				},
1745
				matchObjs: []v1alpha1.Obj{
1746
					{
1747
						Path:      "contains(spec.containers[? @.name==`test3`].image, `not matched`)",
1748
						Condition: mustNotExist,
1749
					},
1750
				},
1751
			},
1752
			want:  true,
1753
			want1: "",
1754
		},
1755
		{
1756
			name: "boolean result not exist true",
1757
			args: args{
1758
				obj: map[string]interface{}{
1759
					"metadata": map[string]interface{}{
1760
						"name":         "name-obj",
1761
						"generateName": "generateName-obj",
1762
					},
1763
					"spec": map[string]interface{}{
1764
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1765
							{
1766
								"name":  "istio-proxy",
1767
								"value": "match",
1768
								"test":  "val",
1769
							},
1770
							{
1771
								"name":  "test3",
1772
								"value": "val",
1773
								"match": "test3",
1774
								"image": "val",
1775
							},
1776
						},
1777
					},
1778
				},
1779
				matchObjs: []v1alpha1.Obj{
1780
					{
1781
						Path:      "contains(spec.containers[? @.name==`test3`].image, `val`)",
1782
						Condition: mustNotExist,
1783
					},
1784
				},
1785
			},
1786
			want:  false,
1787
			want1: "match conditions",
1788
		},
1789
		{
1790
			name: "matched result is a number",
1791
			args: args{
1792
				obj: map[string]interface{}{
1793
					"metadata": map[string]interface{}{
1794
						"name":         "name-obj",
1795
						"generateName": "generateName-obj",
1796
						"value":        3.14,
1797
					},
1798
					"spec": map[string]interface{}{
1799
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1800
							{
1801
								"name":  "istio-proxy",
1802
								"value": "3.14",
1803
								"test":  "val",
1804
							},
1805
							{
1806
								"name":  "test3",
1807
								"value": "val",
1808
								"match": "test3",
1809
								"image": "val",
1810
							},
1811
						},
1812
					},
1813
				},
1814
				matchObjs: []v1alpha1.Obj{
1815
					{
1816
						Path:      "metadata.value",
1817
						Condition: mustExist,
1818
					},
1819
				},
1820
			},
1821
			want:  true,
1822
			want1: "",
1823
		},
1824
		{
1825
			name: "matched result is a string",
1826
			args: args{
1827
				obj: map[string]interface{}{
1828
					"metadata": map[string]interface{}{
1829
						"name":         "name-obj",
1830
						"generateName": "generateName-obj",
1831
						"value":        3.14,
1832
					},
1833
					"spec": map[string]interface{}{
1834
						"containers": []map[string]interface{}{ //[]map[string]string - wrong type
1835
							{
1836
								"name":  "istio-proxy",
1837
								"value": "3.14",
1838
								"test":  "val",
1839
							},
1840
							{
1841
								"name":  "test3",
1842
								"value": "val",
1843
								"match": "test3",
1844
								"image": "val",
1845
							},
1846
						},
1847
					},
1848
				},
1849
				matchObjs: []v1alpha1.Obj{
1850
					{
1851
						Path:      "metadata.name",
1852
						Condition: mustExist,
1853
					},
1854
				},
1855
			},
1856
			want:  true,
1857
			want1: "",
1858
		},
1859
		{
1860
			name: "matched result is a map",
1861
			args: args{
1862
				obj: map[string]interface{}{
1863
					"metadata": map[string]interface{}{
1864
						"name":         "name-obj",
1865
						"generateName": "generateName-obj",
1866
						"value":        3.14,
1867
					},
1868
				},
1869
				matchObjs: []v1alpha1.Obj{
1870
					{
1871
						Path:      "metadata",
1872
						Condition: mustExist,
1873
					},
1874
				},
1875
			},
1876
			want:  true,
1877
			want1: "",
1878
		},
1879
	}
1880
	for _, tt := range tests {
1881
		t.Run(tt.name, func(t *testing.T) {
1882
			lib.ZapLogger = zap.NewNop()
1883
			m := Matcher{}
1884
			got, got1 := m.doesObjectSectionMatches(tt.args.obj, tt.args.matchObjs)
1885
			if got != tt.want {
1886
				t.Errorf("doesObjectSectionMatches() got = %v, want %v", got, tt.want)
1887
			}
1888
			if got1 != tt.want1 {
1889
				t.Errorf("doesObjectSectionMatches() got1 = %v, want %v", got1, tt.want1)
1890
			}
1891
		})
1892
	}
1893
}
1894

1895
func Test_doesOperationsMatches(t *testing.T) {
1896
	type args struct {
1897
		fields common.ARFields
1898
		match  v1alpha1.Match
1899
	}
1900
	tests := []struct {
1901
		name        string
1902
		args        args
1903
		wantMatches bool
1904
		wantReason  string
1905
	}{
1906
		{
1907
			name: "Empty match section",
1908
			args: args{
1909
				fields: common.ARFields{
1910
					Operation: v1beta1.Create,
1911
				},
1912
				match: v1alpha1.Match{},
1913
			},
1914
			wantMatches: true,
1915
			wantReason:  "",
1916
		},
1917
		{
1918
			name: "Must be Matched",
1919
			args: args{
1920
				fields: common.ARFields{
1921
					Operation: v1beta1.Create,
1922
				},
1923
				match: v1alpha1.Match{
1924
					Operations: []string{"CREATE"},
1925
				},
1926
			},
1927
			wantMatches: true,
1928
			wantReason:  "",
1929
		},
1930
		{
1931
			name: "Must be not Matched",
1932
			args: args{
1933
				fields: common.ARFields{
1934
					Operation: v1beta1.Delete,
1935
				},
1936
				match: v1alpha1.Match{
1937
					Operations: []string{"CREATE"},
1938
				},
1939
			},
1940
			wantMatches: false,
1941
			wantReason:  "request.operation",
1942
		},
1943
	}
1944
	for _, tt := range tests {
1945
		t.Run(tt.name, func(t *testing.T) {
1946
			m := Matcher{}
1947
			gotMatches, gotReason := m.doesOperationsMatches(context.Background(), tt.args.fields, tt.args.match)
1948
			if gotMatches != tt.wantMatches {
1949
				t.Errorf("doesOperationsMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
1950
			}
1951
			if gotReason != tt.wantReason {
1952
				t.Errorf("doesOperationsMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
1953
			}
1954
		})
1955
	}
1956
}
1957

1958
func Test_doesScopeMatches(t *testing.T) {
1959
	type args struct {
1960
		fields common.ARFields
1961
		match  v1alpha1.Match
1962
	}
1963
	tests := []struct {
1964
		name        string
1965
		args        args
1966
		wantMatches bool
1967
		wantReason  string
1968
	}{
1969
		{
1970
			name: "Namespaced match",
1971
			args: args{
1972
				fields: common.ARFields{
1973
					Namespace: "kblt",
1974
				},
1975
				match: v1alpha1.Match{
1976
					Scope: scopeNamespaced,
1977
				},
1978
			},
1979
			wantMatches: true,
1980
			wantReason:  "",
1981
		},
1982
		{
1983
			name: "Namespaced not match",
1984
			args: args{
1985
				fields: common.ARFields{},
1986
				match: v1alpha1.Match{
1987
					Scope: scopeNamespaced,
1988
				},
1989
			},
1990
			wantMatches: false,
1991
			wantReason:  "request.namespace",
1992
		},
1993
		{
1994
			name: "Cluster match",
1995
			args: args{
1996
				fields: common.ARFields{},
1997
				match: v1alpha1.Match{
1998
					Scope: scopeCluster,
1999
				},
2000
			},
2001
			wantMatches: true,
2002
			wantReason:  "",
2003
		},
2004
		{
2005
			name: "Cluster not match",
2006
			args: args{
2007
				fields: common.ARFields{
2008
					Namespace: "kblt",
2009
				},
2010
				match: v1alpha1.Match{
2011
					Scope: scopeCluster,
2012
				},
2013
			},
2014
			wantMatches: false,
2015
			wantReason:  "request.namespace",
2016
		},
2017
		{
2018
			name: "Any match",
2019
			args: args{
2020
				fields: common.ARFields{},
2021
				match: v1alpha1.Match{
2022
					Scope: scopeAny,
2023
				},
2024
			},
2025
			wantMatches: true,
2026
			wantReason:  "",
2027
		},
2028
		{
2029
			name: "Empty match section",
2030
			args: args{
2031
				fields: common.ARFields{
2032
					Namespace: "kblt",
2033
				},
2034
				match: v1alpha1.Match{},
2035
			},
2036
			wantMatches: true,
2037
			wantReason:  "",
2038
		},
2039
	}
2040
	for _, tt := range tests {
2041
		t.Run(tt.name, func(t *testing.T) {
2042
			m := Matcher{}
2043
			gotMatches, gotReason := m.doesScopeMatches(context.Background(), tt.args.fields, tt.args.match)
2044
			if gotMatches != tt.wantMatches {
2045
				t.Errorf("doesScopeMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
2046
			}
2047
			if gotReason != tt.wantReason {
2048
				t.Errorf("doesScopeMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
2049
			}
2050
		})
2051
	}
2052
}
2053

2054
func Test_doesUserInfoMatches(t *testing.T) {
2055
	type args struct {
2056
		fields common.ARFields
2057
		match  v1alpha1.Match
2058
	}
2059
	tests := []struct {
2060
		name        string
2061
		args        args
2062
		wantMatches bool
2063
		wantReason  string
2064
	}{
2065
		{
2066
			name: "Exist user value",
2067
			args: args{
2068
				fields: common.ARFields{
2069
					Namespace: "kblt",
2070
					UserInfo: common.ARUserInfo{
2071
						Username: "user-for-test",
2072
					},
2073
					Operation: "CREATE",
2074
				},
2075
				match: v1alpha1.Match{
2076
					UserInfo: v1alpha1.UserInfo{
2077
						Username: "user-for-test",
2078
					},
2079
				},
2080
			},
2081
			wantMatches: true,
2082
			wantReason:  "",
2083
		},
2084
		{
2085
			name: "NotExist user value",
2086
			args: args{
2087
				fields: common.ARFields{
2088
					Namespace: "kblt",
2089
					UserInfo: common.ARUserInfo{
2090
						Username: "user-for-test",
2091
					},
2092
					Operation: "CREATE",
2093
				},
2094
				match: v1alpha1.Match{
2095
					UserInfo: v1alpha1.UserInfo{
2096
						Username: "test-user",
2097
					},
2098
				},
2099
			},
2100
			wantMatches: false,
2101
			wantReason:  "request.userInfo.username",
2102
		},
2103
		{
2104
			name: "Exist user regex",
2105
			args: args{
2106
				fields: common.ARFields{
2107
					Namespace: "kblt",
2108
					UserInfo: common.ARUserInfo{
2109
						Username: "user-for-test",
2110
					},
2111
					Operation: "CREATE",
2112
				},
2113
				match: v1alpha1.Match{
2114
					UserInfo: v1alpha1.UserInfo{
2115
						UsernameRegex: ".*-for-.*",
2116
					},
2117
				},
2118
			},
2119
			wantMatches: true,
2120
			wantReason:  "",
2121
		},
2122
		{
2123
			name: "NotExist user regex",
2124
			args: args{
2125
				fields: common.ARFields{
2126
					Namespace: "kblt",
2127
					UserInfo: common.ARUserInfo{
2128
						Username: "user-for-test",
2129
					},
2130
					Operation: "CREATE",
2131
				},
2132
				match: v1alpha1.Match{
2133
					UserInfo: v1alpha1.UserInfo{
2134
						UsernameRegex: "ololol-.*",
2135
					},
2136
				},
2137
			},
2138
			wantMatches: false,
2139
			wantReason:  "request.userInfo.username",
2140
		},
2141
		{
2142
			name: "Wrong user regex",
2143
			args: args{
2144
				fields: common.ARFields{
2145
					Namespace: "kblt",
2146
					UserInfo: common.ARUserInfo{
2147
						Username: "user-for-test",
2148
					},
2149
					Operation: "CREATE",
2150
				},
2151
				match: v1alpha1.Match{
2152
					UserInfo: v1alpha1.UserInfo{
2153
						UsernameRegex: `a(b`,
2154
					},
2155
				},
2156
			},
2157
			wantMatches: false,
2158
			wantReason:  "wrong match.userInfo.usernameRegex: error parsing regexp: missing closing ): `a(b`",
2159
		},
2160
		{
2161
			name: "Empty Match section",
2162
			args: args{
2163
				fields: common.ARFields{
2164
					Namespace: "kblt",
2165
					UserInfo: common.ARUserInfo{
2166
						Username: "user-for-test",
2167
					},
2168
					Operation: "CREATE",
2169
				},
2170
				match: v1alpha1.Match{
2171
					UserInfo: v1alpha1.UserInfo{
2172
						Username:      "",
2173
						UsernameRegex: "",
2174
					},
2175
				},
2176
			},
2177
			wantMatches: true,
2178
			wantReason:  "",
2179
		},
2180
		{
2181
			name: "Empty Match section",
2182
			args: args{
2183
				fields: common.ARFields{
2184
					Namespace: "kblt",
2185
					UserInfo: common.ARUserInfo{
2186
						Username: "user-for-test",
2187
					},
2188
					Operation: "CREATE",
2189
				},
2190
				match: v1alpha1.Match{},
2191
			},
2192
			wantMatches: true,
2193
			wantReason:  "",
2194
		},
2195
	}
2196
	for _, tt := range tests {
2197
		t.Run(tt.name, func(t *testing.T) {
2198
			m := Matcher{}
2199
			gotMatches, gotReason := m.doesUserInfoMatches(context.Background(), tt.args.fields, tt.args.match)
2200
			if gotMatches != tt.wantMatches {
2201
				t.Errorf("doesUserInfoMatches() gotMatches = %v, want %v", gotMatches, tt.wantMatches)
2202
			}
2203
			if gotReason != tt.wantReason {
2204
				t.Errorf("doesUserInfoMatches() gotReason = %v, want %v", gotReason, tt.wantReason)
2205
			}
2206
		})
2207
	}
2208
}
2209

2210
func Test_findNonRegexNsMatch(t *testing.T) {
2211
	type args struct {
2212
		ns       string
2213
		matchVal []string
2214
	}
2215
	tests := []struct {
2216
		name  string
2217
		args  args
2218
		want  bool
2219
		want1 string
2220
	}{
2221
		{
2222
			name: "Exist value",
2223
			args: args{
2224
				ns:       "kblt",
2225
				matchVal: []string{"kblt", "test"},
2226
			},
2227
			want:  true,
2228
			want1: "namespace value",
2229
		},
2230
		{
2231
			name: "NotExist value",
2232
			args: args{
2233
				ns:       "kblt",
2234
				matchVal: []string{"test"},
2235
			},
2236
			want:  false,
2237
			want1: "namespace value",
2238
		},
2239
	}
2240
	for _, tt := range tests {
2241
		t.Run(tt.name, func(t *testing.T) {
2242
			got, got1 := findNonRegexNsMatch(tt.args.ns, tt.args.matchVal)
2243
			if got != tt.want {
2244
				t.Errorf("findNonRegexNsMatch() got = %v, want %v", got, tt.want)
2245
			}
2246
			if got1 != tt.want1 {
2247
				t.Errorf("findNonRegexNsMatch() got1 = %v, want %v", got1, tt.want1)
2248
			}
2249
		})
2250
	}
2251
}
2252

2253
func Test_findRegexNsMatch(t *testing.T) {
2254
	type args struct {
2255
		ns       string
2256
		matchVal []string
2257
	}
2258
	tests := []struct {
2259
		name  string
2260
		args  args
2261
		want  bool
2262
		want1 string
2263
	}{
2264
		{
2265
			name: "Exist value",
2266
			args: args{
2267
				ns:       "kblt",
2268
				matchVal: []string{".*blt"},
2269
			},
2270
			want:  true,
2271
			want1: "namespace regex",
2272
		},
2273
		{
2274
			name: "NotExist value",
2275
			args: args{
2276
				ns:       "kblt",
2277
				matchVal: []string{".*-test"},
2278
			},
2279
			want:  false,
2280
			want1: "namespace regex",
2281
		},
2282
		{
2283
			name: "Wrong regex",
2284
			args: args{
2285
				ns:       "kblt",
2286
				matchVal: []string{`a(b`},
2287
			},
2288
			want:  false,
2289
			want1: "namespace invalid regex",
2290
		},
2291
	}
2292
	for _, tt := range tests {
2293
		t.Run(tt.name, func(t *testing.T) {
2294
			lib.ZapLogger = zap.NewNop()
2295
			got, got1 := findRegexNsMatch(tt.args.ns, tt.args.matchVal)
2296
			if got != tt.want {
2297
				t.Errorf("findRegexNsMatch() got = %v, want %v", got, tt.want)
2298
			}
2299
			if got1 != tt.want1 {
2300
				t.Errorf("findRegexNsMatch() got1 = %v, want %v", got1, tt.want1)
2301
			}
2302
		})
2303
	}
2304
}
2305

2306
func Test_getAdmissionReviewResourceName(t *testing.T) {
2307
	type args struct {
2308
		obj map[string]interface{}
2309
	}
2310
	tests := []struct {
2311
		name     string
2312
		args     args
2313
		wantName string
2314
	}{
2315
		{
2316
			name: "name",
2317
			args: args{
2318
				obj: map[string]interface{}{
2319
					"metadata": map[string]interface{}{
2320
						"name":         "name-obj",
2321
						"generateName": "generateName-obj",
2322
					},
2323
				},
2324
			},
2325
			wantName: "name-obj",
2326
		},
2327
		{
2328
			name: "generateName",
2329
			args: args{
2330
				obj: map[string]interface{}{
2331
					"metadata": map[string]interface{}{
2332
						"generateName": "generateName-obj",
2333
					},
2334
				},
2335
			},
2336
			wantName: "generateName-obj",
2337
		},
2338
		{
2339
			name: "empty name",
2340
			args: args{
2341
				obj: map[string]interface{}{
2342
					"metadata": map[string]interface{}{
2343
						"whatsYourName": "zhoskiy",
2344
					},
2345
				},
2346
			},
2347
			wantName: "config_name_was_not_found",
2348
		},
2349
	}
2350
	for _, tt := range tests {
2351
		t.Run(tt.name, func(t *testing.T) {
2352
			if gotName := getAdmissionReviewResourceName(tt.args.obj); gotName != tt.wantName {
2353
				t.Errorf("getAdmissionReviewResourceName() = %v, want %v", gotName, tt.wantName)
2354
			}
2355
		})
2356
	}
2357
}
2358

2359
func Test_getObjectToValidate(t *testing.T) {
2360
	type args struct {
2361
		data common.ARFields
2362
	}
2363
	tests := []struct {
2364
		name string
2365
		args args
2366
		want map[string]interface{}
2367
	}{
2368
		{
2369
			name: "Create",
2370
			args: args{data: common.ARFields{
2371
				Operation: v1beta1.Create,
2372
				OldObject: map[string]interface{}{"old": -1},
2373
				Object:    map[string]interface{}{"new": 1},
2374
			}},
2375
			want: map[string]interface{}{"new": 1},
2376
		},
2377
		{
2378
			name: "Update",
2379
			args: args{data: common.ARFields{
2380
				Operation: v1beta1.Update,
2381
				OldObject: map[string]interface{}{"old": -1},
2382
				Object:    map[string]interface{}{"new": 1},
2383
			}},
2384
			want: map[string]interface{}{"new": 1},
2385
		},
2386
		{
2387
			name: "Delete",
2388
			args: args{data: common.ARFields{
2389
				Operation: v1beta1.Delete,
2390
				OldObject: map[string]interface{}{"old": -1},
2391
				Object:    map[string]interface{}{"new": 1},
2392
			}},
2393
			want: map[string]interface{}{"old": -1},
2394
		},
2395
		{
2396
			name: "Wrong",
2397
			args: args{data: common.ARFields{
2398
				Operation: v1beta1.Operation("wrong"),
2399
				OldObject: map[string]interface{}{"old": -1},
2400
				Object:    map[string]interface{}{"new": 1},
2401
			}},
2402
			want: nil,
2403
		},
2404
	}
2405
	for _, tt := range tests {
2406
		t.Run(tt.name, func(t *testing.T) {
2407
			lib.ZapLogger = zap.NewNop()
2408
			assert.Equalf(t, tt.want, getObjectToValidate(context.Background(), tt.args.data), "getObjectToValidate(%v)", tt.args.data)
2409
		})
2410
	}
2411
}
2412

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.