controllers/util/common.go (514 lines of code) (raw):

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 util import ( policyv1 "k8s.io/api/policy/v1" "reflect" "strconv" "strings" "time" "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) var ( SecretReadOnlyPermissions int32 = 440 PublicReadOnlyPermissions int32 = 444 ) func Replicas(num int) *int32 { num32 := int32(num) return &num32 } // Set the requeueAfter if it has not been set, or is greater than the new time to requeue at func updateRequeueAfter(requeueOrNot *reconcile.Result, newWait time.Duration) { if requeueOrNot.RequeueAfter <= 0 || requeueOrNot.RequeueAfter > newWait { requeueOrNot.RequeueAfter = newWait } } // CopyLabelsAndAnnotations copies the labels and annotations from one object to another. // Additional Labels and Annotations in the 'to' object will not be removed. // Returns true if there are updates required to the object. func CopyLabelsAndAnnotations(from, to *metav1.ObjectMeta, logger logr.Logger) (requireUpdate bool) { if len(to.Labels) == 0 && len(from.Labels) > 0 { to.Labels = make(map[string]string, len(from.Labels)) } for k, v := range from.Labels { if to.Labels[k] != v { requireUpdate = true logger.Info("Update Label", "label", k, "newValue", v, "oldValue", to.Labels[k]) to.Labels[k] = v } } if len(to.Annotations) == 0 && len(from.Annotations) > 0 { to.Annotations = make(map[string]string, len(from.Annotations)) } for k, v := range from.Annotations { if to.Annotations[k] != v { requireUpdate = true logger.Info("Update Annotation", "annotation", k, "newValue", v, "oldValue", to.Annotations[k]) to.Annotations[k] = v } } return requireUpdate } func DuplicateLabelsOrAnnotations(from map[string]string) map[string]string { to := make(map[string]string, len(from)) for k, v := range from { to[k] = v } return to } func MergeLabelsOrAnnotations(base, additional map[string]string) map[string]string { merged := DuplicateLabelsOrAnnotations(base) for k, v := range additional { if _, alreadyExists := merged[k]; !alreadyExists { merged[k] = v } } return merged } // DeepEqualWithNils returns a deepEquals call that treats nil and zero-length maps, arrays and slices as the same. func DeepEqualWithNils(x, y interface{}) bool { if (x == nil) != (y == nil) { // Make sure that x is not the nil value if x == nil { x = y } v := reflect.ValueOf(x) switch v.Kind() { case reflect.Array: case reflect.Map: case reflect.Slice: return v.Len() == 0 } } return reflect.DeepEqual(x, y) } // ContainsString helper function to test string contains func ContainsString(slice []string, s string) bool { for _, item := range slice { if item == s { return true } } return false } // RemoveString helper function to remove string func RemoveString(slice []string, s string) (result []string) { for _, item := range slice { if item == s { continue } result = append(result, item) } return } // IsPVCOrphan determines whether the given name represents a PVC that is an orphan, or no longer has a pod associated with it. func IsPVCOrphan(pvcName string, replicas int32) bool { index := strings.LastIndexAny(pvcName, "-") if index == -1 { return false } ordinal, err := strconv.Atoi(pvcName[index+1:]) if err != nil { return false } return int32(ordinal) >= replicas } // customizeProbe builds the probe logic used for pod liveness, readiness, startup checks func customizeProbe(initialProbe *corev1.Probe, customProbe corev1.Probe) *corev1.Probe { if customProbe.InitialDelaySeconds != 0 { initialProbe.InitialDelaySeconds = customProbe.InitialDelaySeconds } if customProbe.TimeoutSeconds != 0 { initialProbe.TimeoutSeconds = customProbe.TimeoutSeconds } if customProbe.SuccessThreshold != 0 { initialProbe.SuccessThreshold = customProbe.SuccessThreshold } if customProbe.FailureThreshold != 0 { initialProbe.FailureThreshold = customProbe.FailureThreshold } if customProbe.PeriodSeconds != 0 { initialProbe.PeriodSeconds = customProbe.PeriodSeconds } if customProbe.ProbeHandler.Exec != nil || customProbe.ProbeHandler.HTTPGet != nil || customProbe.ProbeHandler.TCPSocket != nil { initialProbe.ProbeHandler = customProbe.ProbeHandler } return initialProbe } // CopyConfigMapFields copies the owned fields from one ConfigMap to another func CopyConfigMapFields(from, to *corev1.ConfigMap, logger logr.Logger) bool { logger = logger.WithValues("kind", "configMap") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) // Don't copy the entire Spec, because we can't overwrite the clusterIp field if !DeepEqualWithNils(to.Data, from.Data) { requireUpdate = true logger.Info("Update required because field changed", "field", "Data", "from", to.Data, "to", from.Data) } to.Data = from.Data return requireUpdate } // CopyServiceFields copies the owned fields from one Service to another func CopyServiceFields(from, to *corev1.Service, logger logr.Logger) bool { logger = logger.WithValues("kind", "service") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) // Don't copy the entire Spec, because we can't overwrite the clusterIp field if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector) } to.Spec.Selector = from.Spec.Selector if !DeepEqualWithNils(to.Spec.Ports, from.Spec.Ports) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Ports", "from", to.Spec.Ports, "to", from.Spec.Ports) } to.Spec.Ports = from.Spec.Ports if !DeepEqualWithNils(to.Spec.ExternalName, from.Spec.ExternalName) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.ExternalName", "from", to.Spec.ExternalName, "to", from.Spec.ExternalName) } to.Spec.ExternalName = from.Spec.ExternalName if !DeepEqualWithNils(to.Spec.PublishNotReadyAddresses, from.Spec.PublishNotReadyAddresses) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.PublishNotReadyAddresses", "from", to.Spec.PublishNotReadyAddresses, "to", from.Spec.PublishNotReadyAddresses) } to.Spec.PublishNotReadyAddresses = from.Spec.PublishNotReadyAddresses return requireUpdate } // CopyIngressFields copies the owned fields from one Ingress to another func CopyIngressFields(from, to *netv1.Ingress, logger logr.Logger) bool { logger = logger.WithValues("kind", "ingress") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) if len(to.Spec.Rules) != len(from.Spec.Rules) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Rules", "from", to.Spec.Rules, "to", from.Spec.Rules) to.Spec.Rules = from.Spec.Rules } else { for i := range from.Spec.Rules { ruleBase := "Spec.Rules[" + strconv.Itoa(i) + "]." fromRule := &from.Spec.Rules[i] toRule := &to.Spec.Rules[i] if !DeepEqualWithNils(toRule.Host, fromRule.Host) { requireUpdate = true logger.Info("Update required because field changed", "field", ruleBase+"Host", "from", toRule.Host, "to", fromRule.Host) toRule.Host = fromRule.Host } if fromRule.HTTP == nil || toRule.HTTP == nil { requireUpdate = true logger.Info("Update required because field changed", "field", ruleBase+"HTTP", "from", toRule.HTTP, "to", fromRule.HTTP) toRule.HTTP = fromRule.HTTP } else if len(fromRule.HTTP.Paths) != len(toRule.HTTP.Paths) { requireUpdate = true logger.Info("Update required because field changed", "field", ruleBase+"HTTP.Paths", "from", toRule.HTTP.Paths, "to", fromRule.HTTP.Paths) toRule.HTTP.Paths = fromRule.HTTP.Paths } else { for j := range fromRule.HTTP.Paths { pathBase := ruleBase + "HTTP.Paths[" + strconv.Itoa(j) + "]." fromPath := &fromRule.HTTP.Paths[j] toPath := &toRule.HTTP.Paths[j] if toPath.PathType != nil && !DeepEqualWithNils(toPath.PathType, fromPath.PathType) { requireUpdate = true logger.Info("Update required because field changed", "field", pathBase+"PathType", "from", toPath.PathType, "to", fromPath.PathType) toPath.PathType = fromPath.PathType } if !DeepEqualWithNils(toPath.Path, fromPath.Path) { requireUpdate = true logger.Info("Update required because field changed", "field", pathBase+"Path", "from", toPath.Path, "to", fromPath.Path) toPath.Path = fromPath.Path } if !DeepEqualWithNils(toPath.Backend.Service, fromPath.Backend.Service) { requireUpdate = true logger.Info("Update required because field changed", "field", pathBase+"Backend.Service", "from", toPath.Backend.Service, "to", fromPath.Backend.Service) toPath.Backend.Service = fromPath.Backend.Service } if !DeepEqualWithNils(toPath.Backend.Resource, fromPath.Backend.Resource) { requireUpdate = true logger.Info("Update required because field changed", "field", pathBase+"Backend.Resource", "from", toPath.Backend.Resource, "to", fromPath.Backend.Resource) toPath.Backend.Resource = fromPath.Backend.Resource } } } } } if !DeepEqualWithNils(to.Spec.TLS, from.Spec.TLS) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.TLS", "from", to.Spec.TLS, "to", from.Spec.TLS) to.Spec.TLS = from.Spec.TLS } if from.Spec.IngressClassName != nil && !DeepEqualWithNils(to.Spec.IngressClassName, from.Spec.IngressClassName) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.IngressClassName", "from", to.Spec.IngressClassName, "to", from.Spec.IngressClassName) to.Spec.IngressClassName = from.Spec.IngressClassName } return requireUpdate } // CopyStatefulSetFields copies the owned fields from one StatefulSet to another // Returns true if the fields copied from don't match to. func CopyStatefulSetFields(from, to *appsv1.StatefulSet, logger logr.Logger) bool { logger = logger.WithValues("kind", "statefulSet") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Replicas", "from", to.Spec.Replicas, "to", from.Spec.Replicas) to.Spec.Replicas = from.Spec.Replicas } if !DeepEqualWithNils(to.Spec.UpdateStrategy, from.Spec.UpdateStrategy) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.UpdateStrategy", "from", to.Spec.UpdateStrategy, "to", from.Spec.UpdateStrategy) to.Spec.UpdateStrategy = from.Spec.UpdateStrategy } /* Kubernetes does not currently support updates to these fields: Selector and PodManagementPolicy if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector) to.Spec.Selector = from.Spec.Selector } if !DeepEqualWithNils(to.Spec.PodManagementPolicy, from.Spec.PodManagementPolicy) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.PodManagementPolicy", "from", to.Spec.PodManagementPolicy, "to", from.Spec.PodManagementPolicy) to.Spec.PodManagementPolicy = from.Spec.PodManagementPolicy } */ /* Kubernetes does not support modification of VolumeClaimTemplates currently. See: https://github.com/kubernetes/enhancements/issues/661 if len(from.Spec.VolumeClaimTemplates) > len(to.Spec.VolumeClaimTemplates) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.VolumeClaimTemplates", "from", to.Spec.VolumeClaimTemplates, "to", from.Spec.VolumeClaimTemplates) to.Spec.VolumeClaimTemplates = from.Spec.VolumeClaimTemplates } for i := range from.Spec.VolumeClaimTemplates { vctBase := "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"]." fromVct := &from.Spec.VolumeClaimTemplates[i] toVct := &to.Spec.VolumeClaimTemplates[i] if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Name, fromVct.Name) { requireUpdate = true logger.Info("Update required because field changed", "field", vctBase+"Name", "from", toVct.Name, "to", fromVct.Name) toVct.Name = fromVct.Name } if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Labels, fromVct.Labels) { requireUpdate = true logger.Info("Update required because field changed", "field", vctBase+"Labels", "from", toVct.Labels, "to", fromVct.Labels) toVct.Labels = fromVct.Labels } if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Annotations, fromVct.Annotations) { requireUpdate = true logger.Info("Update required because field changed", "field", vctBase+"Annotations", "from", toVct.Annotations, "to", fromVct.Annotations) toVct.Annotations = fromVct.Annotations } if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Spec, fromVct.Spec) { requireUpdate = true logger.Info("Update required because field changed", "field", vctBase+"Spec", "from", toVct.Spec, "to", fromVct.Spec) toVct.Spec = fromVct.Spec } } */ requireUpdate = CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger) || requireUpdate return requireUpdate } // CopyDeploymentFields copies the owned fields from one Deployment to another // Returns true if the fields copied from don't match to. func CopyDeploymentFields(from, to *appsv1.Deployment, logger logr.Logger) bool { logger = logger.WithValues("kind", "deployment") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Replicas", "from", to.Spec.Replicas, "to", from.Spec.Replicas) to.Spec.Replicas = from.Spec.Replicas } if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector) to.Spec.Selector = from.Spec.Selector } requireUpdate = CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger) || requireUpdate return requireUpdate } func CopyPodTemplates(from, to *corev1.PodTemplateSpec, basePath string, logger logr.Logger) (requireUpdate bool) { if basePath == "" { logger = logger.WithValues("kind", "pod") } if !DeepEqualWithNils(to.Labels, from.Labels) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Labels", "from", to.Labels, "to", from.Labels) to.Labels = from.Labels } if !DeepEqualWithNils(to.Annotations, from.Annotations) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Annotations", "from", to.Annotations, "to", from.Annotations) to.Annotations = from.Annotations } requireUpdate = CopyPodContainers(&from.Spec.Containers, &to.Spec.Containers, basePath+"Spec.Containers", logger) || requireUpdate requireUpdate = CopyPodContainers(&from.Spec.InitContainers, &to.Spec.InitContainers, basePath+"Spec.InitContainers", logger) || requireUpdate requireUpdate = CopyPodVolumes(&from.Spec.Volumes, &to.Spec.Volumes, basePath+"Spec.Volumes", logger) || requireUpdate if !DeepEqualWithNils(to.Spec.HostAliases, from.Spec.HostAliases) { requireUpdate = true to.Spec.HostAliases = from.Spec.HostAliases logger.Info("Update required because field changed", "field", basePath+"Spec.HostAliases", "from", to.Spec.HostAliases, "to", from.Spec.HostAliases) } if !DeepEqualWithNils(to.Spec.ImagePullSecrets, from.Spec.ImagePullSecrets) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.ImagePullSecrets", "from", to.Spec.ImagePullSecrets, "to", from.Spec.ImagePullSecrets) to.Spec.ImagePullSecrets = from.Spec.ImagePullSecrets } if !DeepEqualWithNils(to.Spec.Affinity, from.Spec.Affinity) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.Affinity", "from", to.Spec.Affinity, "to", from.Spec.Affinity) to.Spec.Affinity = from.Spec.Affinity } if !DeepEqualWithNils(to.Spec.SecurityContext, from.Spec.SecurityContext) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.SecurityContext", "from", to.Spec.SecurityContext, "to", from.Spec.SecurityContext) to.Spec.SecurityContext = from.Spec.SecurityContext } if !DeepEqualWithNils(to.Spec.NodeSelector, from.Spec.NodeSelector) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.NodeSelector", "from", to.Spec.NodeSelector, "to", from.Spec.NodeSelector) to.Spec.NodeSelector = from.Spec.NodeSelector } if !DeepEqualWithNils(to.Spec.Tolerations, from.Spec.Tolerations) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.Tolerations", "from", to.Spec.Tolerations, "to", from.Spec.Tolerations) to.Spec.Tolerations = from.Spec.Tolerations } if !DeepEqualWithNils(to.Spec.PriorityClassName, from.Spec.PriorityClassName) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.PriorityClassName", "from", to.Spec.PriorityClassName, "to", from.Spec.PriorityClassName) to.Spec.PriorityClassName = from.Spec.PriorityClassName } if !DeepEqualWithNils(to.Spec.TerminationGracePeriodSeconds, from.Spec.TerminationGracePeriodSeconds) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.TerminationGracePeriodSeconds", "from", to.Spec.TerminationGracePeriodSeconds, "to", from.Spec.TerminationGracePeriodSeconds) to.Spec.TerminationGracePeriodSeconds = from.Spec.TerminationGracePeriodSeconds } if !DeepEqualWithNils(to.Spec.ServiceAccountName, from.Spec.ServiceAccountName) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.ServiceAccountName", "from", to.Spec.ServiceAccountName, "to", from.Spec.ServiceAccountName) to.Spec.ServiceAccountName = from.Spec.ServiceAccountName } if !DeepEqualWithNils(to.Spec.TopologySpreadConstraints, from.Spec.TopologySpreadConstraints) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+"Spec.TopologySpreadConstraints", "from", to.Spec.TopologySpreadConstraints, "to", from.Spec.TopologySpreadConstraints) to.Spec.TopologySpreadConstraints = from.Spec.TopologySpreadConstraints } return requireUpdate } func CopyPodContainers(fromPtr, toPtr *[]corev1.Container, basePath string, logger logr.Logger) (requireUpdate bool) { to := *toPtr from := *fromPtr if len(to) != len(from) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from)) *toPtr = from } else { for i := 0; i < len(from); i++ { containerBasePath := basePath + "[" + strconv.Itoa(i) + "]." if !DeepEqualWithNils(to[i].Name, from[i].Name) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Name", "from", to[i].Name, "to", from[i].Name) to[i].Name = from[i].Name } if !DeepEqualWithNils(to[i].Image, from[i].Image) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Image", "from", to[i].Image, "to", from[i].Image) to[i].Image = from[i].Image } if from[i].ImagePullPolicy != "" && !DeepEqualWithNils(to[i].ImagePullPolicy, from[i].ImagePullPolicy) { // Only request an update if the requestedPullPolicy is not empty // Otherwise kubernetes will specify a defaultPollPolicy and the operator will endlessly recurse, trying to unset the default policy. requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"ImagePullPolicy", "from", to[i].ImagePullPolicy, "to", from[i].ImagePullPolicy) } to[i].ImagePullPolicy = from[i].ImagePullPolicy if !DeepEqualWithNils(to[i].Command, from[i].Command) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Command", "from", to[i].Command, "to", from[i].Command) to[i].Command = from[i].Command } if !DeepEqualWithNils(to[i].Args, from[i].Args) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Args", "from", to[i].Args, "to", from[i].Args) to[i].Args = from[i].Args } if !DeepEqualWithNils(to[i].Env, from[i].Env) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Env", "from", to[i].Env, "to", from[i].Env) to[i].Env = from[i].Env } requireUpdate = CopyResources(&from[i].Resources, &to[i].Resources, containerBasePath+"Resources.", logger) || requireUpdate if !DeepEqualWithNils(to[i].VolumeMounts, from[i].VolumeMounts) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"VolumeMounts", "from", to[i].VolumeMounts, "to", from[i].VolumeMounts) to[i].VolumeMounts = from[i].VolumeMounts } if !DeepEqualWithNils(to[i].Ports, from[i].Ports) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Ports", "from", to[i].Ports, "to", from[i].Ports) to[i].Ports = from[i].Ports } if !DeepEqualWithNils(to[i].Lifecycle, from[i].Lifecycle) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Lifecycle", "from", to[i].Lifecycle, "to", from[i].Lifecycle) to[i].Lifecycle = from[i].Lifecycle } if !DeepEqualWithNils(to[i].LivenessProbe, from[i].LivenessProbe) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"LivenessProbe", "from", to[i].LivenessProbe, "to", from[i].LivenessProbe) to[i].LivenessProbe = from[i].LivenessProbe } if !DeepEqualWithNils(to[i].ReadinessProbe, from[i].ReadinessProbe) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"ReadinessProbe", "from", to[i].ReadinessProbe, "to", from[i].ReadinessProbe) to[i].ReadinessProbe = from[i].ReadinessProbe } if !DeepEqualWithNils(to[i].StartupProbe, from[i].StartupProbe) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"StartupProbe", "from", to[i].StartupProbe, "to", from[i].StartupProbe) to[i].StartupProbe = from[i].StartupProbe } if !DeepEqualWithNils(to[i].Lifecycle, from[i].Lifecycle) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Lifecycle", "from", to[i].Lifecycle, "to", from[i].Lifecycle) to[i].Lifecycle = from[i].Lifecycle } if from[i].TerminationMessagePath != "" && !DeepEqualWithNils(to[i].TerminationMessagePath, from[i].TerminationMessagePath) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"TerminationMessagePath", "from", to[i].TerminationMessagePath, "to", from[i].TerminationMessagePath) to[i].TerminationMessagePath = from[i].TerminationMessagePath } if from[i].TerminationMessagePolicy != "" && !DeepEqualWithNils(to[i].TerminationMessagePolicy, from[i].TerminationMessagePolicy) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"TerminationMessagePolicy", "from", to[i].TerminationMessagePolicy, "to", from[i].TerminationMessagePolicy) to[i].TerminationMessagePolicy = from[i].TerminationMessagePolicy } } } return requireUpdate } func CopyPodVolumes(fromPtr, toPtr *[]corev1.Volume, basePath string, logger logr.Logger) (requireUpdate bool) { to := *toPtr from := *fromPtr if len(to) != len(from) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from)) *toPtr = from } else { for i := 0; i < len(from); i++ { containerBasePath := basePath + "[" + strconv.Itoa(i) + "]." if !DeepEqualWithNils(to[i].Name, from[i].Name) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"Name", "from", to[i].Name, "to", from[i].Name) to[i].Name = from[i].Name } if !DeepEqualWithNils(to[i].VolumeSource, from[i].VolumeSource) { requireUpdate = true logger.Info("Update required because field changed", "field", containerBasePath+"VolumeSource", "from", to[i].VolumeSource, "to", from[i].VolumeSource) to[i].VolumeSource = from[i].VolumeSource } } } return requireUpdate } func CopyResources(from, to *corev1.ResourceRequirements, basePath string, logger logr.Logger) (requireUpdate bool) { requireUpdate = CopyContainerResourceList(&from.Requests, &to.Requests, basePath+"Requests", logger) || requireUpdate requireUpdate = CopyContainerResourceList(&from.Limits, &to.Limits, basePath+"Limits", logger) || requireUpdate return requireUpdate } func CopyContainerResourceList(fromPtr, toPtr *corev1.ResourceList, basePath string, logger logr.Logger) (requireUpdate bool) { to := *toPtr from := *fromPtr copyEntireMap := false if len(to) != len(from) { requireUpdate = true logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from)) copyEntireMap = true } else { for resourceName, newQuantity := range from { resourceBasePath := basePath + "[" + resourceName.String() + "]" if oldQuantity, isThere := to[resourceName]; isThere { if !oldQuantity.Equal(newQuantity) { requireUpdate = true logger.Info("Update required because field changed", "field", resourceBasePath, "from", oldQuantity, "to", newQuantity) to[resourceName] = newQuantity } } else { // This means the keys of the resource maps (from & to) are not the same, therefore update the entire map. // We will still loop through the rest of the resources to print out any additional updates required, // otherwise the logging could miss resource quantity changes. requireUpdate = true logger.Info("Update required because field changed", "field", resourceBasePath, "from", nil, "to", newQuantity) copyEntireMap = true } } } if copyEntireMap { *toPtr = from } return requireUpdate } // CopyPodDisruptionBudgetFields copies the owned fields from one PodDisruptionBudget to another func CopyPodDisruptionBudgetFields(from, to *policyv1.PodDisruptionBudget, logger logr.Logger) bool { logger = logger.WithValues("kind", "PodDisruptionBudget") requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger) if !DeepEqualWithNils(to.Spec.MinAvailable, from.Spec.MinAvailable) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.MinAvailable", "from", to.Spec.MinAvailable, "to", from.Spec.MinAvailable) to.Spec.MinAvailable = from.Spec.MinAvailable } if !DeepEqualWithNils(to.Spec.MaxUnavailable, from.Spec.MaxUnavailable) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.MaxUnavailable", "from", to.Spec.MaxUnavailable, "to", from.Spec.MaxUnavailable) to.Spec.MaxUnavailable = from.Spec.MaxUnavailable } if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) { requireUpdate = true logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector) to.Spec.Selector = from.Spec.Selector } return requireUpdate } // OvertakeControllerRef makes sure that the controlled object has the owner as the controller ref. // If the object has a different controller, then that ref will be downgraded to an "owner" and the new controller ref will be added func OvertakeControllerRef(owner metav1.Object, controlled metav1.Object, scheme *runtime.Scheme) (needsUpdate bool, err error) { if !metav1.IsControlledBy(controlled, owner) { if otherController := metav1.GetControllerOfNoCopy(controlled); otherController != nil { otherController.Controller = pointer.BoolPtr(false) otherController.BlockOwnerDeletion = pointer.BoolPtr(false) } err = controllerutil.SetControllerReference(owner, controlled, scheme) needsUpdate = true } return needsUpdate, err }