Repository: Azure/ARO-RP
Pull request #1152 (2020-11-12 22:43:44)

Consolidate and clean up operator controllers

jim-minter: @troy0820 as discussed, ptal
jim-minter: /azp run E2E
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
jim-minter: /azp test CI
azure-pipelines[bot]: <samp>
Command 'test' is not supported by Azure Pipelines.<br>
<br>
<b>Supported commands</b><br>
<ul type="none"><li><b>help:</b></li><ul type="none"><li>Get descriptions, examples and documentation about supported commands</li><li><b>Example: </b>help "command_name"</li></ul><li><b>list:</b></li><ul type="none"><li>List all pipelines for this repository using a comment.</li><li><b>Example: </b>"list"</li></ul><li><b>run:</b></li><ul type="none"><li>Run all pipelines or specific pipelines for this repository using a comment. Use this command by itself to trigger all related pipelines, or specify specific pipelines to run.</li><li><b>Example: </b>"run" or "run pipeline_name, pipeline_name, pipeline_name"</li></ul><li><b>where:</b></li><ul type="none"><li>Report back the Azure DevOps orgs that are related to this repository and org</li><li><b>Example: </b>"where"</li></ul></ul><br>
See <a href="https://go.microsoft.com/fwlink/?linkid=2056279">additional documentation.</a>
</samp>
jim-minter: /azp run E2E
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
github-actions[bot]: Please rebase pull request.

Patch:
diff --git a/pkg/operator/controllers/genevalogging/genevalogging.go b/pkg/operator/controllers/genevalogging/genevalogging.go
index 3bd8ec4e4..3c10b4f6f 100644
--- a/pkg/operator/controllers/genevalogging/genevalogging.go
+++ b/pkg/operator/controllers/genevalogging/genevalogging.go
@@ -11,8 +11,6 @@ import (
 	"github.com/Azure/go-autorest/autorest/to"
 	projectv1 "github.com/openshift/api/project/v1"
 	securityv1 "github.com/openshift/api/security/v1"
-	securityclient "github.com/openshift/client-go/security/clientset/versioned"
-	"github.com/sirupsen/logrus"
 	appsv1 "k8s.io/api/apps/v1"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -28,34 +26,8 @@ const (
 	GenevaKeyName  = "gcskey.pem"
 )
 
-type GenevaLogging interface {
-	Resources(context.Context) ([]runtime.Object, error)
-}
-
-type genevaLogging struct {
-	log     *logrus.Entry
-	cluster *arov1alpha1.Cluster
-
-	seccli securityclient.Interface
-
-	gcscert []byte
-	gcskey  []byte
-}
-
-func New(log *logrus.Entry, cluster *arov1alpha1.Cluster, seccli securityclient.Interface, gcscert, gcskey []byte) GenevaLogging {
-	return &genevaLogging{
-		log:     log,
-		cluster: cluster,
-
-		seccli: seccli,
-
-		gcscert: gcscert,
-		gcskey:  gcskey,
-	}
-}
-
-func (g *genevaLogging) securityContextConstraints(ctx context.Context, name, serviceAccountName string) (*securityv1.SecurityContextConstraints, error) {
-	scc, err := g.seccli.SecurityV1().SecurityContextConstraints().Get(ctx, "privileged", metav1.GetOptions{})
+func (g *GenevaloggingReconciler) securityContextConstraints(ctx context.Context, name, serviceAccountName string) (*securityv1.SecurityContextConstraints, error) {
+	scc, err := g.securitycli.SecurityV1().SecurityContextConstraints().Get(ctx, "privileged", metav1.GetOptions{})
 	if err != nil {
 		return nil, err
 	}
@@ -68,7 +40,11 @@ func (g *genevaLogging) securityContextConstraints(ctx context.Context, name, se
 	return scc, nil
 }
 
-func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
+func (g *GenevaloggingReconciler) daemonset(cluster *arov1alpha1.Cluster) (*appsv1.DaemonSet, error) {
+	r, err := azure.ParseResourceID(cluster.Spec.ResourceID)
+	if err != nil {
+		return nil, err
+	}
 
 	return &appsv1.DaemonSet{
 		ObjectMeta: metav1.ObjectMeta{
@@ -143,7 +119,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 					Containers: []v1.Container{
 						{
 							Name:  "fluentbit-journal",
-							Image: version.FluentbitImage(g.cluster.Spec.ACRDomain),
+							Image: version.FluentbitImage(cluster.Spec.ACRDomain),
 							Command: []string{
 								"/opt/td-agent-bit/bin/td-agent-bit",
 							},
@@ -180,7 +156,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 						},
 						{
 							Name:  "fluentbit-containers",
-							Image: version.FluentbitImage(g.cluster.Spec.ACRDomain),
+							Image: version.FluentbitImage(cluster.Spec.ACRDomain),
 							Command: []string{
 								"/opt/td-agent-bit/bin/td-agent-bit",
 							},
@@ -217,7 +193,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 						},
 						{
 							Name:  "fluentbit-audit",
-							Image: version.FluentbitImage(g.cluster.Spec.ACRDomain),
+							Image: version.FluentbitImage(cluster.Spec.ACRDomain),
 							Command: []string{
 								"/opt/td-agent-bit/bin/td-agent-bit",
 							},
@@ -254,7 +230,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 						},
 						{
 							Name:  "mdsd",
-							Image: version.MdsdImage(g.cluster.Spec.ACRDomain),
+							Image: version.MdsdImage(cluster.Spec.ACRDomain),
 							Command: []string{
 								"/usr/sbin/mdsd",
 							},
@@ -269,7 +245,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 							Env: []v1.EnvVar{
 								{
 									Name:  "MONITORING_GCS_ENVIRONMENT",
-									Value: g.cluster.Spec.GenevaLogging.MonitoringGCSEnvironment,
+									Value: cluster.Spec.GenevaLogging.MonitoringGCSEnvironment,
 								},
 								{
 									Name:  "MONITORING_GCS_ACCOUNT",
@@ -277,7 +253,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 								},
 								{
 									Name:  "MONITORING_GCS_REGION",
-									Value: g.cluster.Spec.Location,
+									Value: cluster.Spec.Location,
 								},
 								{
 									Name:  "MONITORING_GCS_CERT_CERTFILE",
@@ -293,7 +269,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 								},
 								{
 									Name:  "MONITORING_CONFIG_VERSION",
-									Value: g.cluster.Spec.GenevaLogging.ConfigVersion,
+									Value: cluster.Spec.GenevaLogging.ConfigVersion,
 								},
 								{
 									Name:  "MONITORING_USE_GENEVA_CONFIG_SERVICE",
@@ -301,7 +277,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 								},
 								{
 									Name:  "MONITORING_TENANT",
-									Value: g.cluster.Spec.Location,
+									Value: cluster.Spec.Location,
 								},
 								{
 									Name:  "MONITORING_ROLE",
@@ -318,7 +294,7 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 								},
 								{
 									Name:  "RESOURCE_ID",
-									Value: strings.ToLower(g.cluster.Spec.ResourceID),
+									Value: strings.ToLower(cluster.Spec.ResourceID),
 								},
 								{
 									Name:  "SUBSCRIPTION_ID",
@@ -358,16 +334,16 @@ func (g *genevaLogging) daemonset(r azure.Resource) *appsv1.DaemonSet {
 				},
 			},
 		},
-	}
+	}, nil
 }
 
-func (g *genevaLogging) Resources(ctx context.Context) ([]runtime.Object, error) {
-	r, err := azure.ParseResourceID(g.cluster.Spec.ResourceID)
+func (g *GenevaloggingReconciler) resources(ctx context.Context, cluster *arov1alpha1.Cluster, gcscert, gcskey []byte) ([]runtime.Object, error) {
+	scc, err := g.securityContextConstraints(ctx, "privileged-genevalogging", kubeServiceAccount)
 	if err != nil {
 		return nil, err
 	}
 
-	scc, err := g.securityContextConstraints(ctx, "privileged-genevalogging", kubeServiceAccount)
+	daemonset, err := g.daemonset(cluster)
 	if err != nil {
 		return nil, err
 	}
@@ -385,8 +361,8 @@ func (g *genevaLogging) Resources(ctx context.Context) ([]runtime.Object, error)
 				Namespace: kubeNamespace,
 			},
 			Data: map[string][]byte{
-				GenevaCertName: g.gcscert,
-				GenevaKeyName:  g.gcskey,
+				GenevaCertName: gcscert,
+				GenevaKeyName:  gcskey,
 			},
 		},
 		&v1.ConfigMap{
@@ -408,6 +384,6 @@ func (g *genevaLogging) Resources(ctx context.Context) ([]runtime.Object, error)
 			},
 		},
 		scc,
-		g.daemonset(r),
+		daemonset,
 	}, nil
 }
diff --git a/pkg/operator/controllers/genevalogging/genevalogging_controller.go b/pkg/operator/controllers/genevalogging/genevalogging_controller.go
index ff5a3cffe..a009bd747 100644
--- a/pkg/operator/controllers/genevalogging/genevalogging_controller.go
+++ b/pkg/operator/controllers/genevalogging/genevalogging_controller.go
@@ -5,21 +5,16 @@ package genevalogging
 
 import (
 	"context"
-	"sort"
 
 	securityv1 "github.com/openshift/api/security/v1"
 	securityclient "github.com/openshift/client-go/security/clientset/versioned"
 	"github.com/sirupsen/logrus"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	"github.com/Azure/ARO-RP/pkg/operator"
@@ -74,57 +69,29 @@ func (r *GenevaloggingReconciler) Reconcile(request ctrl.Request) (ctrl.Result,
 		r.log.Error(err)
 		return reconcile.Result{}, err
 	}
-	gl := New(r.log, instance, r.securitycli, mysec.Data[GenevaCertName], mysec.Data[GenevaKeyName])
 
-	resources, err := gl.Resources(ctx)
+	resources, err := r.resources(ctx, instance, mysec.Data[GenevaCertName], mysec.Data[GenevaKeyName])
 	if err != nil {
 		r.log.Error(err)
 		return reconcile.Result{}, err
 	}
 
-	for _, res := range resources {
-		o, err := meta.Accessor(res)
-		if err != nil {
-			r.log.Errorf("Accessor %s/%s: %v", o.GetNamespace(), o.GetName(), err)
-			return reconcile.Result{}, err
-		}
-
-		// This sets the reference on all objects that we create
-		// to our cluster instance. This causes the Owns() below to work and
-		// to get Reconcile events when anything happens to our objects.
-		err = controllerutil.SetControllerReference(instance, o, scheme.Scheme)
-		if err != nil {
-			r.log.Errorf("SetControllerReference %s/%s: %v", o.GetNamespace(), o.GetName(), err)
-			return reconcile.Result{}, err
-		}
-	}
-
-	err = dynamichelper.HashWorkloadConfigs(resources)
+	err = dynamichelper.SetControllerReferences(resources, instance)
 	if err != nil {
-		r.log.Errorf("HashWorkloadConfigs %v", err)
+		r.log.Error(err)
 		return reconcile.Result{}, err
 	}
 
-	uns := make([]*unstructured.Unstructured, 0, len(resources))
-	for _, res := range resources {
-		un := &unstructured.Unstructured{}
-		err = scheme.Scheme.Convert(res, un, nil)
-		if err != nil {
-			return reconcile.Result{}, err
-		}
-		uns = append(uns, un)
+	uns, err := dynamichelper.Prepare(resources)
+	if err != nil {
+		r.log.Error(err)
+		return reconcile.Result{}, err
 	}
 
-	sort.Slice(uns, func(i, j int) bool {
-		return dynamichelper.CreateOrder(uns[i], uns[j])
-	})
-
-	for _, un := range uns {
-		err = dh.Ensure(ctx, un)
-		if err != nil {
-			r.log.Error(err)
-			return reconcile.Result{}, err
-		}
+	err = dh.Ensure(ctx, uns...)
+	if err != nil {
+		r.log.Error(err)
+		return reconcile.Result{}, err
 	}
 
 	return reconcile.Result{}, nil
diff --git a/pkg/operator/controllers/routefix/routefix_controller.go b/pkg/operator/controllers/routefix/routefix_controller.go
index 7fab908a7..61fe7e112 100644
--- a/pkg/operator/controllers/routefix/routefix_controller.go
+++ b/pkg/operator/controllers/routefix/routefix_controller.go
@@ -5,19 +5,14 @@ package routefix
 
 import (
 	"context"
-	"sort"
 
 	securityclient "github.com/openshift/client-go/security/clientset/versioned"
 	"github.com/sirupsen/logrus"
 	appsv1 "k8s.io/api/apps/v1"
-	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/rest"
 	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
@@ -55,6 +50,7 @@ func (r *RouteFixReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error
 
 	instance, err := r.arocli.Clusters().Get(ctx, request.Name, metav1.GetOptions{})
 	if err != nil {
+		r.log.Error(err)
 		return reconcile.Result{}, err
 	}
 
@@ -73,49 +69,22 @@ func (r *RouteFixReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error
 		return reconcile.Result{}, err
 	}
 
-	for _, res := range resources {
-		o, err := meta.Accessor(res)
-		if err != nil {
-			r.log.Errorf("Accessor %s/%s: %v", o.GetNamespace(), o.GetName(), err)
-			return reconcile.Result{}, err
-		}
-
-		// This sets the reference on all objects that we create
-		// to our cluster instance. This causes the Owns() below to work and
-		// to get Reconcile events when anything happens to our objects.
-		err = controllerutil.SetControllerReference(instance, o, scheme.Scheme)
-		if err != nil {
-			r.log.Errorf("SetControllerReference %s/%s: %v", o.GetNamespace(), o.GetName(), err)
-			return reconcile.Result{}, err
-		}
-	}
-
-	err = dynamichelper.HashWorkloadConfigs(resources)
+	err = dynamichelper.SetControllerReferences(resources, instance)
 	if err != nil {
-		r.log.Errorf("HashWorkloadConfigs %v", err)
+		r.log.Error(err)
 		return reconcile.Result{}, err
 	}
 
-	uns := make([]*unstructured.Unstructured, 0, len(resources))
-	for _, res := range resources {
-		un := &unstructured.Unstructured{}
-		err = scheme.Scheme.Convert(res, un, nil)
-		if err != nil {
-			return reconcile.Result{}, err
-		}
-		uns = append(uns, un)
+	uns, err := dynamichelper.Prepare(resources)
+	if err != nil {
+		r.log.Error(err)
+		return reconcile.Result{}, err
 	}
 
-	sort.Slice(uns, func(i, j int) bool {
-		return dynamichelper.CreateOrder(uns[i], uns[j])
-	})
-
-	for _, un := range uns {
-		err = dh.Ensure(ctx, un)
-		if err != nil {
-			r.log.Error(err)
-			return reconcile.Result{}, err
-		}
+	err = dh.Ensure(ctx, uns...)
+	if err != nil {
+		r.log.Error(err)
+		return reconcile.Result{}, err
 	}
 
 	return reconcile.Result{}, nil
diff --git a/pkg/operator/deploy/deploy.go b/pkg/operator/deploy/deploy.go
index 8cd181a20..cf94a5b90 100644
--- a/pkg/operator/deploy/deploy.go
+++ b/pkg/operator/deploy/deploy.go
@@ -6,7 +6,6 @@ package deploy
 import (
 	"context"
 	"fmt"
-	"sort"
 	"time"
 
 	"github.com/Azure/go-autorest/autorest/azure"
@@ -18,7 +17,6 @@ import (
 	extensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/kubernetes"
@@ -185,20 +183,11 @@ func (o *operator) CreateOrUpdate(ctx context.Context) error {
 		return err
 	}
 
-	uns := make([]*unstructured.Unstructured, 0, len(resources))
-	for _, res := range resources {
-		un := &unstructured.Unstructured{}
-		err = scheme.Scheme.Convert(res, un, nil)
-		if err != nil {
-			return err
-		}
-		uns = append(uns, un)
+	uns, err := dynamichelper.Prepare(resources)
+	if err != nil {
+		return err
 	}
 
-	sort.Slice(uns, func(i, j int) bool {
-		return dynamichelper.CreateOrder(uns[i], uns[j])
-	})
-
 	for _, un := range uns {
 		err = o.dh.Ensure(ctx, un)
 		if err != nil {
diff --git a/pkg/util/dynamichelper/createorder.go b/pkg/util/dynamichelper/createorder.go
index bf1dc6922..a1d877302 100644
--- a/pkg/util/dynamichelper/createorder.go
+++ b/pkg/util/dynamichelper/createorder.go
@@ -9,7 +9,7 @@ import (
 
 // rather than list every GK, just list the ones whose creation really has to be
 // brought forward
-var createOrder = map[string]int{
+var createOrderMap = map[string]int{
 	// non-namespaced resources
 	"CustomResourceDefinition.apiextensions.k8s.io":    1, // before custom resources
 	"ClusterRole.rbac.authorization.k8s.io":            2, // before workload resources
@@ -26,15 +26,15 @@ var createOrder = map[string]int{
 
 const createOrderMax = 99
 
-// CreateOrder is to be used in a sort.Slice() comparison.  It is to help make
+// createOrder is to be used in a sort.Slice() comparison.  It is to help make
 // sure that resources are created in an order that causes a reliable startup.
-func CreateOrder(i, j *unstructured.Unstructured) bool {
-	io, ok := createOrder[i.GroupVersionKind().GroupKind().String()]
+func createOrder(i, j *unstructured.Unstructured) bool {
+	io, ok := createOrderMap[i.GroupVersionKind().GroupKind().String()]
 	if !ok {
 		io = createOrderMax
 	}
 
-	jo, ok := createOrder[j.GroupVersionKind().GroupKind().String()]
+	jo, ok := createOrderMap[j.GroupVersionKind().GroupKind().String()]
 	if !ok {
 		jo = createOrderMax
 	}
diff --git a/pkg/util/dynamichelper/dynamichelper.go b/pkg/util/dynamichelper/dynamichelper.go
index 0f659e1de..c21ad850d 100644
--- a/pkg/util/dynamichelper/dynamichelper.go
+++ b/pkg/util/dynamichelper/dynamichelper.go
@@ -29,7 +29,7 @@ type Interface interface {
 	RefreshAPIResources() error
 	CreateOrUpdate(ctx context.Context, obj *unstructured.Unstructured) error
 	Delete(ctx context.Context, groupKind, namespace, name string) error
-	Ensure(ctx context.Context, o *unstructured.Unstructured) error
+	Ensure(ctx context.Context, objs ...*unstructured.Unstructured) error
 	Get(ctx context.Context, groupKind, namespace, name string) (*unstructured.Unstructured, error)
 	List(ctx context.Context, groupKind, namespace string) (*unstructured.UnstructuredList, error)
 }
@@ -173,7 +173,18 @@ func (dh *dynamicHelper) Delete(ctx context.Context, groupKind, namespace, name
 // is intended to ensure that an object matches a desired state.  It is tolerant
 // of unspecified fields in the desired state (e.g. it will leave typically
 // leave .status untouched).
-func (dh *dynamicHelper) Ensure(ctx context.Context, o *unstructured.Unstructured) error {
+func (dh *dynamicHelper) Ensure(ctx context.Context, objs ...*unstructured.Unstructured) error {
+	for _, o := range objs {
+		err := dh.ensureOne(ctx, o)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (dh *dynamicHelper) ensureOne(ctx context.Context, o *unstructured.Unstructured) error {
 	gvr, err := dh.findGVR(o.GroupVersionKind().GroupKind().String(), o.GroupVersionKind().Version)
 	if err != nil {
 		return err
diff --git a/pkg/util/dynamichelper/workloadhelper.go b/pkg/util/dynamichelper/workloadhelper.go
index 0d02baada..949039c0f 100644
--- a/pkg/util/dynamichelper/workloadhelper.go
+++ b/pkg/util/dynamichelper/workloadhelper.go
@@ -11,14 +11,80 @@ import (
 
 	appsv1 "k8s.io/api/apps/v1"
 	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/meta"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/client-go/kubernetes/scheme"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 )
 
-// HashWorkloadConfigs iterates daemonsets, walks their volumes, and updates
+func SetControllerReferences(resources []runtime.Object, owner metav1.Object) error {
+	for _, resource := range resources {
+		r, err := meta.Accessor(resource)
+		if err != nil {
+			return err
+		}
+
+		err = controllerutil.SetControllerReference(owner, r, scheme.Scheme)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func Prepare(resources []runtime.Object) ([]*unstructured.Unstructured, error) {
+	err := hashWorkloadConfigs(resources)
+	if err != nil {
+		return nil, err
+	}
+
+	uns := make([]*unstructured.Unstructured, 0, len(resources))
+	for _, resource := range resources {
+		un := &unstructured.Unstructured{}
+		err = scheme.Scheme.Convert(resource, un, nil)
+		if err != nil {
+			return nil, err
+		}
+		uns = append(uns, un)
+	}
+
+	sort.Slice(uns, func(i, j int) bool {
+		return createOrder(uns[i], uns[j])
+	})
+
+	return uns, nil
+}
+
+func addWorkloadHashes(o *metav1.ObjectMeta, t *v1.PodTemplateSpec, configToHash map[string]string) {
+	for _, v := range t.Spec.Volumes {
+		if v.Secret != nil {
+			if hash, found := configToHash[keyFunc(schema.GroupKind{Kind: "Secret"}, o.Namespace, v.Secret.SecretName)]; found {
+				if t.Annotations == nil {
+					t.Annotations = map[string]string{}
+				}
+				t.Annotations["checksum/secret-"+v.Secret.SecretName] = hash
+			}
+		}
+
+		if v.ConfigMap != nil {
+			if hash, found := configToHash[keyFunc(schema.GroupKind{Kind: "ConfigMap"}, o.Namespace, v.ConfigMap.Name)]; found {
+				if t.Annotations == nil {
+					t.Annotations = map[string]string{}
+				}
+				t.Annotations["checksum/configmap-"+v.ConfigMap.Name] = hash
+			}
+		}
+	}
+}
+
+// hashWorkloadConfigs iterates daemonsets, walks their volumes, and updates
 // their pod templates with annotations that include the hashes of the content
 // for each configmap or secret.
-func HashWorkloadConfigs(resources []runtime.Object) error {
+func hashWorkloadConfigs(resources []runtime.Object) error {
 	// map config resources to their hashed content
 	configToHash := map[string]string{}
 	for _, o := range resources {
@@ -36,29 +102,13 @@ func HashWorkloadConfigs(resources []runtime.Object) error {
 	for _, o := range resources {
 		switch o := o.(type) {
 		case *appsv1.DaemonSet:
-			for _, v := range o.Spec.Template.Spec.Volumes {
-				if v.Secret != nil {
-					if hash, found := configToHash[keyFunc(schema.GroupKind{Kind: "Secret"}, o.Namespace, v.Secret.SecretName)]; found {
-						if o.Spec.Template.Annotations == nil {
-							o.Spec.Template.Annotations = map[string]string{}
-						}
-						o.Spec.Template.Annotations["checksum/secret-"+v.Secret.SecretName] = hash
-					}
-				}
+			addWorkloadHashes(&o.ObjectMeta, &o.Spec.Template, configToHash)
 
-				if v.ConfigMap != nil {
-					if hash, found := configToHash[keyFunc(schema.GroupKind{Kind: "ConfigMap"}, o.Namespace, v.ConfigMap.Name)]; found {
-						if o.Spec.Template.Annotations == nil {
-							o.Spec.Template.Annotations = map[string]string{}
-						}
-						o.Spec.Template.Annotations["checksum/configmap-"+v.ConfigMap.Name] = hash
-					}
-				}
-			}
+		case *appsv1.Deployment:
+			addWorkloadHashes(&o.ObjectMeta, &o.Spec.Template, configToHash)
 
-		case *appsv1.Deployment, *appsv1.StatefulSet:
-			// TODO: add as/when needed
-			return fmt.Errorf("unimplemented: %T", o)
+		case *appsv1.StatefulSet:
+			addWorkloadHashes(&o.ObjectMeta, &o.Spec.Template, configToHash)
 		}
 	}
 
diff --git a/pkg/util/mocks/dynamichelper/dynamichelper.go b/pkg/util/mocks/dynamichelper/dynamichelper.go
index e1b647aa9..9db60b2d9 100644
--- a/pkg/util/mocks/dynamichelper/dynamichelper.go
+++ b/pkg/util/mocks/dynamichelper/dynamichelper.go
@@ -64,17 +64,22 @@ func (mr *MockInterfaceMockRecorder) Delete(arg0, arg1, arg2, arg3 interface{})
 }
 
 // Ensure mocks base method
-func (m *MockInterface) Ensure(arg0 context.Context, arg1 *unstructured.Unstructured) error {
+func (m *MockInterface) Ensure(arg0 context.Context, arg1 ...*unstructured.Unstructured) error {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "Ensure", arg0, arg1)
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "Ensure", varargs...)
 	ret0, _ := ret[0].(error)
 	return ret0
 }
 
 // Ensure indicates an expected call of Ensure
-func (mr *MockInterfaceMockRecorder) Ensure(arg0, arg1 interface{}) *gomock.Call {
+func (mr *MockInterfaceMockRecorder) Ensure(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ensure", reflect.TypeOf((*MockInterface)(nil).Ensure), arg0, arg1)
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ensure", reflect.TypeOf((*MockInterface)(nil).Ensure), varargs...)
 }
 
 // Get mocks base method


Test patch:
diff --git a/pkg/util/dynamichelper/createorder_test.go b/pkg/util/dynamichelper/createorder_test.go
index b8f13a87e..ebf22f2a9 100644
--- a/pkg/util/dynamichelper/createorder_test.go
+++ b/pkg/util/dynamichelper/createorder_test.go
@@ -29,7 +29,7 @@ func TestCreateOrder(t *testing.T) {
 	}
 
 	sort.Slice(test, func(i, j int) bool {
-		return CreateOrder(test[i], test[j])
+		return createOrder(test[i], test[j])
 	})
 
 	if !reflect.DeepEqual(expect, test) {
diff --git a/pkg/util/dynamichelper/workloadhelper_test.go b/pkg/util/dynamichelper/workloadhelper_test.go
index 70b27c07f..9349f6c13 100644
--- a/pkg/util/dynamichelper/workloadhelper_test.go
+++ b/pkg/util/dynamichelper/workloadhelper_test.go
@@ -91,7 +91,7 @@ func TestHashWorkloadConfigs(t *testing.T) {
 		},
 	}
 
-	err := HashWorkloadConfigs([]runtime.Object{cm, sec, ds})
+	err := hashWorkloadConfigs([]runtime.Object{cm, sec, ds})
 	if err != nil {
 		t.Fatal(err)
 	}