github.com/docker/compose-on-kubernetes@v0.5.0/internal/stackresources/diff/stackstatediff.go (about)

     1  package diff
     2  
     3  import (
     4  	"reflect"
     5  	"strings"
     6  
     7  	"github.com/docker/compose-on-kubernetes/internal/stackresources"
     8  	log "github.com/sirupsen/logrus"
     9  	appstypes "k8s.io/api/apps/v1"
    10  	coretypes "k8s.io/api/core/v1"
    11  )
    12  
    13  const (
    14  	ingnoredTolerationPrefix = "com.docker.ucp."
    15  )
    16  
    17  // StackStateDiff is a diff between a current and desired state
    18  type StackStateDiff struct {
    19  	DeploymentsToAdd     []appstypes.Deployment
    20  	DeploymentsToDelete  []appstypes.Deployment
    21  	DeploymentsToUpdate  []appstypes.Deployment
    22  	StatefulsetsToAdd    []appstypes.StatefulSet
    23  	StatefulsetsToDelete []appstypes.StatefulSet
    24  	StatefulsetsToUpdate []appstypes.StatefulSet
    25  	DaemonsetsToAdd      []appstypes.DaemonSet
    26  	DaemonsetsToDelete   []appstypes.DaemonSet
    27  	DaemonsetsToUpdate   []appstypes.DaemonSet
    28  	ServicesToAdd        []coretypes.Service
    29  	ServicesToDelete     []coretypes.Service
    30  	ServicesToUpdate     []coretypes.Service
    31  }
    32  
    33  //Empty returns true if the diff is empty
    34  func (d *StackStateDiff) Empty() bool {
    35  	return len(d.DeploymentsToAdd) == 0 &&
    36  		len(d.DeploymentsToDelete) == 0 &&
    37  		len(d.DeploymentsToUpdate) == 0 &&
    38  		len(d.StatefulsetsToAdd) == 0 &&
    39  		len(d.StatefulsetsToDelete) == 0 &&
    40  		len(d.StatefulsetsToUpdate) == 0 &&
    41  		len(d.DaemonsetsToAdd) == 0 &&
    42  		len(d.DaemonsetsToDelete) == 0 &&
    43  		len(d.DaemonsetsToUpdate) == 0 &&
    44  		len(d.ServicesToAdd) == 0 &&
    45  		len(d.ServicesToDelete) == 0 &&
    46  		len(d.ServicesToUpdate) == 0
    47  }
    48  
    49  func normalizeTolerationsForEquality(spec *coretypes.PodSpec) {
    50  	var tolerations []coretypes.Toleration
    51  	for _, t := range spec.Tolerations {
    52  		if !strings.HasPrefix(t.Key, ingnoredTolerationPrefix) {
    53  			tolerations = append(tolerations, t)
    54  		}
    55  	}
    56  	spec.Tolerations = tolerations
    57  }
    58  
    59  func normalizePodSpecForEquality(current, desired *coretypes.PodSpec) {
    60  	// normalize tolerations
    61  	normalizeTolerationsForEquality(current)
    62  	normalizeTolerationsForEquality(desired)
    63  
    64  	// normalize potentially DCT patched images
    65  	if len(desired.InitContainers) == len(current.InitContainers) &&
    66  		len(desired.Containers) == len(current.Containers) {
    67  		for ix := range desired.InitContainers {
    68  			currentImage := current.InitContainers[ix].Image
    69  			desiredImage := desired.InitContainers[ix].Image
    70  			if strings.HasPrefix(currentImage, desiredImage+"@") {
    71  				desired.InitContainers[ix].Image = currentImage
    72  			}
    73  		}
    74  		for ix := range desired.Containers {
    75  			currentImage := current.Containers[ix].Image
    76  			desiredImage := desired.Containers[ix].Image
    77  			if strings.HasPrefix(currentImage, desiredImage+"@") {
    78  				desired.Containers[ix].Image = currentImage
    79  			}
    80  		}
    81  	}
    82  }
    83  
    84  func normalizeEqualDeployment(current, desired *appstypes.Deployment) bool {
    85  	current = current.DeepCopy()
    86  	desired = desired.DeepCopy()
    87  	normalizePodSpecForEquality(&current.Spec.Template.Spec, &desired.Spec.Template.Spec)
    88  	return reflect.DeepEqual(current.Spec, desired.Spec) && reflect.DeepEqual(current.Labels, desired.Labels)
    89  }
    90  
    91  func normalizeEqualStatefulset(current, desired *appstypes.StatefulSet) bool {
    92  	current = current.DeepCopy()
    93  	desired = desired.DeepCopy()
    94  	normalizePodSpecForEquality(&current.Spec.Template.Spec, &desired.Spec.Template.Spec)
    95  	return reflect.DeepEqual(current.Spec, desired.Spec) && reflect.DeepEqual(current.Labels, desired.Labels)
    96  }
    97  
    98  func normalizeEqualDaemonset(current, desired *appstypes.DaemonSet) bool {
    99  	current = current.DeepCopy()
   100  	desired = desired.DeepCopy()
   101  	normalizePodSpecForEquality(&current.Spec.Template.Spec, &desired.Spec.Template.Spec)
   102  	return reflect.DeepEqual(current.Spec, desired.Spec) && reflect.DeepEqual(current.Labels, desired.Labels)
   103  }
   104  
   105  func normalizeEqualService(current, desired *coretypes.Service) bool {
   106  	current = current.DeepCopy()
   107  	desired = desired.DeepCopy()
   108  	return reflect.DeepEqual(current.Spec, desired.Spec) && reflect.DeepEqual(current.Labels, desired.Labels)
   109  }
   110  
   111  func serviceRequiresReCreate(current, desired *coretypes.Service) bool {
   112  	if current.Spec.Type != coretypes.ServiceTypeExternalName &&
   113  		desired.Spec.Type != coretypes.ServiceTypeExternalName &&
   114  		current.Spec.ClusterIP != "" {
   115  		// once a cluster IP is assigned to a service, it cannot be changed (except if changing type from/to external name)
   116  		return current.Spec.ClusterIP != desired.Spec.ClusterIP
   117  	}
   118  	return false
   119  }
   120  
   121  func computeDeploymentsDiff(current, desired *stackresources.StackState, result *StackStateDiff) {
   122  	for k, desiredVersion := range desired.Deployments {
   123  		if currentVersion, ok := current.Deployments[k]; ok {
   124  			if !normalizeEqualDeployment(&currentVersion, &desiredVersion) {
   125  				desiredVersion.ResourceVersion = currentVersion.ResourceVersion
   126  				result.DeploymentsToUpdate = append(result.DeploymentsToUpdate, desiredVersion)
   127  			}
   128  		} else {
   129  			result.DeploymentsToAdd = append(result.DeploymentsToAdd, desiredVersion)
   130  		}
   131  	}
   132  	for k, v := range current.Deployments {
   133  		if _, ok := desired.Deployments[k]; !ok {
   134  			result.DeploymentsToDelete = append(result.DeploymentsToDelete, v)
   135  		}
   136  	}
   137  }
   138  
   139  func computeStatefulsetsDiff(current, desired *stackresources.StackState, result *StackStateDiff) {
   140  	for k, desiredVersion := range desired.Statefulsets {
   141  		if currentVersion, ok := current.Statefulsets[k]; ok {
   142  			if !normalizeEqualStatefulset(&currentVersion, &desiredVersion) {
   143  				desiredVersion.ResourceVersion = currentVersion.ResourceVersion
   144  				result.StatefulsetsToUpdate = append(result.StatefulsetsToUpdate, desiredVersion)
   145  			}
   146  		} else {
   147  			result.StatefulsetsToAdd = append(result.StatefulsetsToAdd, desiredVersion)
   148  		}
   149  	}
   150  	for k, v := range current.Statefulsets {
   151  		if _, ok := desired.Statefulsets[k]; !ok {
   152  			result.StatefulsetsToDelete = append(result.StatefulsetsToDelete, v)
   153  		}
   154  	}
   155  }
   156  
   157  func computeDaemonsetsDiff(current, desired *stackresources.StackState, result *StackStateDiff) {
   158  	for k, desiredVersion := range desired.Daemonsets {
   159  		if currentVersion, ok := current.Daemonsets[k]; ok {
   160  			if !normalizeEqualDaemonset(&currentVersion, &desiredVersion) {
   161  				desiredVersion.ResourceVersion = currentVersion.ResourceVersion
   162  				result.DaemonsetsToUpdate = append(result.DaemonsetsToUpdate, desiredVersion)
   163  			}
   164  		} else {
   165  			result.DaemonsetsToAdd = append(result.DaemonsetsToAdd, desiredVersion)
   166  		}
   167  	}
   168  	for k, v := range current.Daemonsets {
   169  		if _, ok := desired.Daemonsets[k]; !ok {
   170  			result.DaemonsetsToDelete = append(result.DaemonsetsToDelete, v)
   171  		}
   172  	}
   173  }
   174  
   175  func computeServicesDiff(current, desired *stackresources.StackState, result *StackStateDiff) {
   176  	for k, desiredVersion := range desired.Services {
   177  		if currentVersion, ok := current.Services[k]; ok {
   178  			if !normalizeEqualService(&currentVersion, &desiredVersion) {
   179  				if serviceRequiresReCreate(&currentVersion, &desiredVersion) {
   180  					result.ServicesToDelete = append(result.ServicesToDelete, currentVersion)
   181  					result.ServicesToAdd = append(result.ServicesToAdd, desiredVersion)
   182  				} else {
   183  					desiredVersion.ResourceVersion = currentVersion.ResourceVersion
   184  					result.ServicesToUpdate = append(result.ServicesToUpdate, desiredVersion)
   185  				}
   186  			}
   187  		} else {
   188  			result.ServicesToAdd = append(result.ServicesToAdd, desiredVersion)
   189  		}
   190  	}
   191  	for k, v := range current.Services {
   192  		if _, ok := desired.Services[k]; !ok {
   193  			result.ServicesToDelete = append(result.ServicesToDelete, v)
   194  		}
   195  	}
   196  }
   197  
   198  // ComputeDiff computes a diff between a current and a desired stack state
   199  func ComputeDiff(current, desired *stackresources.StackState) *StackStateDiff {
   200  	result := &StackStateDiff{}
   201  	computeDeploymentsDiff(current, desired, result)
   202  	computeStatefulsetsDiff(current, desired, result)
   203  	computeDaemonsetsDiff(current, desired, result)
   204  	computeServicesDiff(current, desired, result)
   205  
   206  	log.Debugf("produced stack state diff %#v", result)
   207  	return result
   208  }