github.com/docker/compose-on-kubernetes@v0.5.0/internal/convert/pod.go (about) 1 package convert 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/docker/compose-on-kubernetes/api/compose/latest" 10 "github.com/docker/docker/api/types/swarm" 11 "github.com/pkg/errors" 12 apiv1 "k8s.io/api/core/v1" 13 "k8s.io/apimachinery/pkg/api/resource" 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 ) 16 17 func toPodTemplate(serviceConfig latest.ServiceConfig, labels map[string]string, configuration *latest.StackSpec, original apiv1.PodTemplateSpec) (apiv1.PodTemplateSpec, error) { 18 tpl := *original.DeepCopy() 19 nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy.Placement.Constraints) 20 if err != nil { 21 return apiv1.PodTemplateSpec{}, err 22 } 23 hostAliases, err := toHostAliases(serviceConfig.ExtraHosts) 24 if err != nil { 25 return apiv1.PodTemplateSpec{}, err 26 } 27 env, err := toEnv(serviceConfig.Environment) 28 if err != nil { 29 return apiv1.PodTemplateSpec{}, err 30 } 31 restartPolicy, err := toRestartPolicy(serviceConfig, tpl.Spec.RestartPolicy) 32 if err != nil { 33 return apiv1.PodTemplateSpec{}, err 34 } 35 limits, err := toResource(serviceConfig.Deploy.Resources.Limits) 36 if err != nil { 37 return apiv1.PodTemplateSpec{}, err 38 } 39 requests, err := toResource(serviceConfig.Deploy.Resources.Reservations) 40 if err != nil { 41 return apiv1.PodTemplateSpec{}, err 42 } 43 volumes, err := toVolumes(serviceConfig, configuration) 44 if err != nil { 45 return apiv1.PodTemplateSpec{}, err 46 } 47 volumeMounts, err := toVolumeMounts(serviceConfig, configuration) 48 if err != nil { 49 return apiv1.PodTemplateSpec{}, err 50 } 51 pullPolicy, err := toImagePullPolicy(serviceConfig.Image, serviceConfig.PullPolicy) 52 if err != nil { 53 return apiv1.PodTemplateSpec{}, err 54 } 55 tpl.ObjectMeta = metav1.ObjectMeta{ 56 Labels: labels, 57 Annotations: serviceConfig.Labels, 58 } 59 tpl.Spec.RestartPolicy = restartPolicy 60 tpl.Spec.Volumes = volumes 61 tpl.Spec.HostPID = toHostPID(serviceConfig.Pid) 62 tpl.Spec.HostIPC = toHostIPC(serviceConfig.Ipc) 63 tpl.Spec.Hostname = serviceConfig.Hostname 64 tpl.Spec.TerminationGracePeriodSeconds = toTerminationGracePeriodSeconds(serviceConfig.StopGracePeriod, tpl.Spec.TerminationGracePeriodSeconds) 65 tpl.Spec.HostAliases = hostAliases 66 tpl.Spec.Affinity = nodeAffinity 67 // we dont want to remove all containers and recreate them because: 68 // an admission plugin can add sidecar containers 69 // we for sure want to keep the main container to be additive 70 if len(tpl.Spec.Containers) == 0 { 71 tpl.Spec.Containers = []apiv1.Container{{}} 72 } 73 74 containerIX := 0 75 for ix, c := range tpl.Spec.Containers { 76 if c.Name == serviceConfig.Name { 77 containerIX = ix 78 break 79 } 80 } 81 tpl.Spec.Containers[containerIX].Name = serviceConfig.Name 82 tpl.Spec.Containers[containerIX].Image = serviceConfig.Image 83 tpl.Spec.Containers[containerIX].ImagePullPolicy = pullPolicy 84 tpl.Spec.Containers[containerIX].Command = serviceConfig.Entrypoint 85 tpl.Spec.Containers[containerIX].Args = serviceConfig.Command 86 tpl.Spec.Containers[containerIX].WorkingDir = serviceConfig.WorkingDir 87 tpl.Spec.Containers[containerIX].TTY = serviceConfig.Tty 88 tpl.Spec.Containers[containerIX].Stdin = serviceConfig.StdinOpen 89 tpl.Spec.Containers[containerIX].Ports = toPorts(serviceConfig.Ports) 90 tpl.Spec.Containers[containerIX].LivenessProbe = toLivenessProbe(serviceConfig.HealthCheck) 91 tpl.Spec.Containers[containerIX].Env = env 92 tpl.Spec.Containers[containerIX].VolumeMounts = volumeMounts 93 tpl.Spec.Containers[containerIX].SecurityContext = toSecurityContext(serviceConfig) 94 tpl.Spec.Containers[containerIX].Resources = apiv1.ResourceRequirements{ 95 Limits: limits, 96 Requests: requests, 97 } 98 99 if serviceConfig.PullSecret != "" { 100 pullSecrets := map[string]struct{}{} 101 for _, ps := range tpl.Spec.ImagePullSecrets { 102 pullSecrets[ps.Name] = struct{}{} 103 } 104 if _, ok := pullSecrets[serviceConfig.PullSecret]; !ok { 105 tpl.Spec.ImagePullSecrets = append(tpl.Spec.ImagePullSecrets, apiv1.LocalObjectReference{Name: serviceConfig.PullSecret}) 106 } 107 } 108 return tpl, nil 109 } 110 111 func toImagePullPolicy(image string, specifiedPolicy string) (apiv1.PullPolicy, error) { 112 if specifiedPolicy == "" { 113 if strings.HasSuffix(image, ":latest") { 114 return apiv1.PullAlways, nil 115 } 116 return apiv1.PullIfNotPresent, nil 117 } 118 switch apiv1.PullPolicy(specifiedPolicy) { 119 case apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever: 120 return apiv1.PullPolicy(specifiedPolicy), nil 121 default: 122 return "", errors.Errorf("invalid pull policy %q, must be %q, %q or %q", specifiedPolicy, apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever) 123 } 124 } 125 126 func toHostAliases(extraHosts []string) ([]apiv1.HostAlias, error) { 127 if extraHosts == nil { 128 return nil, nil 129 } 130 131 byHostnames := map[string]string{} 132 for _, host := range extraHosts { 133 split := strings.SplitN(host, ":", 2) 134 if len(split) != 2 { 135 return nil, errors.Errorf("malformed host %s", host) 136 } 137 byHostnames[split[0]] = split[1] 138 } 139 140 byIPs := map[string][]string{} 141 for k, v := range byHostnames { 142 byIPs[v] = append(byIPs[v], k) 143 } 144 145 aliases := make([]apiv1.HostAlias, len(byIPs)) 146 i := 0 147 for key, hosts := range byIPs { 148 sort.Strings(hosts) 149 aliases[i] = apiv1.HostAlias{ 150 IP: key, 151 Hostnames: hosts, 152 } 153 i++ 154 } 155 sort.Slice(aliases, func(i, j int) bool { return aliases[i].IP < aliases[j].IP }) 156 return aliases, nil 157 } 158 159 func toHostPID(pid string) bool { 160 return "host" == pid 161 } 162 163 func toHostIPC(ipc string) bool { 164 return "host" == ipc 165 } 166 167 func toTerminationGracePeriodSeconds(duration *time.Duration, original *int64) *int64 { 168 if duration == nil { 169 return original 170 } 171 gracePeriod := int64(duration.Seconds()) 172 return &gracePeriod 173 } 174 175 func toLivenessProbe(hc *latest.HealthCheckConfig) *apiv1.Probe { 176 if hc == nil || len(hc.Test) < 1 || hc.Test[0] == "NONE" { 177 return nil 178 } 179 180 command := hc.Test[1:] 181 if hc.Test[0] == "CMD-SHELL" { 182 command = append([]string{"sh", "-c"}, command...) 183 } 184 185 return &apiv1.Probe{ 186 TimeoutSeconds: toSecondsOrDefault(hc.Timeout, 1), 187 PeriodSeconds: toSecondsOrDefault(hc.Interval, 1), 188 FailureThreshold: int32(defaultUint64(hc.Retries, 3)), 189 Handler: apiv1.Handler{ 190 Exec: &apiv1.ExecAction{ 191 Command: command, 192 }, 193 }, 194 } 195 } 196 197 func toEnv(env map[string]*string) ([]apiv1.EnvVar, error) { 198 var envVars []apiv1.EnvVar 199 200 for k, v := range env { 201 if v == nil { 202 return nil, errors.Errorf("%s has no value, unsetting an environment variable is not supported", k) 203 } 204 envVars = append(envVars, toEnvVar(k, *v)) 205 } 206 sort.Slice(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name }) 207 return envVars, nil 208 } 209 210 func toEnvVar(key, value string) apiv1.EnvVar { 211 return apiv1.EnvVar{ 212 Name: key, 213 Value: value, 214 } 215 } 216 217 func toPorts(list []latest.ServicePortConfig) []apiv1.ContainerPort { 218 var ports []apiv1.ContainerPort 219 220 for _, v := range list { 221 ports = append(ports, apiv1.ContainerPort{ 222 ContainerPort: int32(v.Target), 223 Protocol: toProtocol(v.Protocol), 224 }) 225 } 226 227 return ports 228 } 229 230 func toProtocol(value string) apiv1.Protocol { 231 if value == "udp" { 232 return apiv1.ProtocolUDP 233 } 234 return apiv1.ProtocolTCP 235 } 236 237 func toRestartPolicy(s latest.ServiceConfig, original apiv1.RestartPolicy) (apiv1.RestartPolicy, error) { 238 policy := s.Deploy.RestartPolicy 239 if policy == nil { 240 return original, nil 241 } 242 243 switch policy.Condition { 244 case string(swarm.RestartPolicyConditionAny): 245 return apiv1.RestartPolicyAlways, nil 246 case string(swarm.RestartPolicyConditionNone): 247 return apiv1.RestartPolicyNever, nil 248 case string(swarm.RestartPolicyConditionOnFailure): 249 return apiv1.RestartPolicyOnFailure, nil 250 default: 251 return "", errors.Errorf("unsupported restart policy %s", policy.Condition) 252 } 253 } 254 255 func toResource(res *latest.Resource) (apiv1.ResourceList, error) { 256 if res == nil { 257 return nil, nil 258 } 259 list := make(apiv1.ResourceList) 260 if res.NanoCPUs != "" { 261 cpus, err := resource.ParseQuantity(res.NanoCPUs) 262 if err != nil { 263 return nil, err 264 } 265 list[apiv1.ResourceCPU] = cpus 266 } 267 if res.MemoryBytes != 0 { 268 memory, err := resource.ParseQuantity(fmt.Sprintf("%v", res.MemoryBytes)) 269 if err != nil { 270 return nil, err 271 } 272 list[apiv1.ResourceMemory] = memory 273 } 274 return list, nil 275 } 276 277 func toSecurityContext(s latest.ServiceConfig) *apiv1.SecurityContext { 278 isPrivileged := toBoolPointer(s.Privileged) 279 isReadOnly := toBoolPointer(s.ReadOnly) 280 281 var capabilities *apiv1.Capabilities 282 if s.CapAdd != nil || s.CapDrop != nil { 283 capabilities = &apiv1.Capabilities{ 284 Add: toCapabilities(s.CapAdd), 285 Drop: toCapabilities(s.CapDrop), 286 } 287 } 288 289 if isPrivileged == nil && isReadOnly == nil && capabilities == nil && s.User == nil { 290 return nil 291 } 292 293 return &apiv1.SecurityContext{ 294 RunAsUser: s.User, 295 Privileged: isPrivileged, 296 ReadOnlyRootFilesystem: isReadOnly, 297 Capabilities: capabilities, 298 } 299 } 300 301 func toBoolPointer(value bool) *bool { 302 if value { 303 return &value 304 } 305 306 return nil 307 } 308 309 func defaultUint64(v *uint64, defaultValue uint64) uint64 { //nolint: unparam 310 if v == nil { 311 return defaultValue 312 } 313 314 return *v 315 } 316 317 func toCapabilities(list []string) (capabilities []apiv1.Capability) { 318 for _, c := range list { 319 capabilities = append(capabilities, apiv1.Capability(c)) 320 } 321 return 322 } 323 324 //nolint: unparam 325 func forceRestartPolicy(podTemplate apiv1.PodTemplateSpec, forcedRestartPolicy apiv1.RestartPolicy) apiv1.PodTemplateSpec { 326 if podTemplate.Spec.RestartPolicy != "" { 327 podTemplate.Spec.RestartPolicy = forcedRestartPolicy 328 } 329 330 return podTemplate 331 }