blob: da0681d95fd48972dbc3e63fd63446b715b74b65 [file] [log] [blame]
/*
Copyright 2019 Bloomberg Finance LP.
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 util
import (
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
extv1 "k8s.io/api/extensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"reflect"
"strconv"
"strings"
)
// 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
}
// 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 *extv1.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.ServiceName, fromPath.Backend.ServiceName) {
requireUpdate = true
logger.Info("Update required because field changed", "field", pathBase+"Backend.ServiceName", "from", toPath.Backend.ServiceName, "to", fromPath.Backend.ServiceName)
toPath.Backend.ServiceName = fromPath.Backend.ServiceName
}
if !DeepEqualWithNils(toPath.Backend.ServicePort, fromPath.Backend.ServicePort) {
requireUpdate = true
logger.Info("Update required because field changed", "field", pathBase+"Backend.ServicePort", "from", toPath.Backend.ServicePort, "to", fromPath.Backend.ServicePort)
toPath.Backend.ServicePort = fromPath.Backend.ServicePort
}
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
}
}
}
}
}
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 = requireUpdate || CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger)
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 = requireUpdate || CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger)
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 = 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)
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.Volumes, from.Spec.Volumes) {
requireUpdate = true
to.Spec.Volumes = from.Spec.Volumes
logger.Info("Update required because field changed", "field", basePath+"Spec.Volumes", "from", to.Spec.Volumes, "to", from.Spec.Volumes)
}
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
}
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
*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
}
if !DeepEqualWithNils(to[i].Resources, from[i].Resources) {
requireUpdate = true
logger.Info("Update required because field changed", "field", containerBasePath+"Resources", "from", to[i].Resources, "to", from[i].Resources)
to[i].Resources = from[i].Resources
}
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].LivenessProbe = 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 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
}