Skip to content

Commit

Permalink
Close #5565: Added parsing of annotations (#5585)
Browse files Browse the repository at this point in the history
Signed-off-by: Venkat Ramaraju <vramaraj@redhat.com>
  • Loading branch information
VenkatRamaraju committed Mar 11, 2022
1 parent 6a18cc3 commit d632ae2
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 3 deletions.
11 changes: 11 additions & 0 deletions changelog/fragments/helm-annotations-reconcile-period.yaml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 7 additions & 1 deletion internal/cmd/helm-operator/run/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"runtime"
"strings"
"time"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -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,
Expand Down
34 changes: 32 additions & 2 deletions internal/helm/controller/reconcile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 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
}
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 {
Expand Down
50 changes: 50 additions & 0 deletions internal/helm/controller/reconcile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
1 change: 1 addition & 0 deletions internal/helm/watches/watches.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
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. 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.

0 comments on commit d632ae2

Please sign in to comment.