From c04315bfa355e06407dbe3cbb5d58b289f59e2b0 Mon Sep 17 00:00:00 2001 From: Venkat Ramaraju Date: Fri, 18 Jun 2021 10:40:56 -0700 Subject: [PATCH] predicate for helm Signed-off-by: Venkat Ramaraju --- changelog/fragments/helm-labelselector.yaml | 7 ++++ internal/cmd/helm-operator/run/cmd.go | 1 + internal/helm/controller/controller.go | 31 ++++++++++++++- internal/helm/controller/controller_test.go | 39 +++++++++++++++++++ internal/helm/watches/watches.go | 8 ++-- .../helm/reference/watches.md | 7 +++- 6 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 changelog/fragments/helm-labelselector.yaml create mode 100644 internal/helm/controller/controller_test.go diff --git a/changelog/fragments/helm-labelselector.yaml b/changelog/fragments/helm-labelselector.yaml new file mode 100644 index 00000000000..1e2d1049a27 --- /dev/null +++ b/changelog/fragments/helm-labelselector.yaml @@ -0,0 +1,7 @@ +entries: + - description: > + Added a predicate for helm operators that filters resources based on selectors specified in watches.yaml. + Only the resources that contain the labels specified by selectors will be reconciled. + + kind: addition + breaking: false diff --git a/internal/cmd/helm-operator/run/cmd.go b/internal/cmd/helm-operator/run/cmd.go index 3eecfd48898..f72f121b13a 100644 --- a/internal/cmd/helm-operator/run/cmd.go +++ b/internal/cmd/helm-operator/run/cmd.go @@ -184,6 +184,7 @@ func run(cmd *cobra.Command, f *flags.Flags) { WatchDependentResources: *w.WatchDependentResources, OverrideValues: w.OverrideValues, MaxConcurrentReconciles: f.MaxConcurrentReconciles, + Selector: w.Selector, }) if err != nil { log.Error(err, "Failed to add manager factory to controller.") diff --git a/internal/helm/controller/controller.go b/internal/helm/controller/controller.go index a3f5a5d0467..4d963a9fbaa 100644 --- a/internal/helm/controller/controller.go +++ b/internal/helm/controller/controller.go @@ -16,6 +16,7 @@ package controller import ( "fmt" + "reflect" "strings" "sync" "time" @@ -30,6 +31,7 @@ import ( crthandler "sigs.k8s.io/controller-runtime/pkg/handler" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" + ctrlpredicate "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/yaml" @@ -51,6 +53,7 @@ type WatchOptions struct { WatchDependentResources bool OverrideValues map[string]string MaxConcurrentReconciles int + Selector metav1.LabelSelector } // Add creates a new helm operator controller and adds it to the manager @@ -80,7 +83,19 @@ func Add(mgr manager.Manager, options WatchOptions) error { o := &unstructured.Unstructured{} o.SetGroupVersionKind(options.GVK) - if err := c.Watch(&source.Kind{Type: o}, &libhandler.InstrumentedEnqueueRequestForObject{}); err != nil { + + var preds []ctrlpredicate.Predicate + p, err := parsePredicateSelector(options.Selector) + + if err != nil { + return err + } + + if p != nil { + preds = append(preds, p) + } + + if err := c.Watch(&source.Kind{Type: o}, &libhandler.InstrumentedEnqueueRequestForObject{}, preds...); err != nil { return err } @@ -93,6 +108,20 @@ func Add(mgr manager.Manager, options WatchOptions) error { return nil } +// parsePredicateSelector parses the selector in the WatchOptions and creates a predicate +// that is used to filter resources based on the specified selector +func parsePredicateSelector(selector metav1.LabelSelector) (ctrlpredicate.Predicate, error) { + // If a selector has been specified in watches.yaml, add it to the watch's predicates. + if !reflect.ValueOf(selector).IsZero() { + p, err := ctrlpredicate.LabelSelectorPredicate(selector) + if err != nil { + return nil, fmt.Errorf("error constructing predicate from watches selector: %v", err) + } + return p, nil + } + return nil, nil +} + // watchDependentResources adds a release hook function to the HelmOperatorReconciler // that adds watches for resources in released Helm charts. func watchDependentResources(mgr manager.Manager, r *HelmOperatorReconciler, c controller.Controller) { diff --git a/internal/helm/controller/controller_test.go b/internal/helm/controller/controller_test.go new file mode 100644 index 00000000000..968fdef1140 --- /dev/null +++ b/internal/helm/controller/controller_test.go @@ -0,0 +1,39 @@ +// Copyright 2021 The Operator-SDK Authors +// +// 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. + +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFilterPredicate(t *testing.T) { + matchLabelPass := make(map[string]string) + matchLabelPass["testKey"] = "testValue" + selectorPass := metav1.LabelSelector{ + MatchLabels: matchLabelPass, + } + noSelector := metav1.LabelSelector{} + + passPredicate, err := parsePredicateSelector(selectorPass) + assert.Equal(t, nil, err, "Verify that no error is thrown on a valid populated selector") + assert.NotEqual(t, nil, passPredicate, "Verify that a predicate is constructed using a valid selector") + + nilPredicate, err := parsePredicateSelector(noSelector) + assert.Equal(t, nil, err, "Verify that no error is thrown on a valid unpopulated selector") + assert.Equal(t, nil, nilPredicate, "Verify correct parsing of an unpopulated selector") +} diff --git a/internal/helm/watches/watches.go b/internal/helm/watches/watches.go index c02413abb32..8aa0e650832 100644 --- a/internal/helm/watches/watches.go +++ b/internal/helm/watches/watches.go @@ -22,6 +22,7 @@ import ( "os" "helm.sh/helm/v3/pkg/chartutil" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/yaml" ) @@ -32,9 +33,10 @@ const WatchesFile = "watches.yaml" // custom resource. type Watch struct { schema.GroupVersionKind `json:",inline"` - ChartDir string `json:"chart"` - WatchDependentResources *bool `json:"watchDependentResources,omitempty"` - OverrideValues map[string]string `json:"overrideValues,omitempty"` + ChartDir string `json:"chart"` + WatchDependentResources *bool `json:"watchDependentResources,omitempty"` + OverrideValues map[string]string `json:"overrideValues,omitempty"` + Selector metav1.LabelSelector `json:"selector"` } // UnmarshalYAML unmarshals an individual watch from the Helm watches.yaml file diff --git a/website/content/en/docs/building-operators/helm/reference/watches.md b/website/content/en/docs/building-operators/helm/reference/watches.md index de423dfd50d..f8447b684b3 100644 --- a/website/content/en/docs/building-operators/helm/reference/watches.md +++ b/website/content/en/docs/building-operators/helm/reference/watches.md @@ -19,6 +19,7 @@ The follow tables describes the fields in an entry in `watches.yaml`: | chart | The path to the helm chart to use when reconciling this GVK. | | watchDependentResources | Enable watching resources that are created by helm (default: `true`). | | overrideValues | Values to be used for overriding Helm chart's defaults. For additional information see the [reference doc][override-values]. | +| selector | The conditions that a resource's labels must satsify in order to get reconciled. For additional information see [labels and selectors documentation][label-selector-doc]. | For reference, here is an example of a simple `watches.yaml` file: @@ -31,7 +32,11 @@ For reference, here is an example of a simple `watches.yaml` file: chart: helm-charts/foo overrideValues: image.repository: quay.io/mycustomrepo - watchDependentResources: false + watchDependentResources: false + selector: + matchExpressions: + - {key: testLabel, operator: Exists, values: []} ``` [override-values]: /docs/building-operators/helm/reference/advanced_features/override_values/ +[label-selector-doc]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/