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(¤t.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(¤t.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(¤t.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(¤tVersion, &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(¤tVersion, &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(¤tVersion, &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(¤tVersion, &desiredVersion) { 179 if serviceRequiresReCreate(¤tVersion, &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 }