diff --git a/api/resmap/reswrangler.go b/api/resmap/reswrangler.go index ce83767e6..f439831ba 100644 --- a/api/resmap/reswrangler.go +++ b/api/resmap/reswrangler.go @@ -6,7 +6,6 @@ package resmap import ( "bytes" "fmt" - "regexp" "github.com/pkg/errors" "sigs.k8s.io/kustomize/api/resid" @@ -510,51 +509,34 @@ func (m *resWrangler) appendReplaceOrMerge( return nil } -func anchorRegex(pattern string) string { - if pattern == "" { - return pattern - } - return "^(?:" + pattern + ")$" -} - // Select returns a list of resources that // are selected by a Selector func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) { - ns := regexp.MustCompile(anchorRegex(s.Namespace)) - nm := regexp.MustCompile(anchorRegex(s.Name)) var result []*resource.Resource + sr, err := types.NewSelectorRegex(&s) + if err != nil { + return nil, err + } for _, r := range m.Resources() { curId := r.CurId() orgId := r.OrgId() - // matches the namespace when namespace is not empty in the selector // It first tries to match with the original namespace // then matches with the current namespace - if s.Namespace != "" { - matched := ns.MatchString(orgId.EffectiveNamespace()) - if !matched { - matched = ns.MatchString(curId.EffectiveNamespace()) - if !matched { - continue - } - } + if !sr.MatchNamespace(orgId.EffectiveNamespace()) && + !sr.MatchNamespace(curId.EffectiveNamespace()) { + continue } - // matches the name when name is not empty in the selector // It first tries to match with the original name // then matches with the current name - if s.Name != "" { - matched := nm.MatchString(orgId.Name) - if !matched { - matched = nm.MatchString(curId.Name) - if !matched { - continue - } - } + if !sr.MatchName(orgId.Name) && + !sr.MatchName(curId.Name) { + continue } // matches the GVK - if !r.GetGvk().IsSelected(&s.Gvk) { + if !sr.MatchGvk(r.GetGvk()) { continue } diff --git a/api/types/selector.go b/api/types/selector.go index c58c5a985..007b508f1 100644 --- a/api/types/selector.go +++ b/api/types/selector.go @@ -4,6 +4,8 @@ package types import ( + "regexp" + "sigs.k8s.io/kustomize/api/resid" ) @@ -25,3 +27,89 @@ type Selector struct { // It matches with the resource labels. LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"` } + +// SelectorRegex is a Selector with regex in GVK +// Any resource that matches intersection of all conditions +// is included in this set. +type SelectorRegex struct { + selector *Selector + groupRegex *regexp.Regexp + versionRegex *regexp.Regexp + kindRegex *regexp.Regexp + nameRegex *regexp.Regexp + namespaceRegex *regexp.Regexp +} + +// NewSelectorRegex returns a pointer to a new SelectorRegex +// which uses the same condition as s. +func NewSelectorRegex(s *Selector) (*SelectorRegex, error) { + sr := new(SelectorRegex) + var err error + sr.selector = s + sr.groupRegex, err = regexp.Compile(anchorRegex(s.Gvk.Group)) + if err != nil { + return nil, err + } + sr.versionRegex, err = regexp.Compile(anchorRegex(s.Gvk.Version)) + if err != nil { + return nil, err + } + sr.kindRegex, err = regexp.Compile(anchorRegex(s.Gvk.Kind)) + if err != nil { + return nil, err + } + sr.nameRegex, err = regexp.Compile(anchorRegex(s.Name)) + if err != nil { + return nil, err + } + sr.namespaceRegex, err = regexp.Compile(anchorRegex(s.Namespace)) + if err != nil { + return nil, err + } + return sr, nil +} + +func anchorRegex(pattern string) string { + if pattern == "" { + return pattern + } + return "^(?:" + pattern + ")$" +} + +// MatchGvk return true if gvk can be matched by s. +func (s *SelectorRegex) MatchGvk(gvk resid.Gvk) bool { + if len(s.selector.Gvk.Group) > 0 { + if !s.groupRegex.MatchString(gvk.Group) { + return false + } + } + if len(s.selector.Gvk.Version) > 0 { + if !s.versionRegex.MatchString(gvk.Version) { + return false + } + } + if len(s.selector.Gvk.Kind) > 0 { + if !s.kindRegex.MatchString(gvk.Kind) { + return false + } + } + return true +} + +// MatchName returns true if the name in selector is +// empty or the n can be matches by the name in selector +func (s *SelectorRegex) MatchName(n string) bool { + if s.selector.Name == "" { + return true + } + return s.nameRegex.MatchString(n) +} + +// MatchNamespace returns true if the namespace in selector is +// empty or the ns can be matches by the namespace in selector +func (s *SelectorRegex) MatchNamespace(ns string) bool { + if s.selector.Namespace == "" { + return true + } + return s.namespaceRegex.MatchString(ns) +} diff --git a/api/types/selector_test.go b/api/types/selector_test.go new file mode 100644 index 000000000..9625d9964 --- /dev/null +++ b/api/types/selector_test.go @@ -0,0 +1,216 @@ +package types_test + +import ( + "testing" + + "sigs.k8s.io/kustomize/api/resid" + . "sigs.k8s.io/kustomize/api/types" +) + +func TestSelectorRegexMatchGvk(t *testing.T) { + testcases := []struct { + S Selector + G resid.Gvk + Expected bool + }{ + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind", + }, + Expected: true, + }, + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "group", + Version: "", + Kind: "", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind", + }, + Expected: true, + }, + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "", + }, + Expected: false, + }, + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "version", + Kind: "kind2", + }, + Expected: false, + }, + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "g.*", + Version: "\\d+", + Kind: ".{4}", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "123", + Kind: "abcd", + }, + Expected: true, + }, + { + S: Selector{ + Gvk: resid.Gvk{ + Group: "g.*", + Version: "\\d+", + Kind: ".{4}", + }, + }, + G: resid.Gvk{ + Group: "group", + Version: "123", + Kind: "abc", + }, + Expected: false, + }, + } + + for _, tc := range testcases { + sr, err := NewSelectorRegex(&tc.S) + if err != nil { + t.Fatal(err) + } + if sr.MatchGvk(tc.G) != tc.Expected { + t.Fatalf("unexpected result for selector gvk %s and gvk %s", + tc.S.Gvk.String(), tc.G.String()) + } + } +} + +func TestSelectorRegexMatchName(t *testing.T) { + testcases := []struct { + S Selector + Name string + Expected bool + }{ + { + S: Selector{ + Name: "foo", + Namespace: "bar", + }, + Name: "foo", + Expected: true, + }, + { + S: Selector{ + Name: "foo", + Namespace: "bar", + }, + Name: "bar", + Expected: false, + }, + { + S: Selector{ + Name: "f.*", + }, + Name: "foo", + Expected: true, + }, + { + S: Selector{ + Name: "b.*", + }, + Name: "foo", + Expected: false, + }, + } + for _, tc := range testcases { + sr, err := NewSelectorRegex(&tc.S) + if err != nil { + t.Fatal(err) + } + if sr.MatchName(tc.Name) != tc.Expected { + t.Fatalf("unexpected result for selector name %s and name %s", + tc.S.Name, tc.Name) + } + } +} + +func TestSelectorRegexMatchNamespace(t *testing.T) { + testcases := []struct { + S Selector + Namespace string + Expected bool + }{ + { + S: Selector{ + Name: "bar", + Namespace: "foo", + }, + Namespace: "foo", + Expected: true, + }, + { + S: Selector{ + Name: "foo", + Namespace: "bar", + }, + Namespace: "foo", + Expected: false, + }, + { + S: Selector{ + Namespace: "f.*", + }, + Namespace: "foo", + Expected: true, + }, + { + S: Selector{ + Namespace: "b.*", + }, + Namespace: "foo", + Expected: false, + }, + } + for _, tc := range testcases { + sr, err := NewSelectorRegex(&tc.S) + if err != nil { + t.Fatal(err) + } + if sr.MatchNamespace(tc.Namespace) != tc.Expected { + t.Fatalf("unexpected result for selector namespace %s and namespace %s", + tc.S.Namespace, tc.Namespace) + } + } +} diff --git a/plugin/builtin/patchtransformer/PatchTransformer_test.go b/plugin/builtin/patchtransformer/PatchTransformer_test.go index aab64751e..fde318080 100644 --- a/plugin/builtin/patchtransformer/PatchTransformer_test.go +++ b/plugin/builtin/patchtransformer/PatchTransformer_test.go @@ -508,6 +508,82 @@ spec: `) } +func TestPatchTransformerWithInlineYamlRegexTarget(t *testing.T) { + th := kusttest_test.MakeEnhancedHarness(t). + PrepBuiltin("PatchTransformer") + defer th.Reset() + + th.RunTransformerAndCheckResult(` +apiVersion: builtin +kind: PatchTransformer +metadata: + name: notImportantHere +target: + name: .*Deploy + kind: Deployment|MyKind + group: \w{4} + version: v\d +patch: |- + apiVersion: apps/v1 + metadata: + name: myDeploy + kind: Deployment + spec: + replica: 77 +`, someDeploymentResources, ` +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + old-label: old-value + name: myDeploy +spec: + replica: 77 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - image: nginx + name: nginx +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + new-label: new-value + name: yourDeploy +spec: + replica: 77 + template: + metadata: + labels: + new-label: new-value + spec: + containers: + - image: nginx:1.7.9 + name: nginx +--- +apiVersion: apps/v1 +kind: MyKind +metadata: + label: + old-label: old-value + name: myDeploy +spec: + replica: 77 + template: + metadata: + labels: + old-label: old-value + spec: + containers: + - image: nginx + name: nginx +`) +} + func TestPatchTransformerWithPatchDelete(t *testing.T) { th := kusttest_test.MakeEnhancedHarness(t). PrepBuiltin("PatchTransformer")