Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.

Commit e5d5b53

Browse files
authored
CLOUDP-80014: Move Volume/VolumeMount merging logic + tests into merge package (#289)
1 parent 0f99232 commit e5d5b53

File tree

6 files changed

+304
-354
lines changed

6 files changed

+304
-354
lines changed

pkg/controller/mongodb/build_statefulset_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,20 @@ func assertStatefulSetIsBuiltCorrectly(t *testing.T, mdb mdbv1.MongoDB, sts *app
4747
assert.Equal(t, mdb.Namespace, sts.Namespace)
4848
assert.Equal(t, appsv1.RollingUpdateStatefulSetStrategyType, sts.Spec.UpdateStrategy.Type)
4949
assert.Equal(t, operatorServiceAccountName, sts.Spec.Template.Spec.ServiceAccountName)
50-
assert.Len(t, sts.Spec.Template.Spec.Containers[0].Env, 4)
51-
assert.Len(t, sts.Spec.Template.Spec.Containers[1].Env, 1)
50+
assert.Len(t, sts.Spec.Template.Spec.Containers[1].Env, 4)
51+
assert.Len(t, sts.Spec.Template.Spec.Containers[0].Env, 1)
5252

53-
agentContainer := sts.Spec.Template.Spec.Containers[0]
53+
agentContainer := sts.Spec.Template.Spec.Containers[1]
5454
assert.Equal(t, "agent-image", agentContainer.Image)
5555
probe := agentContainer.ReadinessProbe
5656
assert.True(t, reflect.DeepEqual(probes.New(defaultReadiness()), *probe))
5757
assert.Equal(t, probes.New(defaultReadiness()).FailureThreshold, probe.FailureThreshold)
5858
assert.Equal(t, int32(5), probe.InitialDelaySeconds)
5959
assert.Len(t, agentContainer.VolumeMounts, 4)
6060

61-
mongodContainer := sts.Spec.Template.Spec.Containers[1]
61+
mongodContainer := sts.Spec.Template.Spec.Containers[0]
6262
assert.Equal(t, "repo/mongo:4.2.2", mongodContainer.Image)
63-
assert.NotNil(t, sts.Spec.Template.Spec.Containers[0].ReadinessProbe)
63+
assert.NotNil(t, sts.Spec.Template.Spec.Containers[1].ReadinessProbe)
6464
assert.Len(t, mongodContainer.VolumeMounts, 4)
6565

6666
initContainer := sts.Spec.Template.Spec.InitContainers[0]

pkg/controller/mongodb/replicaset_controller_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ func TestStatefulSet_IsCorrectlyConfigured(t *testing.T) {
157157

158158
assert.Len(t, sts.Spec.Template.Spec.Containers, 2)
159159

160-
agentContainer := sts.Spec.Template.Spec.Containers[0]
160+
agentContainer := sts.Spec.Template.Spec.Containers[1]
161161
assert.Equal(t, agentName, agentContainer.Name)
162162
assert.Equal(t, os.Getenv(agentImageEnv), agentContainer.Image)
163163
expectedProbe := probes.New(defaultReadiness())
164164
assert.True(t, reflect.DeepEqual(&expectedProbe, agentContainer.ReadinessProbe))
165165

166-
mongodbContainer := sts.Spec.Template.Spec.Containers[1]
166+
mongodbContainer := sts.Spec.Template.Spec.Containers[0]
167167
assert.Equal(t, mongodbName, mongodbContainer.Name)
168168
assert.Equal(t, "repo/mongo:4.2.2", mongodbContainer.Image)
169169

@@ -529,8 +529,8 @@ func TestReplicaSet_IsScaledUpToDesiredMembers_WhenFirstCreated(t *testing.T) {
529529

530530
func TestOpenshift_Configuration(t *testing.T) {
531531
sts := performReconciliationAndGetStatefulSet(t, "openshift_mdb.yaml")
532-
assert.Equal(t, "MANAGED_SECURITY_CONTEXT", sts.Spec.Template.Spec.Containers[0].Env[3].Name)
533-
assert.Equal(t, "MANAGED_SECURITY_CONTEXT", sts.Spec.Template.Spec.Containers[1].Env[1].Name)
532+
assert.Equal(t, "MANAGED_SECURITY_CONTEXT", sts.Spec.Template.Spec.Containers[1].Env[3].Name)
533+
assert.Equal(t, "MANAGED_SECURITY_CONTEXT", sts.Spec.Template.Spec.Containers[0].Env[1].Name)
534534
}
535535

536536
func TestVolumeClaimTemplates_Configuration(t *testing.T) {

pkg/kube/podtemplatespec/podspec_template.go

Lines changed: 5 additions & 265 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package podtemplatespec
22

33
import (
4-
"sort"
4+
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/merge"
55

66
"github.com/imdario/mergo"
77
"github.com/mongodb/mongodb-kubernetes-operator/pkg/kube/container"
8-
"github.com/mongodb/mongodb-kubernetes-operator/pkg/util/envvar"
98
corev1 "k8s.io/api/core/v1"
109
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1110
)
@@ -229,27 +228,16 @@ func WithVolumeMounts(containerName string, volumeMounts ...corev1.VolumeMount)
229228
}
230229

231230
func MergePodTemplateSpecs(defaultTemplate, overrideTemplate corev1.PodTemplateSpec) (corev1.PodTemplateSpec, error) {
232-
// Containers need to be merged manually
233-
mergedContainers, err := mergeContainers(defaultTemplate.Spec.Containers, overrideTemplate.Spec.Containers)
234-
if err != nil {
235-
return corev1.PodTemplateSpec{}, err
236-
}
237231

238-
mergedTolerations := mergeTolerations(defaultTemplate.Spec.Tolerations, overrideTemplate.Spec.Tolerations)
239-
240-
// InitContainers need to be merged manually
241-
mergedInitContainers, err := mergeContainers(defaultTemplate.Spec.InitContainers, overrideTemplate.Spec.InitContainers)
242-
if err != nil {
243-
return corev1.PodTemplateSpec{}, err
244-
}
245-
246-
// Affinity needs to be merged manually
232+
mergedContainers := merge.Containers(defaultTemplate.Spec.Containers, overrideTemplate.Spec.Containers)
233+
mergedTolerations := merge.Tolerations(defaultTemplate.Spec.Tolerations, overrideTemplate.Spec.Tolerations)
234+
mergedInitContainers := merge.Containers(defaultTemplate.Spec.InitContainers, overrideTemplate.Spec.InitContainers)
247235
mergedAffinity, err := mergeAffinity(defaultTemplate.Spec.Affinity, overrideTemplate.Spec.Affinity)
248236
if err != nil {
249237
return corev1.PodTemplateSpec{}, err
250238
}
251239

252-
mergedVolumes := mergeVolumes(defaultTemplate.Spec.Volumes, overrideTemplate.Spec.Volumes)
240+
mergedVolumes := merge.Volumes(defaultTemplate.Spec.Volumes, overrideTemplate.Spec.Volumes)
253241

254242
// Everything else can be merged with mergo
255243
mergedPodTemplateSpec := *defaultTemplate.DeepCopy()
@@ -265,254 +253,6 @@ func MergePodTemplateSpecs(defaultTemplate, overrideTemplate corev1.PodTemplateS
265253
return mergedPodTemplateSpec, nil
266254
}
267255

268-
func createKeyToPathMap(items []corev1.KeyToPath) map[string]corev1.KeyToPath {
269-
itemsMap := make(map[string]corev1.KeyToPath)
270-
for _, v := range items {
271-
itemsMap[v.Key] = v
272-
}
273-
return itemsMap
274-
}
275-
276-
func mergeKeyToPath(defaultKey corev1.KeyToPath, overrideKey corev1.KeyToPath) corev1.KeyToPath {
277-
if defaultKey.Key != overrideKey.Key {
278-
// This should not happen as we always merge by Key.
279-
// If it does, we return the default as something's wrong
280-
return defaultKey
281-
}
282-
if overrideKey.Path != "" {
283-
defaultKey.Path = overrideKey.Path
284-
}
285-
if overrideKey.Mode != nil {
286-
defaultKey.Mode = overrideKey.Mode
287-
}
288-
return defaultKey
289-
}
290-
291-
func mergeKeyToPathItems(defaultItems []corev1.KeyToPath, overrideItems []corev1.KeyToPath) []corev1.KeyToPath {
292-
// Merge Items array by KeyToPath.Key entry
293-
defaultItemsMap := createKeyToPathMap(defaultItems)
294-
overrideItemsMap := createKeyToPathMap(overrideItems)
295-
var mergedItems []corev1.KeyToPath
296-
for _, defaultItem := range defaultItemsMap {
297-
mergedKey := defaultItem
298-
if overrideItem, ok := overrideItemsMap[defaultItem.Key]; ok {
299-
// Need to merge
300-
mergedKey = mergeKeyToPath(defaultItem, overrideItem)
301-
}
302-
mergedItems = append(mergedItems, mergedKey)
303-
}
304-
for _, overrideItem := range overrideItemsMap {
305-
if _, ok := defaultItemsMap[overrideItem.Key]; ok {
306-
// Already merged
307-
continue
308-
}
309-
mergedItems = append(mergedItems, overrideItem)
310-
311-
}
312-
return mergedItems
313-
}
314-
315-
func mergeVolume(defaultVolume corev1.Volume, overrideVolume corev1.Volume) corev1.Volume {
316-
// Volume contains only Name and VolumeSource
317-
318-
// Merge VolumeSource
319-
overrideSource := overrideVolume.VolumeSource
320-
defaultSource := defaultVolume.VolumeSource
321-
mergedVolume := defaultVolume
322-
323-
// Only one field must be non-nil.
324-
// We merge if it is one of the ones we fill from the operator side:
325-
// - EmptyDir
326-
if overrideSource.EmptyDir != nil && defaultSource.EmptyDir != nil {
327-
if overrideSource.EmptyDir.Medium != "" {
328-
mergedVolume.EmptyDir.Medium = overrideSource.EmptyDir.Medium
329-
}
330-
if overrideSource.EmptyDir.SizeLimit != nil {
331-
mergedVolume.EmptyDir.SizeLimit = overrideSource.EmptyDir.SizeLimit
332-
}
333-
return mergedVolume
334-
}
335-
// - Secret
336-
if overrideSource.Secret != nil && defaultSource.Secret != nil {
337-
if overrideSource.Secret.SecretName != "" {
338-
mergedVolume.Secret.SecretName = overrideSource.Secret.SecretName
339-
}
340-
mergedVolume.Secret.Items = mergeKeyToPathItems(defaultSource.Secret.Items, overrideSource.Secret.Items)
341-
if overrideSource.Secret.DefaultMode != nil {
342-
mergedVolume.Secret.DefaultMode = overrideSource.Secret.DefaultMode
343-
}
344-
return mergedVolume
345-
}
346-
// - ConfigMap
347-
if overrideSource.ConfigMap != nil && defaultSource.ConfigMap != nil {
348-
if overrideSource.ConfigMap.LocalObjectReference.Name != "" {
349-
mergedVolume.ConfigMap.LocalObjectReference.Name = overrideSource.ConfigMap.LocalObjectReference.Name
350-
}
351-
mergedVolume.ConfigMap.Items = mergeKeyToPathItems(defaultSource.ConfigMap.Items, overrideSource.ConfigMap.Items)
352-
if overrideSource.ConfigMap.DefaultMode != nil {
353-
mergedVolume.ConfigMap.DefaultMode = overrideSource.ConfigMap.DefaultMode
354-
}
355-
if overrideSource.ConfigMap.Optional != nil {
356-
mergedVolume.ConfigMap.Optional = overrideSource.ConfigMap.Optional
357-
}
358-
return mergedVolume
359-
}
360-
361-
// Otherwise we assume that the user provides every field
362-
// and we just assign it and nil every other field
363-
// We also do that in the case the user provides one of the three listed above
364-
// but our volume has a different non-nil entry.
365-
366-
// this is effectively the same as just returning the overrideSource
367-
mergedVolume.VolumeSource = overrideSource
368-
return mergedVolume
369-
}
370-
371-
func createVolumesMap(volumes []corev1.Volume) map[string]corev1.Volume {
372-
volumesMap := make(map[string]corev1.Volume)
373-
for _, v := range volumes {
374-
volumesMap[v.Name] = v
375-
}
376-
return volumesMap
377-
}
378-
379-
func mergeVolumes(defaultVolumes []corev1.Volume, overrideVolumes []corev1.Volume) []corev1.Volume {
380-
defaultVolumesMap := createVolumesMap(defaultVolumes)
381-
overrideVolumesMap := createVolumesMap(overrideVolumes)
382-
mergedVolumes := []corev1.Volume{}
383-
384-
for _, defaultVolume := range defaultVolumes {
385-
mergedVolume := defaultVolume
386-
if overrideVolume, ok := overrideVolumesMap[defaultVolume.Name]; ok {
387-
mergedVolume = mergeVolume(defaultVolume, overrideVolume)
388-
}
389-
mergedVolumes = append(mergedVolumes, mergedVolume)
390-
}
391-
392-
for _, overrideVolume := range overrideVolumes {
393-
if _, ok := defaultVolumesMap[overrideVolume.Name]; ok {
394-
// Already Merged
395-
continue
396-
}
397-
mergedVolumes = append(mergedVolumes, overrideVolume)
398-
}
399-
400-
sort.SliceStable(mergedVolumes, func(i, j int) bool {
401-
return mergedVolumes[i].Name < mergedVolumes[j].Name
402-
})
403-
return mergedVolumes
404-
}
405-
406-
func mergeVolumeMounts(defaultMounts, overrideMounts []corev1.VolumeMount) ([]corev1.VolumeMount, error) {
407-
defaultMountsMap := createMountsMap(defaultMounts)
408-
overrideMountsMap := createMountsMap(overrideMounts)
409-
mergedVolumeMounts := []corev1.VolumeMount{}
410-
for _, defaultMount := range defaultMounts {
411-
if overrideMount, ok := overrideMountsMap[defaultMount.Name]; ok {
412-
// needs merge
413-
if err := mergo.Merge(&defaultMount, overrideMount, mergo.WithAppendSlice); err != nil { //nolint
414-
return nil, err
415-
}
416-
}
417-
mergedVolumeMounts = append(mergedVolumeMounts, defaultMount)
418-
}
419-
for _, overrideMount := range overrideMounts {
420-
if _, ok := defaultMountsMap[overrideMount.Name]; ok {
421-
// already merged
422-
continue
423-
}
424-
mergedVolumeMounts = append(mergedVolumeMounts, overrideMount)
425-
}
426-
return mergedVolumeMounts, nil
427-
}
428-
429-
func createMountsMap(volumeMounts []corev1.VolumeMount) map[string]corev1.VolumeMount {
430-
mountMap := make(map[string]corev1.VolumeMount)
431-
for _, m := range volumeMounts {
432-
mountMap[m.Name] = m
433-
}
434-
return mountMap
435-
}
436-
437-
func createTolerationsMap(tolerations []corev1.Toleration) map[string]corev1.Toleration {
438-
tolerationsMap := make(map[string]corev1.Toleration)
439-
for _, t := range tolerations {
440-
tolerationsMap[t.Key] = t
441-
}
442-
return tolerationsMap
443-
}
444-
445-
func mergeTolerations(defaultTolerations, overrideTolerations []corev1.Toleration) []corev1.Toleration {
446-
mergedTolerations := make([]corev1.Toleration, 0)
447-
defaultMap := createTolerationsMap(defaultTolerations)
448-
for _, v := range overrideTolerations {
449-
defaultMap[v.Key] = v
450-
}
451-
452-
for _, v := range defaultMap {
453-
mergedTolerations = append(mergedTolerations, v)
454-
}
455-
456-
if len(mergedTolerations) == 0 {
457-
return nil
458-
}
459-
460-
sort.SliceStable(mergedTolerations, func(i, j int) bool {
461-
return mergedTolerations[i].Key < mergedTolerations[j].Key
462-
})
463-
464-
return mergedTolerations
465-
}
466-
467-
func mergeContainers(defaultContainers, customContainers []corev1.Container) ([]corev1.Container, error) {
468-
defaultMap := createContainerMap(defaultContainers)
469-
customMap := createContainerMap(customContainers)
470-
mergedContainers := []corev1.Container{}
471-
for _, defaultContainer := range defaultContainers {
472-
if customContainer, ok := customMap[defaultContainer.Name]; ok {
473-
// The container is present in both maps, so we need to merge
474-
// MergeWithOverride mounts
475-
mergedMounts, err := mergeVolumeMounts(defaultContainer.VolumeMounts, customContainer.VolumeMounts)
476-
if err != nil {
477-
return nil, err
478-
}
479-
480-
mergedEnvs := envvar.MergeWithOverride(defaultContainer.Env, customContainer.Env)
481-
482-
if err := mergo.Merge(&defaultContainer, customContainer, mergo.WithOverride); err != nil { //nolint
483-
return nil, err
484-
}
485-
// completely override any resources that were provided
486-
// this prevents issues with custom requests giving errors due
487-
// to the defaulted limits
488-
defaultContainer.Resources = customContainer.Resources
489-
defaultContainer.VolumeMounts = mergedMounts
490-
defaultContainer.Env = mergedEnvs
491-
}
492-
// The default container was not modified by the override, so just add it
493-
mergedContainers = append(mergedContainers, defaultContainer)
494-
}
495-
496-
// Look for customContainers that were not merged into existing ones
497-
for _, customContainer := range customContainers {
498-
if _, ok := defaultMap[customContainer.Name]; ok {
499-
continue
500-
}
501-
// Need to add it
502-
mergedContainers = append(mergedContainers, customContainer)
503-
}
504-
505-
return mergedContainers, nil
506-
}
507-
508-
func createContainerMap(containers []corev1.Container) map[string]corev1.Container {
509-
containerMap := make(map[string]corev1.Container)
510-
for _, c := range containers {
511-
containerMap[c.Name] = c
512-
}
513-
return containerMap
514-
}
515-
516256
func mergeAffinity(defaultAffinity, overrideAffinity *corev1.Affinity) (*corev1.Affinity, error) {
517257
if defaultAffinity == nil {
518258
return overrideAffinity, nil

0 commit comments

Comments
 (0)