Skip to content

Add helpers for StatefulSet container merging, raw ConfigMaps, and preserved Secrets#678

Open
lmiccini wants to merge 1 commit intoopenstack-k8s-operators:mainfrom
lmiccini:drop_clusterop
Open

Add helpers for StatefulSet container merging, raw ConfigMaps, and preserved Secrets#678
lmiccini wants to merge 1 commit intoopenstack-k8s-operators:mainfrom
lmiccini:drop_clusterop

Conversation

@lmiccini
Copy link
Copy Markdown
Contributor

Three new utilities to reduce boilerplate in operator controllers:

  • statefulset.MergeContainersByName: merges container specs by name, preserving server-defaulted fields (TerminationMessagePath, ImagePullPolicy, etc.) to avoid unnecessary reconcile loops. StatefulSet.CreateOrPatch now uses this instead of full Template replacement.

  • configmap.CreateOrPatchRawConfigMap: creates/patches a ConfigMap from raw map[string]string data without template rendering machinery.

  • secret.CreateOrPatchSecretPreserve: creates a Secret on first call and preserves existing Data on subsequent reconciles, useful for generated credentials that should not be rotated.

…eserved Secrets

Three new utilities to reduce boilerplate in operator controllers:

- statefulset.MergeContainersByName: merges container specs by name,
  preserving server-defaulted fields (TerminationMessagePath,
  ImagePullPolicy, StartupProbe, WorkingDir, etc.) to avoid
  unnecessary reconcile loops and pod restarts during operator
  migration. StatefulSet.CreateOrPatch now uses this instead of full
  Template replacement.

- configmap.CreateOrPatchRawConfigMap: creates/patches a ConfigMap from
  raw map[string]string data without template rendering machinery.

- secret.CreateOrPatchSecretPreserve: creates a Secret on first call and
  preserves existing Data on subsequent reconciles, useful for generated
  credentials that should not be rotated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
configMap.Labels = util.MergeStringMaps(configMap.Labels, lbls)
configMap.Data = data

err := controllerutil.SetControllerReference(obj, configMap, h.GetScheme())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we also allow to skip setting owner ref if needed, by passing a SkipSetOwner

}

op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), configMap, func() error {
configMap.Labels = util.MergeStringMaps(configMap.Labels, lbls)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably good to also pass annotations, we might need it for b/r, depending on the consumer/use case

Comment on lines +210 to +213
name string,
namespace string,
data map[string]string,
lbls map[string]string,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my comments on the secret func

ctx context.Context,
h *helper.Helper,
obj client.Object,
secret *corev1.Secret,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we pass a secret, in above CM case, we pass name, namespace, data, lb. should we also pass a cm for CreateOrPatchRawConfigMap ?

s.StringData = secret.StringData
}

err := controllerutil.SetControllerReference(obj, s, h.GetScheme())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above should also allow via a param to skip setting controller ref

// (e.g. TerminationMessagePath, ImagePullPolicy) and avoid
// unnecessary reconcile loops. Fall back to full replacement if
// container sets don't match by name.
if !MergeContainersByName(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we returning a bool, instead of just doing the merge?

func MergeContainersByName(existing, desired []corev1.Container) {
  ...
}
// No if statement needed. The function updates the existing object's memory.
MergeContainersByName(
    statefulset.Spec.Template.Spec.Containers,
    s.statefulset.Spec.Template.Spec.Containers,
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants