Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions modules/common/configmap/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,50 @@ func EnsureConfigMaps(
return nil
}

// CreateOrPatchRawConfigMap creates or patches a ConfigMap from raw data
// (map[string]string) without template rendering. Returns the config hash and
// operation result. This is a simpler alternative to EnsureConfigMaps when the
// caller already has the data and doesn't need template machinery.
// When skipSetOwner is true, no controller reference is set on the ConfigMap.
func CreateOrPatchRawConfigMap(
ctx context.Context,
h *helper.Helper,
obj client.Object,
cm *corev1.ConfigMap,
skipSetOwner bool,
) (string, controllerutil.OperationResult, error) {
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: cm.Name,
Namespace: cm.Namespace,
},
}

op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), configMap, func() error {
configMap.Annotations = util.MergeStringMaps(configMap.Annotations, cm.Annotations)
configMap.Labels = util.MergeStringMaps(configMap.Labels, cm.Labels)
configMap.Data = cm.Data

if !skipSetOwner {
err := controllerutil.SetControllerReference(obj, configMap, h.GetScheme())
if err != nil {
return err
}
}
return nil
})
if err != nil {
return "", op, fmt.Errorf("error create/updating configmap: %w", err)
}

configMapHash, err := Hash(configMap)
if err != nil {
return "", op, fmt.Errorf("error calculating configuration hash: %w", err)
}

return configMapHash, op, nil
}

// GetConfigMaps - get all configmaps required, verify they exist and add the hash to env and status
func GetConfigMaps(
ctx context.Context,
Expand Down
51 changes: 51 additions & 0 deletions modules/common/secret/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,57 @@ func CreateOrPatchSecret(
return secretHash, op, err
}

// CreateOrPatchSecretPreserve creates a secret on first creation and preserves
// existing Data keys on subsequent reconciles. This is useful for generated
// credentials (passwords, cookies) that should only be set once and not
// rotated on every reconcile. Labels and annotations are always updated.
// When skipSetOwner is true, no controller reference is set on the Secret.
func CreateOrPatchSecretPreserve(
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 ?

skipSetOwner bool,
) (string, controllerutil.OperationResult, error) {
s := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secret.Name,
Namespace: secret.Namespace,
},
}

op, err := controllerutil.CreateOrPatch(ctx, h.GetClient(), s, func() error {
s.Annotations = util.MergeStringMaps(s.Annotations, secret.Annotations)
s.Labels = util.MergeStringMaps(s.Labels, secret.Labels)

// Only set data on initial creation (when the object has no data yet)
if len(s.Data) == 0 {
s.Immutable = secret.Immutable
s.Type = secret.Type
s.Data = secret.Data
s.StringData = secret.StringData
}

if !skipSetOwner {
err := controllerutil.SetControllerReference(obj, s, h.GetScheme())
if err != nil {
return err
}
}
return nil
})
if err != nil {
return "", op, fmt.Errorf("error create/updating secret: %w", err)
}

secretHash, err := Hash(s)
if err != nil {
return "", "", fmt.Errorf("error calculating configuration hash: %w", err)
}

return secretHash, op, err
}

// createOrUpdateSecret - create or update existing secrte if it already exists
// finally return configuration hash
func createOrUpdateSecret(
Expand Down
62 changes: 62 additions & 0 deletions modules/common/statefulset/container.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
Copyright 2026 Red Hat

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package statefulset

import (
corev1 "k8s.io/api/core/v1"
)

// MergeContainersByName merges desired container specs into existing containers
// matched by name. It starts from the desired container and preserves only the
// server-defaulted fields (TerminationMessagePath, TerminationMessagePolicy,
// ImagePullPolicy) from the existing container. All other fields come from the
// desired spec, which ensures that new fields added in future Kubernetes
// versions are not silently dropped.
//
// When container counts differ or a desired container name is not found in
// existing, the existing slice is replaced with the desired containers.
func MergeContainersByName(existing *[]corev1.Container, desired []corev1.Container) {
if len(*existing) != len(desired) {
*existing = desired
return
}

existingByName := make(map[string]int, len(*existing))
for i := range *existing {
existingByName[(*existing)[i].Name] = i
}

for _, d := range desired {
idx, ok := existingByName[d.Name]
if !ok {
*existing = desired
return
}
// Preserve server-defaulted fields from the existing container
// only when the desired spec doesn't explicitly set them.
if d.ImagePullPolicy == "" {
d.ImagePullPolicy = (*existing)[idx].ImagePullPolicy
}
if d.TerminationMessagePath == "" {
d.TerminationMessagePath = (*existing)[idx].TerminationMessagePath
}
if d.TerminationMessagePolicy == "" {
d.TerminationMessagePolicy = (*existing)[idx].TerminationMessagePolicy
}
(*existing)[idx] = d
}
}
Loading
Loading