Repository: operator-framework/operator-sdk
Pull request #5581 (2022-03-03 15:51:50)

add darwin-arm64 build to goreleaser config
Signed-off-by: Joe Lanford <joe.lanford@gmail.com>

**Description of the change:**
Add binary builds for `darwin/arm64`

**Motivation for the change:**
I have a Mac M1 machine :)

**Checklist**

If the pull request includes user-facing changes, extra documentation is required:
- [x] 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))
- [x] 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)



Patch:
diff --git a/.goreleaser.yml b/.goreleaser.yml
index f9944da9..f4ad9652 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -30,6 +30,7 @@ builds:
       - -X {{ .Env.REPO }}/internal/version.ImageVersion={{ .Env.IMAGE_VERSION }}
     targets: &build-targets
       - darwin_amd64
+      - darwin_arm64
       - linux_amd64
       - linux_arm64
       - linux_ppc64le
diff --git a/Makefile b/Makefile
index 404f3c57..55581aa3 100644
--- a/Makefile
+++ b/Makefile
@@ -9,9 +9,9 @@ export IMAGE_VERSION = v1.18.0
 export SIMPLE_VERSION = $(shell (test "$(shell git describe)" = "$(shell git describe --abbrev=0)" && echo $(shell git describe)) || echo $(shell git describe --abbrev=0)+git)
 export GIT_VERSION = $(shell git describe --dirty --tags --always)
 export GIT_COMMIT = $(shell git rev-parse HEAD)
-export K8S_VERSION = 1.21
+export K8S_VERSION = 1.23
 # TODO: bump this to 1.21, after kubectl `--generator` flag is removed from e2e tests.
-export ENVTEST_K8S_VERSION = 1.21.1
+export ENVTEST_K8S_VERSION = 1.23.1
 
 # Build settings
 export TOOLS_DIR = tools/bin
diff --git a/changelog/fragments/01-bump-java-operator-plugins.yaml b/changelog/fragments/01-bump-java-operator-plugins.yaml
new file mode 100644
index 00000000..41ce880f
--- /dev/null
+++ b/changelog/fragments/01-bump-java-operator-plugins.yaml
@@ -0,0 +1,20 @@
+# entries is a list of entries to include in
+# release notes and/or the migration guide
+entries:
+  - description: >
+      (java/v1alpha) bumping the java-operator-plugins dependency to v0.3.0.
+      This release includes the following items.
+
+      ### Bug Fixes
+      - chore: update to JOSDK extension 3.0.4 and Quarkus 2.7.3 (Fixes issue 74)
+
+    # kind is one of:
+    # - addition
+    # - change
+    # - deprecation
+    # - removal
+    # - bugfix
+    kind: "bugfix"
+
+    # Is this a breaking change?
+    breaking: false
diff --git a/changelog/fragments/darwin-arm64.yaml b/changelog/fragments/darwin-arm64.yaml
new file mode 100644
index 00000000..42e197e7
--- /dev/null
+++ b/changelog/fragments/darwin-arm64.yaml
@@ -0,0 +1,5 @@
+entries:
+  - description: >
+      Add official binary builds for `darwin/arm64`
+    kind: addition
+    breaking: false
diff --git a/changelog/fragments/helm-annotations-reconcile-period.yaml b/changelog/fragments/helm-annotations-reconcile-period.yaml
new file mode 100644
index 00000000..cf581abe
--- /dev/null
+++ b/changelog/fragments/helm-annotations-reconcile-period.yaml
@@ -0,0 +1,11 @@
+entries:
+  - description: >
+      For the helm/v1 plugin, parsed the "helm.sdk.operatorframework.io/reconcile-period" 
+      value from the custom resource annotations for helm operators. This value is then 
+      set to the 'ReconcilePeriod' field of the reconciler to reconcile the cluster
+      in the specified intervals of time.
+
+    kind: "addition"
+
+    # Is this a breaking change?
+    breaking: false
diff --git a/go.mod b/go.mod
index 771ca723..84fd14ba 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/onsi/gomega v1.17.0
 	github.com/operator-framework/api v0.14.0
 	github.com/operator-framework/helm-operator-plugins v0.0.9
-	github.com/operator-framework/java-operator-plugins v0.2.0
+	github.com/operator-framework/java-operator-plugins v0.3.0
 	github.com/operator-framework/operator-lib v0.10.0
 	github.com/operator-framework/operator-registry v1.19.5
 	github.com/prometheus/client_golang v1.11.0
diff --git a/go.sum b/go.sum
index bc2070d2..1f66344d 100644
--- a/go.sum
+++ b/go.sum
@@ -932,8 +932,8 @@ github.com/operator-framework/api v0.14.0 h1:5nk8fQL8l+dDxi11hZi0T7nqhhoIQLn+qL2
 github.com/operator-framework/api v0.14.0/go.mod h1:r/erkmp9Kc1Al4dnxmRkJYc0uCtD5FohN9VuJ5nTxz0=
 github.com/operator-framework/helm-operator-plugins v0.0.9 h1:G5aBY5sPrNXcRiKLpAaBMOYm7q0+qCmk9XWOAL/ZJuc=
 github.com/operator-framework/helm-operator-plugins v0.0.9/go.mod h1:LFi/PUYE+xA5ubF3ZrleYE1Ez7cphJzSYpjHAyLiNwQ=
-github.com/operator-framework/java-operator-plugins v0.2.0 h1:nIc3/pmH9j9lA6IzcnBBOl1D1V7XFculETUJrucOcrk=
-github.com/operator-framework/java-operator-plugins v0.2.0/go.mod h1:38ZUfA6J5JQjPZLQJZKyXE3v+t91mpJ2utAtVCDQTY0=
+github.com/operator-framework/java-operator-plugins v0.3.0 h1:K+gdg1cLugxP3KbGNc1SttKIY69z7ywBFaXW0vVIG9o=
+github.com/operator-framework/java-operator-plugins v0.3.0/go.mod h1:38ZUfA6J5JQjPZLQJZKyXE3v+t91mpJ2utAtVCDQTY0=
 github.com/operator-framework/operator-lib v0.3.0/go.mod h1:LTp5UQd8ivq4MXqm/W/XHulHQ0RRoZXsAj73sNMAQxc=
 github.com/operator-framework/operator-lib v0.10.0 h1:tTjrt8Udi0msABkMpgxKHp7sXKnC73jFPO5Col0tWso=
 github.com/operator-framework/operator-lib v0.10.0/go.mod h1:sdCls/olFjSHLXU0bHlaPtmyeIdentoxz/9miyw27kw=
diff --git a/hack/generate/samples/internal/ansible/constants.go b/hack/generate/samples/internal/ansible/constants.go
index 6507aaa3..1a3b463d 100644
--- a/hack/generate/samples/internal/ansible/constants.go
+++ b/hack/generate/samples/internal/ansible/constants.go
@@ -560,3 +560,21 @@ const customMetricsTest = `
       - "'histogram_test_sum 2' in metrics_output.stdout"
 
 `
+
+const watchNamespacePatch = `---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: controller-manager
+  namespace: system
+spec:
+  template:
+    spec:
+      containers:
+      - name: manager
+        env:
+        - name: WATCH_NAMESPACE
+          valueFrom:
+            fieldRef:
+              fieldPath: metadata.namespace
+`
diff --git a/hack/generate/samples/internal/ansible/memcached_molecule.go b/hack/generate/samples/internal/ansible/memcached_molecule.go
index 37caa9ad..4c5b3e47 100644
--- a/hack/generate/samples/internal/ansible/memcached_molecule.go
+++ b/hack/generate/samples/internal/ansible/memcached_molecule.go
@@ -16,6 +16,7 @@ package ansible
 
 import (
 	"fmt"
+	"io/ioutil"
 	"os/exec"
 	"path/filepath"
 	"strings"
@@ -187,6 +188,17 @@ func (ma *MemcachedMolecule) Run() {
 		"role: secret", manageStatusFalseForRoleSecret)
 	pkg.CheckError("replacing in watches.yaml", err)
 
+	// prevent high load of controller caused by watching all the secrets in the cluster
+	watchNamespacePatchFileName := "watch_namespace_patch.yaml"
+	log.Info("adding WATCH_NAMESPACE env patch to watch own namespace")
+	err = ioutil.WriteFile(filepath.Join(ma.ctx.Dir, "config", "testing", watchNamespacePatchFileName), []byte(watchNamespacePatch), 0644)
+	pkg.CheckError("adding watch_namespace_patch.yaml", err)
+
+	log.Info("adding WATCH_NAMESPACE env patch to patch list to be applied")
+	err = kbutil.InsertCode(filepath.Join(ma.ctx.Dir, "config", "testing", "kustomization.yaml"), "patchesStrategicMerge:",
+		fmt.Sprintf("\n- %s", watchNamespacePatchFileName))
+	pkg.CheckError("inserting in kustomization.yaml", err)
+
 	log.Infof("removing FIXME asserts from memfin_test.yml")
 	err = kbutil.ReplaceInFile(filepath.Join(ma.ctx.Dir, "molecule", "default", "tasks", "memfin_test.yml"),
 		fixmeAssert, "")
diff --git a/images/ansible-operator-2.11-preview/Pipfile.lock b/images/ansible-operator-2.11-preview/Pipfile.lock
index c9fe3af4..f699e7d6 100644
--- a/images/ansible-operator-2.11-preview/Pipfile.lock
+++ b/images/ansible-operator-2.11-preview/Pipfile.lock
@@ -260,7 +260,7 @@
                 "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==3.1.1"
+            "version": "==3.2.0"
         },
         "openshift": {
             "hashes": [
@@ -449,7 +449,7 @@
                 "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
                 "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
             ],
-            "version": "==1.3.0"
+            "version": "==1.3.1"
         },
         "requests-unixsocket": {
             "hashes": [
diff --git a/images/ansible-operator/Pipfile.lock b/images/ansible-operator/Pipfile.lock
index b7beaeb0..b4bf4cab 100644
--- a/images/ansible-operator/Pipfile.lock
+++ b/images/ansible-operator/Pipfile.lock
@@ -260,7 +260,7 @@
                 "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"
             ],
             "markers": "python_version >= '3.6'",
-            "version": "==3.1.1"
+            "version": "==3.2.0"
         },
         "openshift": {
             "hashes": [
@@ -399,7 +399,7 @@
                 "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a",
                 "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"
             ],
-            "version": "==1.3.0"
+            "version": "==1.3.1"
         },
         "requests-unixsocket": {
             "hashes": [
diff --git a/internal/cmd/helm-operator/run/cmd.go b/internal/cmd/helm-operator/run/cmd.go
index e909d27c..d6deb6d6 100644
--- a/internal/cmd/helm-operator/run/cmd.go
+++ b/internal/cmd/helm-operator/run/cmd.go
@@ -21,6 +21,7 @@ import (
 	"os"
 	"runtime"
 	"strings"
+	"time"
 
 	"github.com/spf13/cobra"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -194,11 +195,16 @@ func run(cmd *cobra.Command, f *flags.Flags) {
 	}
 	for _, w := range ws {
 		// Register the controller with the factory.
+		reconcilePeriod := f.ReconcilePeriod
+		if w.ReconcilePeriod.Duration != time.Duration(0) {
+			reconcilePeriod = w.ReconcilePeriod.Duration
+		}
+
 		err := controller.Add(mgr, controller.WatchOptions{
 			Namespace:               namespace,
 			GVK:                     w.GroupVersionKind,
 			ManagerFactory:          release.NewManagerFactory(mgr, w.ChartDir),
-			ReconcilePeriod:         f.ReconcilePeriod,
+			ReconcilePeriod:         reconcilePeriod,
 			WatchDependentResources: *w.WatchDependentResources,
 			OverrideValues:          w.OverrideValues,
 			MaxConcurrentReconciles: f.MaxConcurrentReconciles,
diff --git a/internal/helm/controller/reconcile.go b/internal/helm/controller/reconcile.go
index 74f7e2a7..53c14e9d 100644
--- a/internal/helm/controller/reconcile.go
+++ b/internal/helm/controller/reconcile.go
@@ -61,8 +61,9 @@ const (
 	// Deprecated: use uninstallFinalizer. This will be removed in operator-sdk v2.0.0.
 	uninstallFinalizerLegacy = "uninstall-helm-release"
 
-	helmUpgradeForceAnnotation  = "helm.sdk.operatorframework.io/upgrade-force"
-	helmUninstallWaitAnnotation = "helm.sdk.operatorframework.io/uninstall-wait"
+	helmUpgradeForceAnnotation    = "helm.sdk.operatorframework.io/upgrade-force"
+	helmUninstallWaitAnnotation   = "helm.sdk.operatorframework.io/uninstall-wait"
+	helmReconcilePeriodAnnotation = "helm.sdk.operatorframework.io/reconcile-period"
 )
 
 // Reconcile reconciles the requested resource by installing, updating, or
@@ -101,6 +102,18 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile
 	status := types.StatusFor(o)
 	log = log.WithValues("release", manager.ReleaseName())
 
+	reconcileResult := reconcile.Result{RequeueAfter: r.ReconcilePeriod}
+	// Determine the correct reconcile period based on the existing value in the reconciler and the
+	// annotations in the custom resource. If a reconcile period is specified in the custom resource
+	// annotations, this value will take precedence over the the existing reconcile period value
+	// (which came from either the command-line flag or the watches.yaml file).
+	finalReconcilePeriod, err := determineReconcilePeriod(r.ReconcilePeriod, o)
+	if err != nil {
+		log.Error(err, "Error: unable to parse reconcile period from the custom resource's annotations")
+		return reconcile.Result{}, err
+	}
+	reconcileResult.RequeueAfter = finalReconcilePeriod
+
 	if o.GetDeletionTimestamp() != nil {
 		if !(controllerutil.ContainsFinalizer(o, uninstallFinalizer) ||
 			controllerutil.ContainsFinalizer(o, uninstallFinalizerLegacy)) {
@@ -171,7 +184,7 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile
 			}
 			if !isAllResourcesDeleted {
 				log.Info("Waiting until all resources are deleted")
-				return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, nil
+				return reconcileResult, nil
 			}
 			status.RemoveCondition(types.ConditionReleaseFailed)
 		}
@@ -271,7 +284,7 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile
 			Manifest: installedRelease.Manifest,
 		}
 		err = r.updateResourceStatus(ctx, o, status)
-		return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err
+		return reconcileResult, err
 	}
 
 	if !(controllerutil.ContainsFinalizer(o, uninstallFinalizer) ||
@@ -334,7 +347,7 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile
 			Manifest: upgradedRelease.Manifest,
 		}
 		err = r.updateResourceStatus(ctx, o, status)
-		return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err
+		return reconcileResult, err
 	}
 
 	// If a change is made to the CR spec that causes a release failure, a
@@ -387,8 +400,26 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile
 		Name:     expectedRelease.Name,
 		Manifest: expectedRelease.Manifest,
 	}
+
 	err = r.updateResourceStatus(ctx, o, status)
-	return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, err
+	return reconcileResult, err
+}
+
+// returns the reconcile period that will be set to the RequeueAfter field in the reconciler. If any period
+// is specified in the custom resource's annotations, this will be returned. If not, the existing reconcile period
+// will be returned. An error will be thrown if the custom resource time period is not in proper format.
+func determineReconcilePeriod(currentPeriod time.Duration, o *unstructured.Unstructured) (time.Duration, error) {
+	// If custom resource annotations are present, they will take precedence over the command-line flag
+	if annot, exists := o.UnstructuredContent()["metadata"].(map[string]interface{})["annotations"]; exists {
+		if timeDuration, present := annot.(map[string]interface{})[helmReconcilePeriodAnnotation]; present {
+			annotationsPeriod, err := time.ParseDuration(timeDuration.(string))
+			if err != nil {
+				return currentPeriod, err // First return value does not matter, since err != nil
+			}
+			return annotationsPeriod, nil
+		}
+	}
+	return currentPeriod, nil
 }
 
 // returns the boolean representation of the annotation string
diff --git a/internal/helm/watches/watches.go b/internal/helm/watches/watches.go
index e2f43a1d..49f2109f 100644
--- a/internal/helm/watches/watches.go
+++ b/internal/helm/watches/watches.go
@@ -40,6 +40,7 @@ type Watch struct {
 	WatchDependentResources *bool                `json:"watchDependentResources,omitempty"`
 	OverrideValues          map[string]string    `json:"overrideValues,omitempty"`
 	Selector                metav1.LabelSelector `json:"selector"`
+	ReconcilePeriod         metav1.Duration      `json:"reconcilePeriod,omitempty"`
 }
 
 // UnmarshalYAML unmarshals an individual watch from the Helm watches.yaml file
diff --git a/website/content/en/docs/building-operators/golang/advanced-topics.md b/website/content/en/docs/building-operators/golang/advanced-topics.md
index 58c7c53c..420cd63c 100644
--- a/website/content/en/docs/building-operators/golang/advanced-topics.md
+++ b/website/content/en/docs/building-operators/golang/advanced-topics.md
@@ -299,7 +299,7 @@ Authors may decide to distribute their bundles for various architectures: x86_64
 [garbage_collection]: https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/
 [finalizers]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers
 [cronjobs]: https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/
-[cronjobs_fields]: https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/#jobs-history-limits
+[cronjob_fields]: https://kubernetes.io/docs/tasks/job/automated-tasks-with-cron-jobs/#jobs-history-limits
 [cronjob_tutorial]: https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html#3-clean-up-old-jobs-according-to-the-history-limit
 [pv]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/
 [reclaiming]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaiming
diff --git a/website/content/en/docs/building-operators/golang/crds-scope.md b/website/content/en/docs/building-operators/golang/crds-scope.md
index e8a1227d..0e16dc0e 100644
--- a/website/content/en/docs/building-operators/golang/crds-scope.md
+++ b/website/content/en/docs/building-operators/golang/crds-scope.md
@@ -30,7 +30,7 @@ $ operator-sdk create api --group cache --version v1alpha1 --kind Memcached --re
 
 You can also manually set the scope in the Go `types.go` file by adding or changing the [kubebuilder scope marker][kubebuilder_crd_markers]
 to your resource. This file is usually located in `api/<version>/<kind>_types.go` or `apis/<group>/<version>/<kind>_types.go` if
-you are using the [multigroup][multigroup-kubebuilder-doc] layout. Once this marker is set, the CRD files will be generated with the approriate scope.
+you are using the [multigroup][kubebuilder_multigroup] layout. Once this marker is set, the CRD files will be generated with the approriate scope.
 Here is an example API type with the marker set to cluster scope:
 
 ```golang
diff --git a/website/content/en/docs/building-operators/golang/migration.md b/website/content/en/docs/building-operators/golang/migration.md
index fa930033..4c83847b 100644
--- a/website/content/en/docs/building-operators/golang/migration.md
+++ b/website/content/en/docs/building-operators/golang/migration.md
@@ -34,7 +34,7 @@ Scaffolded projects now use:
 - Scaffolded tests that use the [`envtest`][envtest] test framework
 - Preliminary support for CLI plugins. For more info see the [plugins design document][plugins-phase1-design-doc]
 - A `PROJECT` configuration file to store information about GVKs, plugins, and help the CLI make decisions.
-- A new option to create projects using ComponentConfig. For more info, see [enhancement proposal][enhancement proposal] and the [Component config tutorial][component-config-tutorial]
+- A new option to create projects using ComponentConfig. For more info, see [enhancement proposal][component-proposal] and the [Component config tutorial][component-config-tutorial]
 - Go version `1.15` (previously it was `1.13`).
 
 Generated files with the default API versions:
@@ -444,6 +444,7 @@ For further steps regarding the deployment of the operator, creation of custom r
 [webhook-doc]: https://book.kubebuilder.io/reference/webhook-overview.html
 [healthz-ping]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/healthz#CheckHandler
 [controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases
+[component-proposal]: https://github.com/kubernetes-sigs/controller-runtime/blob/master/designs/component-config.md
 [component-config-tutorial]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/docs/book/src/component-config-tutorial/tutorial.md
 [plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md
 [migration-doc]: /docs/upgrading-sdk-version/
diff --git a/website/content/en/docs/building-operators/golang/tutorial.md b/website/content/en/docs/building-operators/golang/tutorial.md
index a49a05aa..c8ec2b51 100644
--- a/website/content/en/docs/building-operators/golang/tutorial.md
+++ b/website/content/en/docs/building-operators/golang/tutorial.md
@@ -265,6 +265,8 @@ The `ClusterRole` manifest at `config/rbac/role.yaml` is generated from the abov
 make manifests
 ```
 
+NOTE: If you receive an error, please run the specified command in the error and re-run `make manifests`. 
+
 ## Configure the operator's image registry
 
 All that remains is to build and push the operator image to the desired image registry.
diff --git a/website/content/en/docs/building-operators/helm/quickstart.md b/website/content/en/docs/building-operators/helm/quickstart.md
index be7afdbc..ede63c8f 100644
--- a/website/content/en/docs/building-operators/helm/quickstart.md
+++ b/website/content/en/docs/building-operators/helm/quickstart.md
@@ -60,7 +60,7 @@ This guide walks through an example of building a simple nginx-operator powered
 has a custom CA, these [configuration steps][image-reg-config] must be complete.
 
   ```sh
-  operator-sdk run bundle example.com/memcached-operator-bundle:v0.0.1
+  operator-sdk run bundle example.com/nginx-operator-bundle:v0.0.1
   ```
 
 1. Create a sample Nginx custom resource:
diff --git a/website/content/en/docs/building-operators/helm/reference/advanced_features/annotations.md b/website/content/en/docs/building-operators/helm/reference/advanced_features/annotations.md
index 604bd059..2b8653d8 100644
--- a/website/content/en/docs/building-operators/helm/reference/advanced_features/annotations.md
+++ b/website/content/en/docs/building-operators/helm/reference/advanced_features/annotations.md
@@ -145,3 +145,25 @@ log message when a delete has been triggered:
 {"level":"info","ts":1612294054.5845876,"logger":"helm.controller","msg":"Uninstall wait","namespace":"default","name":"nginx-sample","apiVersion":"example.com/v1alpha1","kind":"Nginx","release":"nginx-sample"}
 
 ```
+
+## `helm.sdk.operatorframework.io/reconcile-period`
+
+While running a Helm-based operator, the reconcile-period can be specified through the custom resource's annotations under the `helm.sdk.operatorframework.io/reconcile-period` key. 
+This feature guarantees that an operator will get reconciled, at minimum, in the specified interval of time. In other words, it ensures that the cluster will not go longer
+than the specified reconcile-period without being reconciled. However, the cluster may be reconciled at any moment if there are changes detected in the desired state.
+
+The reconcile period can be specified in the custom resource's annotations in the following manner: 
+
+```sh
+...
+metadata:
+  name: nginx-sample
+  annotations:
+    helm.sdk.operatorframework.io/reconcile-period: 5s
+...
+```
+
+The value that is present under this key must be in the h/m/s format. For example, 1h2m4s, 3m0s, 4s are all valid values, but 1x3m9s is invalid. 
+
+**NOTE**: This is just one way of specifying the reconcile period for Helm-based operators. There are two other ways: using the `--reconcile-period` command-line flag and under the 'reconcilePeriod' key in the watches.yaml file. If these three methods are used simultaneously to specify reconcile period (which they should not be), the order of precedence is as follows: 
+Custom Resource Annotations > watches.yaml > command-line flag.
\ No newline at end of file
diff --git a/website/content/en/docs/overview/_index.md b/website/content/en/docs/overview/_index.md
index 0f91d9f8..35b251f0 100644
--- a/website/content/en/docs/overview/_index.md
+++ b/website/content/en/docs/overview/_index.md
@@ -100,9 +100,9 @@ Official build architectures for binaries:
 
 | Binary                    | `linux/amd64` | `linux/arm64` |`linux/ppc64le` | `linux/s390x` | `darwin/amd64` | `darwin/arm64` |
 |---------------------------|---------------|---------------|----------------|---------------|----------------|----------------|
-| `operator-sdk`            | ✓             | ✓             | ✓              | ✓             | ✓              | -              |
-| `ansible-operator`        | ✓             | ✓             | ✓              | ✓             | ✓              | -              |
-| `helm-operator`           | ✓             | ✓             | ✓              | ✓             | ✓              | -              |
+| `operator-sdk`            | ✓             | ✓             | ✓              | ✓             | ✓              | ✓              |
+| `ansible-operator`        | ✓             | ✓             | ✓              | ✓             | ✓              | ✓              |
+| `helm-operator`           | ✓             | ✓             | ✓              | ✓             | ✓              | ✓              |
 
 Official build architectures for images:
 


Test patch:
diff --git a/internal/helm/controller/reconcile_test.go b/internal/helm/controller/reconcile_test.go
index 188e936f..c26c3b23 100644
--- a/internal/helm/controller/reconcile_test.go
+++ b/internal/helm/controller/reconcile_test.go
@@ -16,11 +16,61 @@ package controller
 
 import (
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
+func TestDetermineReconcilePeriod(t *testing.T) {
+	testPeriod1, _ := time.ParseDuration("10s")
+	obj1 := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"metadata": map[string]interface{}{
+				"annotations": map[string]interface{}{
+					"name":                        "test-obj-1",
+					helmReconcilePeriodAnnotation: "3s",
+				},
+			},
+		},
+	}
+	expected1, _ := time.ParseDuration("3s")
+	finalPeriod1, err := determineReconcilePeriod(testPeriod1, obj1)
+	assert.Equal(t, nil, err, "Verify that no error is returned on parsing the time period")
+	assert.Equal(t, expected1, finalPeriod1, "Verify that the annotations period takes precedence")
+
+	testPeriod2, _ := time.ParseDuration("1h3m4s")
+	obj2 := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"metadata": map[string]interface{}{
+				"annotations": map[string]interface{}{
+					"name": "test-obj-2",
+				},
+			},
+		},
+	}
+	expected2, _ := time.ParseDuration("1h3m4s")
+	finalPeriod2, err := determineReconcilePeriod(testPeriod2, obj2)
+	assert.Equal(t, nil, err, "Verify that no error is returned on parsing the time period")
+	assert.Equal(t, expected2, finalPeriod2, "Verify that when no time period is present under the CR's annotations, the original time period value gets used")
+
+	testPeriod3, _ := time.ParseDuration("5m15s")
+	obj3 := &unstructured.Unstructured{
+		Object: map[string]interface{}{
+			"metadata": map[string]interface{}{
+				"annotations": map[string]interface{}{
+					"name":                        "test-obj-3",
+					helmReconcilePeriodAnnotation: "4x",
+				},
+			},
+		},
+	}
+	finalPeriod3, err := determineReconcilePeriod(testPeriod3, obj3)
+	expected3, _ := time.ParseDuration("5m15s")
+	assert.NotEqual(t, nil, err, "Verify that error is thrown when invalid time period is passed in the CR annotations")
+	assert.Equal(t, expected3, finalPeriod3, "Verify that when a faulty time period is present under the CR's annotations, the original time period value gets used")
+}
+
 func TestHasAnnotation(t *testing.T) {
 	upgradeForceTests := []struct {
 		input       map[string]interface{}