blob: a677c3111e644c2cf2a1451467994b855db3d223 [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 (
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) (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
log.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
log.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) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
// Don't copy the entire Spec, because we can't overwrite the clusterIp field
if !DeepEqualWithNils(to.Data, from.Data) {
requireUpdate = true
log.Info("Update required because:", "Data changed 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) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
// Don't copy the entire Spec, because we can't overwrite the clusterIp field
if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
requireUpdate = true
log.Info("Update required because:", "Spec.Selector changed from", to.Spec.Selector, "To:", from.Spec.Selector)
}
to.Spec.Selector = from.Spec.Selector
if !DeepEqualWithNils(to.Spec.Ports, from.Spec.Ports) {
requireUpdate = true
log.Info("Update required because:", "Spec.Ports changed from", to.Spec.Ports, "To:", from.Spec.Ports)
}
to.Spec.Ports = from.Spec.Ports
if !DeepEqualWithNils(to.Spec.ExternalName, from.Spec.ExternalName) {
requireUpdate = true
log.Info("Update required because:", "Spec.ExternalName changed from", to.Spec.ExternalName, "To:", from.Spec.ExternalName)
}
to.Spec.ExternalName = from.Spec.ExternalName
if !DeepEqualWithNils(to.Spec.PublishNotReadyAddresses, from.Spec.PublishNotReadyAddresses) {
requireUpdate = true
log.Info("Update required because:", "Spec.PublishNotReadyAddresses changed 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) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !DeepEqualWithNils(to.Spec.Rules, from.Spec.Rules) {
requireUpdate = true
log.Info("Update required because:", "Spec.Rules changed from", to.Spec.Rules, "To:", from.Spec.Rules)
}
to.Spec.Rules = from.Spec.Rules
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) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) {
requireUpdate = true
log.Info("Update required because:", "Spec.Replicas changed from", to.Spec.Replicas, "To:", from.Spec.Replicas)
to.Spec.Replicas = from.Spec.Replicas
}
if !DeepEqualWithNils(to.Spec.UpdateStrategy, from.Spec.UpdateStrategy) {
requireUpdate = true
log.Info("Update required because:", "Spec.UpdateStrategy changed 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
log.Info("Update required because:", "Spec.Selector changed from", to.Spec.Selector, "To:", from.Spec.Selector)
to.Spec.Selector = from.Spec.Selector
}
if !DeepEqualWithNils(to.Spec.PodManagementPolicy, from.Spec.PodManagementPolicy) {
requireUpdate = true
log.Info("Update required because:", "Spec.PodManagementPolicy changed 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
log.Info("Update required because:", "Spec.VolumeClaimTemplates changed from", to.Spec.VolumeClaimTemplates, "To:", from.Spec.VolumeClaimTemplates)
to.Spec.VolumeClaimTemplates = from.Spec.VolumeClaimTemplates
}
for i, fromVct := range from.Spec.VolumeClaimTemplates {
if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Name, fromVct.Name) {
requireUpdate = true
log.Info("Update required because:", "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"].Name changed from", to.Spec.VolumeClaimTemplates[i].Name, "To:", fromVct.Name)
to.Spec.VolumeClaimTemplates[i].Name = fromVct.Name
}
if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Labels, fromVct.Labels) {
requireUpdate = true
log.Info("Update required because:", "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"].Labels changed from", to.Spec.VolumeClaimTemplates[i].Labels, "To:", fromVct.Labels)
to.Spec.VolumeClaimTemplates[i].Labels = fromVct.Labels
}
if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Annotations, fromVct.Annotations) {
requireUpdate = true
log.Info("Update required because:", "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"].Annotations changed from", to.Spec.VolumeClaimTemplates[i].Annotations, "To:", fromVct.Annotations)
to.Spec.VolumeClaimTemplates[i].Annotations = fromVct.Annotations
}
if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Spec, fromVct.Spec) {
requireUpdate = true
log.Info("Update required because:", "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"].Spec changed from", to.Spec.VolumeClaimTemplates[i].Spec, "To:", fromVct.Spec)
to.Spec.VolumeClaimTemplates[i].Spec = fromVct.Spec
}
}
*/
requireUpdate = requireUpdate || CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template")
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) bool {
requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta)
if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) {
requireUpdate = true
log.Info("Update required because:", "Spec.Replicas changed from", to.Spec.Replicas, "To:", from.Spec.Replicas)
to.Spec.Replicas = from.Spec.Replicas
}
if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
requireUpdate = true
log.Info("Update required because:", "Spec.Selector changed 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")
return requireUpdate
}
func CopyPodTemplates(from, to *corev1.PodTemplateSpec, basePath string) (requireUpdate bool) {
if !DeepEqualWithNils(to.Labels, from.Labels) {
requireUpdate = true
log.Info("Update required because:", basePath+".Labels changed from", to.Labels, "To:", from.Labels)
to.Labels = from.Labels
}
if !DeepEqualWithNils(to.Annotations, from.Annotations) {
requireUpdate = true
log.Info("Update required because:", basePath+".Annotations changed from", to.Annotations, "To:", from.Annotations)
to.Annotations = from.Annotations
}
requireUpdate = requireUpdate || CopyPodContainers(from.Spec.Containers, to.Spec.Containers, basePath+".Spec.Containers")
requireUpdate = requireUpdate || CopyPodContainers(from.Spec.InitContainers, to.Spec.InitContainers, basePath+".Spec.InitContainers")
if !DeepEqualWithNils(to.Spec.HostAliases, from.Spec.HostAliases) {
requireUpdate = true
to.Spec.HostAliases = from.Spec.HostAliases
log.Info("Update required because:", basePath+".Spec.HostAliases changed from", to.Spec.HostAliases, "To:", from.Spec.HostAliases)
}
if !DeepEqualWithNils(to.Spec.Volumes, from.Spec.Volumes) {
requireUpdate = true
to.Spec.Volumes = from.Spec.Volumes
log.Info("Update required because:", basePath+".Spec.Volumes changed from", to.Spec.Volumes, "To:", from.Spec.Volumes)
}
if !DeepEqualWithNils(to.Spec.ImagePullSecrets, from.Spec.ImagePullSecrets) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.ImagePullSecrets changed from", to.Spec.ImagePullSecrets, "To:", from.Spec.ImagePullSecrets)
to.Spec.ImagePullSecrets = from.Spec.ImagePullSecrets
}
if !DeepEqualWithNils(to.Spec.Affinity, from.Spec.Affinity) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.Affinity changed from", to.Spec.Affinity, "To:", from.Spec.Affinity)
to.Spec.Affinity = from.Spec.Affinity
}
if !DeepEqualWithNils(to.Spec.SecurityContext, from.Spec.SecurityContext) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.SecurityContext changed from", to.Spec.SecurityContext, "To:", from.Spec.SecurityContext)
to.Spec.SecurityContext = from.Spec.SecurityContext
}
if !DeepEqualWithNils(to.Spec.NodeSelector, from.Spec.NodeSelector) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.NodeSelector changed from", to.Spec.NodeSelector, "To:", from.Spec.NodeSelector)
to.Spec.NodeSelector = from.Spec.NodeSelector
}
if !DeepEqualWithNils(to.Spec.Tolerations, from.Spec.Tolerations) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.Tolerations changed from", to.Spec.Tolerations, "To:", from.Spec.Tolerations)
to.Spec.Tolerations = from.Spec.Tolerations
}
if !DeepEqualWithNils(to.Spec.PriorityClassName, from.Spec.PriorityClassName) {
requireUpdate = true
log.Info("Update required because:", basePath+".Spec.PriorityClassName changed from", to.Spec.PriorityClassName, "To:", from.Spec.PriorityClassName)
to.Spec.PriorityClassName = from.Spec.PriorityClassName
}
return requireUpdate
}
func CopyPodContainers(from, to []corev1.Container, basePath string) (requireUpdate bool) {
if len(to) < len(from) {
requireUpdate = true
to = from
} else {
for i := 0; i < len(from); i++ {
if !DeepEqualWithNils(to[i].Name, from[i].Name) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Name changed from", to[i].Name, "To:", from[i].Name)
to[i].Name = from[i].Name
}
if !DeepEqualWithNils(to[i].Image, from[i].Image) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Image changed 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
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].ImagePullPolicy changed from", to[i].ImagePullPolicy, "To:", from[i].ImagePullPolicy)
to[i].ImagePullPolicy = from[i].ImagePullPolicy
}
if !DeepEqualWithNils(to[i].Command, from[i].Command) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Command changed from", to[i].Command, "To:", from[i].Command)
to[i].Command = from[i].Command
}
if !DeepEqualWithNils(to[i].Args, from[i].Args) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Args changed from", to[i].Args, "To:", from[i].Args)
to[i].Args = from[i].Args
}
if !DeepEqualWithNils(to[i].Env, from[i].Env) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Env changed from", to[i].Env, "To:", from[i].Env)
to[i].Env = from[i].Env
}
if !DeepEqualWithNils(to[i].Resources, from[i].Resources) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Resources changed from", to[i].Resources, "To:", from[i].Resources)
to[i].Resources = from[i].Resources
}
if !DeepEqualWithNils(to[i].VolumeMounts, from[i].VolumeMounts) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].VolumeMounts changed from", to[i].VolumeMounts, "To:", from[i].VolumeMounts)
to[i].VolumeMounts = from[i].VolumeMounts
}
if !DeepEqualWithNils(to[i].Ports, from[i].Ports) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Ports changed from", to[i].Ports, "To:", from[i].Ports)
to[i].Ports = from[i].Ports
}
if !DeepEqualWithNils(to[i].Lifecycle, from[i].Lifecycle) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].Lifecycle changed from", to[i].Lifecycle, "To:", from[i].Lifecycle)
to[i].Lifecycle = from[i].Lifecycle
}
if !DeepEqualWithNils(to[i].LivenessProbe, from[i].LivenessProbe) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].LivenessProbe changed from", to[i].LivenessProbe, "To:", from[i].LivenessProbe)
to[i].LivenessProbe = from[i].LivenessProbe
}
if !DeepEqualWithNils(to[i].ReadinessProbe, from[i].ReadinessProbe) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].ReadinessProbe changed from", to[i].ReadinessProbe, "To:", from[i].ReadinessProbe)
to[i].LivenessProbe = from[i].ReadinessProbe
}
if !DeepEqualWithNils(to[i].StartupProbe, from[i].StartupProbe) {
requireUpdate = true
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].StartupProbe changed 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
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].TerminationMessagePath changed 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
log.Info("Update required because:", basePath+".Containers["+strconv.Itoa(i)+")].TerminationMessagePolicy changed from", to[i].TerminationMessagePolicy, "To:", from[i].TerminationMessagePolicy)
to[i].TerminationMessagePolicy = from[i].TerminationMessagePolicy
}
}
}
return requireUpdate
}