From 1cd6231064192f4e89269c76a096cdda8afa01e6 Mon Sep 17 00:00:00 2001 From: Venkat Ramaraju Date: Mon, 7 Mar 2022 13:41:59 -0800 Subject: [PATCH] Close #5565: Added parsing of annotations Signed-off-by: Venkat Ramaraju --- .../helm-annotations-reconcile-period.yaml | 11 ++++ internal/helm/controller/reconcile.go | 34 ++++++++++++- internal/helm/controller/reconcile_test.go | 50 +++++++++++++++++++ .../advanced_features/reconcile-period.md | 25 ++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 changelog/fragments/helm-annotations-reconcile-period.yaml create mode 100644 website/content/en/docs/building-operators/helm/reference/advanced_features/reconcile-period.md diff --git a/changelog/fragments/helm-annotations-reconcile-period.yaml b/changelog/fragments/helm-annotations-reconcile-period.yaml new file mode 100644 index 0000000000..0450ecf374 --- /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 set to + then set to the 'reconcile-period' 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/internal/helm/controller/reconcile.go b/internal/helm/controller/reconcile.go index 74f7e2a721..5fdd65381d 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 @@ -387,10 +388,39 @@ func (r HelmOperatorReconciler) Reconcile(ctx context.Context, request reconcile Name: expectedRelease.Name, Manifest: expectedRelease.Manifest, } + + // Determine the correct reconcile period based on current value in the reconciler and the + // annotations in the custom resource. If a reconcile period is specified in the custom resource + // annotations, this will take precedence over the command-line flag specified reconcile period, + // which is currently present in the "ReconcilePeriod" field of the reconciler. + 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 + } + r.ReconcilePeriod = finalReconcilePeriod + err = r.updateResourceStatus(ctx, o, status) return reconcile.Result{RequeueAfter: r.ReconcilePeriod}, 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 // will return false if annotation is not set func hasAnnotation(anno string, o *unstructured.Unstructured) bool { diff --git a/internal/helm/controller/reconcile_test.go b/internal/helm/controller/reconcile_test.go index 188e936f40..c26c3b2378 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{} diff --git a/website/content/en/docs/building-operators/helm/reference/advanced_features/reconcile-period.md b/website/content/en/docs/building-operators/helm/reference/advanced_features/reconcile-period.md new file mode 100644 index 0000000000..0e2e7a6e47 --- /dev/null +++ b/website/content/en/docs/building-operators/helm/reference/advanced_features/reconcile-period.md @@ -0,0 +1,25 @@ +--- +title: Reconcile Period from Custom Resource Annotations +linkTitle: Custom Resource Annotations Reconcile Period +weight: 200 +description: Allow a user to set the desired reconcile period from the custom resource's annotations +--- + +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. Another way is using the `--reconcile-period` command line flag while running the operator. However, if both of these methods are used together (which they shouldn't be), *the custom resource's annotations will take precedence*.