Repository: operator-framework/operator-sdk
Pull request #5100 (2021-07-29 22:44:33)

docs: Fix link to the envtest documentation in the v1.0.0 migration guide
Update the v1.0.0.md migration guide and ensure the link to the envtest
documentation is correctly formatted.

<!--

Welcome to the Operator SDK! Before contributing, make sure to:

- Read the contributing guidelines https://github.com/operator-framework/operator-sdk/blob/master/CONTRIBUTING.MD
- Rebase your branch on the latest upstream master
- Link any relevant issues, PR's, or documentation
- When fixing an issue, add "Closes #<ISSUE_NUMBER>"
- Follow the below checklist if making a user-facing change 

-->

**Description of the change:**


**Motivation for the change:**


**Checklist**

If the pull request includes user-facing changes, extra documentation is required:
- [ ] Add a new changelog fragment in `changelog/fragments` (see [`changelog/fragments/00-template.yaml`](https://github.com/operator-framework/operator-sdk/tree/master/changelog/fragments/00-template.yaml))
- [ ] Add or update relevant sections of the docs website in [`website/content/en/docs`](https://github.com/operator-framework/operator-sdk/tree/master/website/content/en/docs)


timflannagan: That looks like it can be ignored:

```
  *  External link https://v1-10-x.sdk.operatorframework.io/ failed: response code 0 means something's wrong.
             It's possible libcurl couldn't connect to the server or perhaps the request timed out.
             Sometimes, making too many requests at once also breaks things.
             Either way, the return message (if any) from the server is: Couldn't resolve host name
```

Patch:
diff --git a/changelog/fragments/helm-labelselector.yaml b/changelog/fragments/helm-labelselector.yaml
new file mode 100644
index 00000000..1e2d1049
--- /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 ef367713..e909d27c 100644
--- a/internal/cmd/helm-operator/run/cmd.go
+++ b/internal/cmd/helm-operator/run/cmd.go
@@ -202,6 +202,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 3870fa78..7b4e44ba 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/watches/watches.go b/internal/helm/watches/watches.go
index c02413ab..8aa0e650 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 de423dfd..f8447b68 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/
diff --git a/website/content/en/docs/faqs/_index.md b/website/content/en/docs/faqs/_index.md
index 6f197a6d..bfdc384c 100644
--- a/website/content/en/docs/faqs/_index.md
+++ b/website/content/en/docs/faqs/_index.md
@@ -20,7 +20,7 @@ For further context about the relationship between Kubebuilder and Operator SDK,
 ## Can I use the Kubebuilder docs?
 
 Yes, you can use [https://book.kubebuilder.io/](https://book.kubebuilder.io/). Just keep in mind that when you see an instruction such as:
-`$ kubebuilder <command>` you will use `$ operator-sdk <command>`. 
+`$ kubebuilder <command>` you will use `$ operator-sdk <command>`.
 
 ## Controller Runtime FAQ
 
@@ -154,5 +154,5 @@ SHELL := /bin/bash
 [owner-references-permission-enforcement]: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement
 [rbac-markers]: https://book.kubebuilder.io/reference/markers/rbac.html
 [rbac]:https://kubernetes.io/docs/reference/access-authn-authz/rbac/
-[scorecard-doc]: https://sdk.operatorframework.io/docs/advanced-topics/scorecard/
+[scorecard-doc]: https://sdk.operatorframework.io/docs/testing-operators/scorecard/
 [project-doc]: /docs/overview/project-layout
diff --git a/website/content/en/docs/upgrading-sdk-version/v1.0.0.md b/website/content/en/docs/upgrading-sdk-version/v1.0.0.md
index ad32facb..fecefa13 100644
--- a/website/content/en/docs/upgrading-sdk-version/v1.0.0.md
+++ b/website/content/en/docs/upgrading-sdk-version/v1.0.0.md
@@ -31,7 +31,7 @@ The following subcommands were removed:
 | `operator-sdk migrate`        | Removed support for hybrid operators, no migration                                                       | [#3385](https://github.com/operator-framework/operator-sdk/pull/3385)
 | `operator-sdk print-deps`     | Removed, no migration                                                                                    | [#3385](https://github.com/operator-framework/operator-sdk/pull/3385)
 | `operator-sdk run local`      | Use `make run`                                                                                           | [#3406](https://github.com/operator-framework/operator-sdk/pull/3406)
-| `operator-sdk test`           | Use controller-runtime's [envtest]https://book.kubebuilder.io/reference/envtest.html) framework          | [#3409](https://github.com/operator-framework/operator-sdk/pull/3409)
+| `operator-sdk test`           | Use controller-runtime's [envtest](https://book.kubebuilder.io/reference/envtest.html) framework          | [#3409](https://github.com/operator-framework/operator-sdk/pull/3409)
 
 ### Library changes
 


Test patch:
diff --git a/internal/helm/controller/controller_test.go b/internal/helm/controller/controller_test.go
new file mode 100644
index 00000000..968fdef1
--- /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")
+}