diff --git a/e2e/testdata/fn-eval/selectors/exclude/.expected/config.yaml b/e2e/testdata/fn-eval/selectors/exclude/.expected/config.yaml new file mode 100644 index 0000000000..8afcfeb8d7 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/exclude/.expected/config.yaml @@ -0,0 +1,17 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +stdErr: | + [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.1.3" on 1 resource(s) + [PASS] "gcr.io/kpt-fn/set-namespace:v0.1.3" in 0s diff --git a/e2e/testdata/fn-eval/selectors/exclude/.expected/diff.patch b/e2e/testdata/fn-eval/selectors/exclude/.expected/diff.patch new file mode 100644 index 0000000000..dbb4ba4919 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/exclude/.expected/diff.patch @@ -0,0 +1,12 @@ +diff --git a/resources.yaml b/resources.yaml +index 7a494c9..9d82bd6 100644 +--- a/resources.yaml ++++ b/resources.yaml +@@ -15,6 +15,7 @@ apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment ++ namespace: staging + spec: + replicas: 3 + --- diff --git a/e2e/testdata/fn-eval/selectors/exclude/.expected/exec.sh b/e2e/testdata/fn-eval/selectors/exclude/.expected/exec.sh new file mode 100644 index 0000000000..2abe8a1826 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/exclude/.expected/exec.sh @@ -0,0 +1,18 @@ +#! /bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +kpt fn eval -i set-namespace:v0.1.3 --exclude-kind Custom -- namespace=staging diff --git a/e2e/testdata/fn-eval/selectors/exclude/.krmignore b/e2e/testdata/fn-eval/selectors/exclude/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/exclude/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/selectors/exclude/resources.yaml b/e2e/testdata/fn-eval/selectors/exclude/resources.yaml new file mode 100644 index 0000000000..7a494c9ca7 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/exclude/resources.yaml @@ -0,0 +1,26 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/config.yaml b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/config.yaml new file mode 100644 index 0000000000..8afcfeb8d7 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/config.yaml @@ -0,0 +1,17 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +stdErr: | + [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.1.3" on 1 resource(s) + [PASS] "gcr.io/kpt-fn/set-namespace:v0.1.3" in 0s diff --git a/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/diff.patch b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/diff.patch new file mode 100644 index 0000000000..0851ad2267 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/diff.patch @@ -0,0 +1,12 @@ +diff --git a/resources.yaml b/resources.yaml +index 98394a0..163aba6 100644 +--- a/resources.yaml ++++ b/resources.yaml +@@ -27,6 +27,7 @@ metadata: + labels: + foo: bar + foo-1: bar ++ namespace: staging + --- + apiVersion: custom.io/v1 + kind: Custom \ No newline at end of file diff --git a/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/exec.sh b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/exec.sh new file mode 100644 index 0000000000..f2e222c8ce --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.expected/exec.sh @@ -0,0 +1,18 @@ +#! /bin/bash +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eo pipefail + +kpt fn eval -i set-namespace:v0.1.3 --match-labels foo=bar --match-labels foo-1=bar --exclude-annotations foo=bar -- namespace=staging diff --git a/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.krmignore b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-eval/selectors/selectors-with-exclude/resources.yaml b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/resources.yaml new file mode 100644 index 0000000000..98394a03a7 --- /dev/null +++ b/e2e/testdata/fn-eval/selectors/selectors-with-exclude/resources.yaml @@ -0,0 +1,41 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + foo: bar +spec: + replicas: 3 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: nginx-deployment + labels: + foo: bar + foo-1: bar +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom + annotations: + foo: bar + labels: + foo: bar + foo-1: bar +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/selectors/exclude/.expected/config.yaml b/e2e/testdata/fn-render/selectors/exclude/.expected/config.yaml new file mode 100644 index 0000000000..661052dce8 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/exclude/.expected/config.yaml @@ -0,0 +1,22 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +stdErr: | + Package "exclude": + [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.1.3" on 1 resource(s) + [PASS] "gcr.io/kpt-fn/set-namespace:v0.1.3" in 0s + [RUNNING] "gcr.io/kpt-fn/set-labels:v0.1.4" on 2 resource(s) + [PASS] "gcr.io/kpt-fn/set-labels:v0.1.4" in 0s + + Successfully executed 2 function(s) in 1 package(s). diff --git a/e2e/testdata/fn-render/selectors/exclude/.expected/diff.patch b/e2e/testdata/fn-render/selectors/exclude/.expected/diff.patch new file mode 100644 index 0000000000..0c359ecf8e --- /dev/null +++ b/e2e/testdata/fn-render/selectors/exclude/.expected/diff.patch @@ -0,0 +1,29 @@ +diff --git a/resources.yaml b/resources.yaml +index 7a494c9..1d36135 100644 +--- a/resources.yaml ++++ b/resources.yaml +@@ -15,12 +15,24 @@ apiVersion: apps/v1 + kind: Deployment + metadata: + name: nginx-deployment ++ namespace: staging ++ labels: ++ tier: backend + spec: + replicas: 3 ++ selector: ++ matchLabels: ++ tier: backend ++ template: ++ metadata: ++ labels: ++ tier: backend + --- + apiVersion: custom.io/v1 + kind: Custom + metadata: + name: custom ++ labels: ++ tier: backend + spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/selectors/exclude/.krmignore b/e2e/testdata/fn-render/selectors/exclude/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/exclude/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/selectors/exclude/Kptfile b/e2e/testdata/fn-render/selectors/exclude/Kptfile new file mode 100644 index 0000000000..41ab79f206 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/exclude/Kptfile @@ -0,0 +1,17 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging + selectors: + - name: nginx-deployment + kind: Deployment + - image: gcr.io/kpt-fn/set-labels:v0.1.4 + configMap: + tier: backend + exclude: + - kind: Kptfile diff --git a/e2e/testdata/fn-render/selectors/exclude/resources.yaml b/e2e/testdata/fn-render/selectors/exclude/resources.yaml new file mode 100644 index 0000000000..7a494c9ca7 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/exclude/resources.yaml @@ -0,0 +1,26 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom +spec: + image: nginx:1.2.3 diff --git a/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/config.yaml b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/config.yaml new file mode 100644 index 0000000000..b631b8bb4a --- /dev/null +++ b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/config.yaml @@ -0,0 +1,22 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +stdErr: | + Package "selectors-with-exclude": + [RUNNING] "gcr.io/kpt-fn/set-namespace:v0.1.3" on 1 resource(s) + [PASS] "gcr.io/kpt-fn/set-namespace:v0.1.3" in 0s + [RUNNING] "gcr.io/kpt-fn/set-labels:v0.1.4" + [PASS] "gcr.io/kpt-fn/set-labels:v0.1.4" in 0s + + Successfully executed 2 function(s) in 1 package(s). diff --git a/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/diff.patch b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/diff.patch new file mode 100644 index 0000000000..94eb7abe4d --- /dev/null +++ b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.expected/diff.patch @@ -0,0 +1,42 @@ +diff --git a/Kptfile b/Kptfile +index a47bd49..a402119 100644 +--- a/Kptfile ++++ b/Kptfile +@@ -2,6 +2,8 @@ apiVersion: kpt.dev/v1 + kind: Kptfile + metadata: + name: app ++ labels: ++ tier: backend + pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 +diff --git a/resources.yaml b/resources.yaml +index e7eda6a..f50b306 100644 +--- a/resources.yaml ++++ b/resources.yaml +@@ -17,8 +17,17 @@ metadata: + name: nginx-deployment + labels: + foo: bar ++ tier: backend ++ namespace: staging + spec: + replicas: 3 ++ selector: ++ matchLabels: ++ tier: backend ++ template: ++ metadata: ++ labels: ++ tier: backend + --- + apiVersion: custom.io/v1 + kind: Custom +@@ -26,5 +35,6 @@ metadata: + name: custom + labels: + foo: bar ++ tier: backend + spec: + image: nginx:1.2.3 \ No newline at end of file diff --git a/e2e/testdata/fn-render/selectors/selectors-with-exclude/.krmignore b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.krmignore new file mode 100644 index 0000000000..9d7a4007d6 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/selectors-with-exclude/.krmignore @@ -0,0 +1 @@ +.expected diff --git a/e2e/testdata/fn-render/selectors/selectors-with-exclude/Kptfile b/e2e/testdata/fn-render/selectors/selectors-with-exclude/Kptfile new file mode 100644 index 0000000000..a47bd49868 --- /dev/null +++ b/e2e/testdata/fn-render/selectors/selectors-with-exclude/Kptfile @@ -0,0 +1,17 @@ +apiVersion: kpt.dev/v1 +kind: Kptfile +metadata: + name: app +pipeline: + mutators: + - image: gcr.io/kpt-fn/set-namespace:v0.1.3 + configMap: + namespace: staging + selectors: + - labels: + foo: bar + exclude: + - kind: Custom + - image: gcr.io/kpt-fn/set-labels:v0.1.4 + configMap: + tier: backend diff --git a/e2e/testdata/fn-render/selectors/selectors-with-exclude/resources.yaml b/e2e/testdata/fn-render/selectors/selectors-with-exclude/resources.yaml new file mode 100644 index 0000000000..e7eda6a0ab --- /dev/null +++ b/e2e/testdata/fn-render/selectors/selectors-with-exclude/resources.yaml @@ -0,0 +1,30 @@ +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-deployment + labels: + foo: bar +spec: + replicas: 3 +--- +apiVersion: custom.io/v1 +kind: Custom +metadata: + name: custom + labels: + foo: bar +spec: + image: nginx:1.2.3 diff --git a/internal/fnruntime/utils.go b/internal/fnruntime/utils.go index e62874a2c3..acfe6d658b 100644 --- a/internal/fnruntime/utils.go +++ b/internal/fnruntime/utils.go @@ -140,17 +140,34 @@ type SelectionContext struct { } // SelectInput returns the selected resources based on criteria in selectors -func SelectInput(input []*yaml.RNode, selectors []kptfilev1.Selector, _ *SelectionContext) ([]*yaml.RNode, error) { +func SelectInput(input []*yaml.RNode, selectors, exclusions []kptfilev1.Selector, _ *SelectionContext) ([]*yaml.RNode, error) { + var selectedInput []*yaml.RNode if len(selectors) == 0 { - return input, nil + selectedInput = input + } else { + for _, node := range input { + for _, selector := range selectors { + if isMatch(node, selector) { + selectedInput = append(selectedInput, node) + } + } + } + } + if len(exclusions) == 0 { + return selectedInput, nil } var filteredInput []*yaml.RNode - for _, node := range input { - for _, selector := range selectors { - if isMatch(node, selector) { - filteredInput = append(filteredInput, node) + for _, node := range selectedInput { + matchesExclusion := false + for _, exclusion := range exclusions { + if !exclusion.IsEmpty() && isMatch(node, exclusion) { + matchesExclusion = true + break } } + if !matchesExclusion { + filteredInput = append(filteredInput, node) + } } return filteredInput, nil } @@ -159,7 +176,8 @@ func SelectInput(input []*yaml.RNode, selectors []kptfilev1.Selector, _ *Selecti func isMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { // keep expanding with new selectors return nameMatch(node, selector) && namespaceMatch(node, selector) && - kindMatch(node, selector) && apiVersionMatch(node, selector) + kindMatch(node, selector) && apiVersionMatch(node, selector) && + labelMatch(node, selector) && annoMatch(node, selector) } // nameMatch returns true if the resource name matches input selection criteria @@ -181,3 +199,25 @@ func kindMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { func apiVersionMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { return selector.APIVersion == "" || selector.APIVersion == node.GetApiVersion() } + +// labelMatch returns true if the resource labels match input selection criteria +func labelMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { + nodeLabels := node.GetLabels() + for sk, sv := range selector.Labels { + if nv, found := nodeLabels[sk]; !found || sv != nv { + return false + } + } + return true +} + +// annoMatch returns true if the resource annotations match input selection criteria +func annoMatch(node *yaml.RNode, selector kptfilev1.Selector) bool { + nodeAnnos := node.GetAnnotations() + for sk, sv := range selector.Annotations { + if nv, found := nodeAnnos[sk]; !found || sv != nv { + return false + } + } + return true +} diff --git a/internal/util/render/executor.go b/internal/util/render/executor.go index 63df023d65..c443e0cc62 100644 --- a/internal/util/render/executor.go +++ b/internal/util/render/executor.go @@ -406,8 +406,9 @@ func (pn *pkgNode) runMutators(ctx context.Context, hctx *hydrationContext, inpu for i, mutator := range mutators { selectors := pl.Mutators[i].Selectors + exclusions := pl.Mutators[i].Exclusions - if len(selectors) > 0 { + if len(selectors) > 0 || len(exclusions) > 0 { // set kpt-resource-id annotation on each resource before mutation err = fnruntime.SetResourceIds(input) if err != nil { @@ -415,7 +416,7 @@ func (pn *pkgNode) runMutators(ctx context.Context, hctx *hydrationContext, inpu } } // select the resources on which function should be applied - selectedInput, err := fnruntime.SelectInput(input, selectors, &fnruntime.SelectionContext{RootPackagePath: hctx.root.pkg.UniquePath}) + selectedInput, err := fnruntime.SelectInput(input, selectors, exclusions, &fnruntime.SelectionContext{RootPackagePath: hctx.root.pkg.UniquePath}) if err != nil { return nil, err } @@ -434,7 +435,7 @@ func (pn *pkgNode) runMutators(ctx context.Context, hctx *hydrationContext, inpu } hctx.executedFunctionCnt += 1 - if len(selectors) > 0 { + if len(selectors) > 0 || len(exclusions) > 0 { // merge the output resources with input resources input = fnruntime.MergeWithInput(output.Nodes, selectedInput, input) // delete the kpt-resource-id annotation on each resource @@ -467,13 +468,13 @@ func (pn *pkgNode) runValidators(ctx context.Context, hctx *hydrationContext, in function := pl.Validators[i] // validators are run on a copy of mutated resources to ensure // resources are not mutated. - selectedResources, err := fnruntime.SelectInput(input, function.Selectors, &fnruntime.SelectionContext{RootPackagePath: hctx.root.pkg.UniquePath}) + selectedResources, err := fnruntime.SelectInput(input, function.Selectors, function.Exclusions, &fnruntime.SelectionContext{RootPackagePath: hctx.root.pkg.UniquePath}) if err != nil { return err } var validator kio.Filter displayResourceCount := false - if len(function.Selectors) > 0 { + if len(function.Selectors) > 0 || len(function.Exclusions) > 0 { displayResourceCount = true } if function.Exec != "" && !hctx.allowExec { @@ -590,7 +591,7 @@ func fnChain(ctx context.Context, hctx *hydrationContext, pkgPath types.UniquePa var runner kio.Filter function := fns[i] displayResourceCount := false - if len(function.Selectors) > 0 { + if len(function.Selectors) > 0 || len(function.Exclusions) > 0 { displayResourceCount = true } if function.Exec != "" && !hctx.allowExec { diff --git a/pkg/api/kptfile/v1/types.go b/pkg/api/kptfile/v1/types.go index f4cd5db0a0..a21028b232 100644 --- a/pkg/api/kptfile/v1/types.go +++ b/pkg/api/kptfile/v1/types.go @@ -291,6 +291,10 @@ type Function struct { // `Selectors` are used to specify resources on which the function should be executed // if not specified, all resources are selected Selectors []Selector `yaml:"selectors,omitempty" json:"selectors,omitempty"` + + // `Exclude` are used to specify resources on which the function should NOT be executed. + // If not specified, all resources selected by `Selectors` are selected. + Exclusions []Selector `yaml:"exclude,omitempty" json:"exclude,omitempty"` } // Selector specifies the selection criteria @@ -304,6 +308,10 @@ type Selector struct { Name string `yaml:"name,omitempty" json:"name,omitempty"` // Namespace of the target resources Namespace string `yaml:"namespace,omitempty" json:"namespace,omitempty"` + // Labels on the target resources + Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` + // Annotations on the target resources + Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"` } // IsEmpty returns true of none of the selection criteria is specified @@ -311,7 +319,9 @@ func (s Selector) IsEmpty() bool { return s.APIVersion == "" && s.Namespace == "" && s.Name == "" && - s.Kind == "" + s.Kind == "" && + len(s.Labels) == 0 && + len(s.Annotations) == 0 } // Inventory encapsulates the parameters for the inventory resource applied to a cluster. diff --git a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go index 94d67584ad..1325a3c3eb 100644 --- a/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go +++ b/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go @@ -82,6 +82,8 @@ func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner { &r.AsCurrentUser, "as-current-user", false, "use the uid and gid that kpt is running with to run the function in the container") r.Command.Flags().StringVar(&r.ImagePullPolicy, "image-pull-policy", string(fnruntime.IfNotPresentPull), fmt.Sprintf("pull image before running the container. It must be one of %s, %s and %s.", fnruntime.AlwaysPull, fnruntime.IfNotPresentPull, fnruntime.NeverPull)) + + // selector flags r.Command.Flags().StringVar( &r.Selector.APIVersion, "match-api-version", "", "select resources matching the given apiVersion") r.Command.Flags().StringVar( @@ -90,6 +92,25 @@ func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner { &r.Selector.Name, "match-name", "", "select resources matching the given name") r.Command.Flags().StringVar( &r.Selector.Namespace, "match-namespace", "", "select resources matching the given namespace") + r.Command.Flags().StringArrayVar( + &r.selectorAnnotations, "match-annotations", []string{}, "select resources matching the given annotations") + r.Command.Flags().StringArrayVar( + &r.selectorLabels, "match-labels", []string{}, "select resources matching the given labels") + + // exclusion flags + r.Command.Flags().StringVar( + &r.Exclusion.APIVersion, "exclude-api-version", "", "exclude resources matching the given apiVersion") + r.Command.Flags().StringVar( + &r.Exclusion.Kind, "exclude-kind", "", "exclude resources matching the given kind") + r.Command.Flags().StringVar( + &r.Exclusion.Name, "exclude-name", "", "exclude resources matching the given name") + r.Command.Flags().StringVar( + &r.Exclusion.Namespace, "exclude-namespace", "", "exclude resources matching the given namespace") + r.Command.Flags().StringArrayVar( + &r.excludeAnnotations, "exclude-annotations", []string{}, "exclude resources matching the given annotations") + r.Command.Flags().StringArrayVar( + &r.excludeLabels, "exclude-labels", []string{}, "exclude resources matching the given labels") + if err := r.Command.Flags().MarkHidden("include-meta-resources"); err != nil { panic(err) } @@ -123,7 +144,14 @@ type EvalFnRunner struct { IncludeMetaResources bool Ctx context.Context Selector kptfile.Selector + Exclusion kptfile.Selector dataItems []string + + // we will need to parse these values into Selector and Exclusion + selectorLabels []string + selectorAnnotations []string + excludeLabels []string + excludeAnnotations []string } func (r *EvalFnRunner) runE(c *cobra.Command, _ []string) error { @@ -153,6 +181,9 @@ func (r *EvalFnRunner) NewFunction() *kptfile.Function { if !r.Selector.IsEmpty() { newFn.Selectors = []kptfile.Selector{r.Selector} } + if !r.Exclusion.IsEmpty() { + newFn.Exclusions = []kptfile.Selector{r.Exclusion} + } if r.FnConfigPath != "" { _, relativePath, _ := pathutil.ResolveAbsAndRelPaths(r.FnConfigPath) newFn.ConfigPath = relativePath @@ -426,6 +457,7 @@ func (r *EvalFnRunner) preRunE(c *cobra.Command, args []string) error { } } + r.parseSelectors() r.RunFns = runfn.RunFns{ Ctx: r.Ctx, Function: fnSpec, @@ -446,7 +478,29 @@ func (r *EvalFnRunner) preRunE(c *cobra.Command, args []string) error { // are deleted. ContinueOnEmptyResult: true, Selector: r.Selector, + Exclusion: r.Exclusion, } return nil } + +// parses annotation and label based selectors and exclusion from the command line input +func (r *EvalFnRunner) parseSelectors() { + r.Selector.Annotations = parseSelectorMap(r.selectorAnnotations) + r.Selector.Labels = parseSelectorMap(r.selectorLabels) + r.Exclusion.Annotations = parseSelectorMap(r.excludeAnnotations) + r.Exclusion.Labels = parseSelectorMap(r.excludeLabels) +} + +func parseSelectorMap(selectors []string) map[string]string { + if len(selectors) == 0 { + return nil + } + result := make(map[string]string) + for _, s := range selectors { + parts := strings.Split(s, "=") + key, value := parts[0], parts[1] + result[key] = value + } + return result +} diff --git a/thirdparty/kyaml/runfn/runfn.go b/thirdparty/kyaml/runfn/runfn.go index 5b373ba75e..6ac7a7d25f 100644 --- a/thirdparty/kyaml/runfn/runfn.go +++ b/thirdparty/kyaml/runfn/runfn.go @@ -93,6 +93,8 @@ type RunFns struct { ImagePullPolicy fnruntime.ImagePullPolicy Selector kptfile.Selector + + Exclusion kptfile.Selector } // Execute runs the command @@ -189,7 +191,7 @@ func (r RunFns) runFunctions(input kio.Reader, output kio.Writer, fltrs []kio.Fi selectedInput := inputResources - if !r.Selector.IsEmpty() { + if !r.Selector.IsEmpty() || !r.Exclusion.IsEmpty() { err = fnruntime.SetResourceIds(inputResources) if err != nil { return err @@ -199,6 +201,7 @@ func (r RunFns) runFunctions(input kio.Reader, output kio.Writer, fltrs []kio.Fi selectedInput, err = fnruntime.SelectInput( inputResources, []kptfile.Selector{r.Selector}, + []kptfile.Selector{r.Exclusion}, &fnruntime.SelectionContext{RootPackagePath: r.uniquePath}) if err != nil { return err @@ -215,7 +218,7 @@ func (r RunFns) runFunctions(input kio.Reader, output kio.Writer, fltrs []kio.Fi err = pipeline.Execute() outputResources := pb.Nodes - if !r.Selector.IsEmpty() { + if !r.Selector.IsEmpty() || !r.Exclusion.IsEmpty() { outputResources = fnruntime.MergeWithInput(pb.Nodes, selectedInput, inputResources) deleteAnnoErr := fnruntime.DeleteResourceIds(outputResources) if deleteAnnoErr != nil { @@ -388,7 +391,7 @@ func (r *RunFns) defaultFnFilterProvider(spec runtimeutil.FunctionSpec, fnConfig } displayResourceCount := false - if !r.Selector.IsEmpty() { + if !r.Selector.IsEmpty() || !r.Exclusion.IsEmpty() { displayResourceCount = true } return fnruntime.NewFunctionRunner(r.Ctx, fltr, "", fnResult, r.fnResults, false, displayResourceCount)