| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- // +build kube
- /*
- Copyright 2020 Docker Compose CLI authors
- 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 kubernetes
- import (
- "fmt"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/compose-spec/compose-go/types"
- "github.com/docker/docker/api/types/swarm"
- "github.com/pkg/errors"
- apiv1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- )
- func toPodTemplate(project *types.Project, serviceConfig types.ServiceConfig, labels map[string]string) (apiv1.PodTemplateSpec, error) {
- tpl := apiv1.PodTemplateSpec{}
- //nodeAffinity, err := toNodeAffinity(serviceConfig.Deploy)
- //if err != nil {
- // return apiv1.PodTemplateSpec{}, err
- //}
- hostAliases, err := toHostAliases(serviceConfig.ExtraHosts)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- env, err := toEnv(serviceConfig.Environment)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- restartPolicy, err := toRestartPolicy(serviceConfig)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- var limits apiv1.ResourceList
- if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Limits != nil {
- limits, err = toResource(serviceConfig.Deploy.Resources.Limits)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- }
- var requests apiv1.ResourceList
- if serviceConfig.Deploy != nil && serviceConfig.Deploy.Resources.Reservations != nil {
- requests, err = toResource(serviceConfig.Deploy.Resources.Reservations)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- }
- volumes, err := toVolumes(project, serviceConfig)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- volumeMounts, err := toVolumeMounts(project, serviceConfig)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- }
- /* pullPolicy, err := toImagePullPolicy(serviceConfig.Image, x-kubernetes-pull-policy)
- if err != nil {
- return apiv1.PodTemplateSpec{}, err
- } */
- tpl.ObjectMeta = metav1.ObjectMeta{
- Labels: labels,
- Annotations: serviceConfig.Labels,
- }
- tpl.Spec.RestartPolicy = restartPolicy
- tpl.Spec.Volumes = volumes
- tpl.Spec.HostPID = toHostPID(serviceConfig.Pid)
- tpl.Spec.HostIPC = toHostIPC(serviceConfig.Ipc)
- tpl.Spec.Hostname = serviceConfig.Hostname
- tpl.Spec.TerminationGracePeriodSeconds = toTerminationGracePeriodSeconds(serviceConfig.StopGracePeriod)
- tpl.Spec.HostAliases = hostAliases
- //tpl.Spec.Affinity = nodeAffinity
- // we dont want to remove all containers and recreate them because:
- // an admission plugin can add sidecar containers
- // we for sure want to keep the main container to be additive
- if len(tpl.Spec.Containers) == 0 {
- tpl.Spec.Containers = []apiv1.Container{{}}
- }
- containerIX := 0
- for ix, c := range tpl.Spec.Containers {
- if c.Name == serviceConfig.Name {
- containerIX = ix
- break
- }
- }
- tpl.Spec.Containers[containerIX].Name = serviceConfig.Name
- tpl.Spec.Containers[containerIX].Image = serviceConfig.Image
- // FIXME tpl.Spec.Containers[containerIX].ImagePullPolicy = pullPolicy
- tpl.Spec.Containers[containerIX].Command = serviceConfig.Entrypoint
- tpl.Spec.Containers[containerIX].Args = serviceConfig.Command
- tpl.Spec.Containers[containerIX].WorkingDir = serviceConfig.WorkingDir
- tpl.Spec.Containers[containerIX].TTY = serviceConfig.Tty
- tpl.Spec.Containers[containerIX].Stdin = serviceConfig.StdinOpen
- tpl.Spec.Containers[containerIX].Ports = toPorts(serviceConfig.Ports)
- tpl.Spec.Containers[containerIX].LivenessProbe = toLivenessProbe(serviceConfig.HealthCheck)
- tpl.Spec.Containers[containerIX].Env = env
- tpl.Spec.Containers[containerIX].VolumeMounts = volumeMounts
- tpl.Spec.Containers[containerIX].SecurityContext = toSecurityContext(serviceConfig)
- tpl.Spec.Containers[containerIX].Resources = apiv1.ResourceRequirements{
- Limits: limits,
- Requests: requests,
- }
- /* FIXME
- if serviceConfig.PullSecret != "" {
- pullSecrets := map[string]struct{}{}
- for _, ps := range tpl.Spec.ImagePullSecrets {
- pullSecrets[ps.Name] = struct{}{}
- }
- if _, ok := pullSecrets[serviceConfig.PullSecret]; !ok {
- tpl.Spec.ImagePullSecrets = append(tpl.Spec.ImagePullSecrets, apiv1.LocalObjectReference{Name: serviceConfig.PullSecret})
- }
- }
- */
- return tpl, nil
- }
- func toHostAliases(extraHosts []string) ([]apiv1.HostAlias, error) {
- if extraHosts == nil {
- return nil, nil
- }
- byHostnames := map[string]string{}
- for _, host := range extraHosts {
- split := strings.SplitN(host, ":", 2)
- if len(split) != 2 {
- return nil, errors.Errorf("malformed host %s", host)
- }
- byHostnames[split[0]] = split[1]
- }
- byIPs := map[string][]string{}
- for k, v := range byHostnames {
- byIPs[v] = append(byIPs[v], k)
- }
- aliases := make([]apiv1.HostAlias, len(byIPs))
- i := 0
- for key, hosts := range byIPs {
- sort.Strings(hosts)
- aliases[i] = apiv1.HostAlias{
- IP: key,
- Hostnames: hosts,
- }
- i++
- }
- sort.Slice(aliases, func(i, j int) bool { return aliases[i].IP < aliases[j].IP })
- return aliases, nil
- }
- func toHostPID(pid string) bool {
- return "host" == pid
- }
- func toHostIPC(ipc string) bool {
- return "host" == ipc
- }
- func toTerminationGracePeriodSeconds(duration *types.Duration) *int64 {
- if duration == nil {
- return nil
- }
- gracePeriod := int64(time.Duration(*duration).Seconds())
- return &gracePeriod
- }
- func toLivenessProbe(hc *types.HealthCheckConfig) *apiv1.Probe {
- if hc == nil || len(hc.Test) < 1 || hc.Test[0] == "NONE" {
- return nil
- }
- command := hc.Test[1:]
- if hc.Test[0] == "CMD-SHELL" {
- command = append([]string{"sh", "-c"}, command...)
- }
- return &apiv1.Probe{
- TimeoutSeconds: toSecondsOrDefault(hc.Timeout, 1),
- PeriodSeconds: toSecondsOrDefault(hc.Interval, 1),
- FailureThreshold: int32(defaultUint64(hc.Retries, 3)),
- Handler: apiv1.Handler{
- Exec: &apiv1.ExecAction{
- Command: command,
- },
- },
- }
- }
- func toEnv(env map[string]*string) ([]apiv1.EnvVar, error) {
- var envVars []apiv1.EnvVar
- for k, v := range env {
- if v == nil {
- return nil, errors.Errorf("%s has no value, unsetting an environment variable is not supported", k)
- }
- envVars = append(envVars, toEnvVar(k, *v))
- }
- sort.Slice(envVars, func(i, j int) bool { return envVars[i].Name < envVars[j].Name })
- return envVars, nil
- }
- func toEnvVar(key, value string) apiv1.EnvVar {
- return apiv1.EnvVar{
- Name: key,
- Value: value,
- }
- }
- func toPorts(list []types.ServicePortConfig) []apiv1.ContainerPort {
- var ports []apiv1.ContainerPort
- for _, v := range list {
- ports = append(ports, apiv1.ContainerPort{
- ContainerPort: int32(v.Target),
- Protocol: toProtocol(v.Protocol),
- })
- }
- return ports
- }
- func toProtocol(value string) apiv1.Protocol {
- if value == "udp" {
- return apiv1.ProtocolUDP
- }
- return apiv1.ProtocolTCP
- }
- func toRestartPolicy(s types.ServiceConfig) (apiv1.RestartPolicy, error) {
- if s.Deploy == nil || s.Deploy.RestartPolicy == nil {
- return apiv1.RestartPolicyAlways, nil
- }
- policy := s.Deploy.RestartPolicy
- switch policy.Condition {
- case string(swarm.RestartPolicyConditionAny):
- return apiv1.RestartPolicyAlways, nil
- case string(swarm.RestartPolicyConditionNone):
- return apiv1.RestartPolicyNever, nil
- case string(swarm.RestartPolicyConditionOnFailure):
- return apiv1.RestartPolicyOnFailure, nil
- default:
- return "", errors.Errorf("unsupported restart policy %s", policy.Condition)
- }
- }
- func toResource(res *types.Resource) (apiv1.ResourceList, error) {
- list := make(apiv1.ResourceList)
- if res.NanoCPUs != "" {
- cpus, err := resource.ParseQuantity(res.NanoCPUs)
- if err != nil {
- return nil, err
- }
- list[apiv1.ResourceCPU] = cpus
- }
- if res.MemoryBytes != 0 {
- memory, err := resource.ParseQuantity(fmt.Sprintf("%v", res.MemoryBytes))
- if err != nil {
- return nil, err
- }
- list[apiv1.ResourceMemory] = memory
- }
- return list, nil
- }
- func toSecurityContext(s types.ServiceConfig) *apiv1.SecurityContext {
- isPrivileged := toBoolPointer(s.Privileged)
- isReadOnly := toBoolPointer(s.ReadOnly)
- var capabilities *apiv1.Capabilities
- if s.CapAdd != nil || s.CapDrop != nil {
- capabilities = &apiv1.Capabilities{
- Add: toCapabilities(s.CapAdd),
- Drop: toCapabilities(s.CapDrop),
- }
- }
- var userID *int64
- if s.User != "" {
- numerical, err := strconv.Atoi(s.User)
- if err == nil {
- unixUserID := int64(numerical)
- userID = &unixUserID
- }
- }
- if isPrivileged == nil && isReadOnly == nil && capabilities == nil && userID == nil {
- return nil
- }
- return &apiv1.SecurityContext{
- RunAsUser: userID,
- Privileged: isPrivileged,
- ReadOnlyRootFilesystem: isReadOnly,
- Capabilities: capabilities,
- }
- }
- func toBoolPointer(value bool) *bool {
- if value {
- return &value
- }
- return nil
- }
- func defaultUint64(v *uint64, defaultValue uint64) uint64 { //nolint: unparam
- if v == nil {
- return defaultValue
- }
- return *v
- }
- func toCapabilities(list []string) (capabilities []apiv1.Capability) {
- for _, c := range list {
- capabilities = append(capabilities, apiv1.Capability(c))
- }
- return
- }
|