From f7b544419263c4ee87a81fb44baccd4c16061eab Mon Sep 17 00:00:00 2001 From: Jaydip Gabani Date: Thu, 6 Jul 2023 17:37:37 +0000 Subject: [PATCH] adding wildcard matching for generate name Signed-off-by: Jaydip Gabani --- pkg/mutation/match/match.go | 2 +- pkg/mutation/match/match_test.go | 207 +++++++++++++++++++++++++++++++ pkg/wildcard/wildcard.go | 12 ++ pkg/wildcard/wildcard_test.go | 98 ++++++++++++++- 4 files changed, 317 insertions(+), 2 deletions(-) diff --git a/pkg/mutation/match/match.go b/pkg/mutation/match/match.go index d1b63917176..f0f935479a6 100644 --- a/pkg/mutation/match/match.go +++ b/pkg/mutation/match/match.go @@ -208,7 +208,7 @@ func namesMatch(match *Match, target *Matchable) (bool, error) { return true, nil } - return match.Name.Matches(target.Object.GetName()), nil + return match.Name.Matches(target.Object.GetName()) || match.Name.MatchesGenerateName(target.Object.GetGenerateName()), nil } func scopeMatch(match *Match, target *Matchable) (bool, error) { diff --git a/pkg/mutation/match/match_test.go b/pkg/mutation/match/match_test.go index e490cb9a40c..5a5382fb4f2 100644 --- a/pkg/mutation/match/match_test.go +++ b/pkg/mutation/match/match_test.go @@ -831,3 +831,210 @@ func TestApplyTo(t *testing.T) { }) } } + +func makeObjectWithGenerateName(gvk schema.GroupVersionKind, namespace, name string, options ...func(*unstructured.Unstructured)) *unstructured.Unstructured { + obj := &unstructured.Unstructured{Object: make(map[string]interface{})} + obj.SetGroupVersionKind(gvk) + obj.SetNamespace(namespace) + obj.SetGenerateName(name) + + for _, o := range options { + o(obj) + } + return obj +} + +func Test_namesMatch(t *testing.T) { + type args struct { + match *Match + target *Matchable + } + + tests := []struct { + name string + args args + want bool + wantErr bool + }{ + { + name: "match name with wild card", + args: args{ + match: &Match{ + Name: "foo*", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObject(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "foo-bar"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: true, + wantErr: false, + }, + { + name: "match generate name with wild card", + args: args{ + match: &Match{ + Name: "foo*", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObjectWithGenerateName(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "foo-bar-"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: true, + wantErr: false, + }, + { + name: "match different name with wild card", + args: args{ + match: &Match{ + Name: "foo*", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObject(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "fob"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "match different generate name with wild card", + args: args{ + match: &Match{ + Name: "foo*", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObjectWithGenerateName(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "fob-bar-"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "match whole name with generate name", + args: args{ + match: &Match{ + Name: "foo", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObjectWithGenerateName(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "foo"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "match prefix wildcard with generate name", + args: args{ + match: &Match{ + Name: "*foo", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObjectWithGenerateName(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "foo"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: false, + wantErr: false, + }, + { + name: "match later half of the name with wild card with generate name", + args: args{ + match: &Match{ + Name: "*-bar*", + Kinds: []Kinds{ + { + Kinds: []string{"Pod"}, + APIGroups: []string{"*"}, + }, + }, + }, + target: &Matchable{ + Object: makeObjectWithGenerateName(schema.GroupVersionKind{Kind: "Pod", Group: "*"}, "my-ns", "fob-bar"), + Namespace: &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-ns", + }, + }, + }, + }, + want: true, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := namesMatch(tt.args.match, tt.args.target) + if (err != nil) != tt.wantErr { + t.Errorf("namesMatch() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("namesMatch() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/wildcard/wildcard.go b/pkg/wildcard/wildcard.go index 6e8eaccd00f..36df10bfa7c 100644 --- a/pkg/wildcard/wildcard.go +++ b/pkg/wildcard/wildcard.go @@ -27,3 +27,15 @@ func (w Wildcard) Matches(candidate string) bool { return wStr == candidate } } + +func (w Wildcard) MatchesGenerateName(candidate string) bool { + wStr := string(w) + switch { + case strings.HasPrefix(wStr, "*") && strings.HasSuffix(wStr, "*"): + return strings.Contains(candidate, strings.TrimSuffix(strings.TrimPrefix(wStr, "*"), "*")) + case strings.HasSuffix(wStr, "*"): + return strings.HasPrefix(candidate, strings.TrimSuffix(wStr, "*")) + default: + return false + } +} diff --git a/pkg/wildcard/wildcard_test.go b/pkg/wildcard/wildcard_test.go index 6ce86a39977..d3853eebae8 100644 --- a/pkg/wildcard/wildcard_test.go +++ b/pkg/wildcard/wildcard_test.go @@ -1,6 +1,8 @@ package wildcard -import "testing" +import ( + "testing" +) func TestMatches(t *testing.T) { tcs := []struct { @@ -95,3 +97,97 @@ func TestMatches(t *testing.T) { }) } } + +func TestWildcard_MatchesGenerateName(t *testing.T) { + tcs := []struct { + name string + w Wildcard + candidate string + matches bool + }{ + { + name: "exact text match", + w: Wildcard("kube-system"), + candidate: "kube-system", + matches: false, + }, + { + name: "no glob, wrong text", + w: Wildcard("kube-system"), + candidate: "gatekeeper-system", + matches: false, + }, + { + name: "wildcard prefix match", + w: Wildcard("kube-*"), + candidate: "kube-system", + matches: true, + }, + { + name: "wildcard prefix doesn't match", + w: Wildcard("kube-*"), + candidate: "gatekeeper-system", + matches: false, + }, + { + name: "wildcard suffix match", + w: Wildcard("*-system"), + candidate: "kube-system", + matches: false, + }, + { + name: "wildcard suffix doesn't match", + w: Wildcard("*-system"), + candidate: "kube-public", + matches: false, + }, + { + name: "missing asterisk yields no wildcard support", + w: Wildcard("kube-"), + candidate: "kube-system", + matches: false, + }, + { + name: "wildcard suffix and prefix match", + w: Wildcard("*-kube-*"), + candidate: "test-kube-test", + matches: true, + }, + { + name: "no wildcard, only hypens at suffix and prefix", + w: Wildcard("-kube-"), + candidate: "test-kube-test", + matches: false, + }, + { + name: "wild card at suffix and prefix, multiple hyphens", + w: Wildcard("*-kube-*"), + candidate: "test-dev-kube-dev-test", + matches: true, + }, + { + name: "wild card at suffid and end, multiple hypens, no match", + w: Wildcard("*-kube-*"), + candidate: "my-kub-controller", + matches: false, + }, + { + name: "wild card at suffix and prefix, multiple hyphens, no match", + w: Wildcard("*-kube-*"), + candidate: "my-controller-manager", + matches: false, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + if tc.w.MatchesGenerateName(tc.candidate) != tc.matches { + if tc.matches { + t.Errorf("Expected candidate '%v' to match wildcard '%v'", tc.candidate, tc.w) + } else { + t.Errorf("Candidate '%v' unexpectedly matched wildcard '%v'", tc.candidate, tc.w) + } + } + }) + } +}