From f7c86a7d30766b81d010fcf48623ea47de7c5435 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 19 Feb 2020 16:07:58 +0100 Subject: [PATCH] Extend compose to kube conversion (Most of this code is copy/paste from https://github.com/docker/compose-on-kubernetes/tree/master/internal/convert) Signed-off-by: Nicolas De Loof --- convert/kube.go | 176 +++++++++++++++++++++++ convert/pod.go | 342 +++++++++++++++++++++++++++++++++++++++++++++ convert/volumes.go | 228 ++++++++++++++++++++++++++++++ transform/kube.go | 72 ---------- 4 files changed, 746 insertions(+), 72 deletions(-) create mode 100644 convert/kube.go create mode 100644 convert/pod.go create mode 100644 convert/volumes.go delete mode 100644 transform/kube.go diff --git a/convert/kube.go b/convert/kube.go new file mode 100644 index 000000000..23a34843f --- /dev/null +++ b/convert/kube.go @@ -0,0 +1,176 @@ +package convert + +import ( + "fmt" + "github.com/compose-spec/compose-go/types" + "github.com/docker/helm-prototype/pkg/compose" + apps "k8s.io/api/apps/v1" + core "k8s.io/api/core/v1" + meta "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" + "strings" + "time" +) + +func MapToKubernetesObjects(model *compose.Project) (map[string]runtime.Object, error) { + objects := map[string]runtime.Object{} + for _, service := range model.Services { + objects[fmt.Sprintf("%s-service.yaml", service.Name)] = mapToService(model, service) + if service.Deploy != nil && service.Deploy.Mode == "global" { + daemonset, err := mapToDaemonset(service, model) + if err != nil { + return nil, err + } + objects[fmt.Sprintf("%s-daemonset.yaml", service.Name)] = daemonset + } else { + deployment, err := mapToDeployment(service, model) + if err != nil { + return nil, err + } + objects[fmt.Sprintf("%s-deployment.yaml", service.Name)] = deployment + } + for _, vol := range service.Volumes { + if vol.Type == "volume" { + objects[fmt.Sprintf("%s-persistentvolumeclain.yaml", service.Name)] = mapToPVC(service, vol) + } + } + } + return objects, nil +} + +func mapToService(model *compose.Project, service types.ServiceConfig) *core.Service { + ports := []core.ServicePort{} + for _, p := range service.Ports { + ports = append(ports, + core.ServicePort{ + Name: fmt.Sprintf("%d-%s", p.Target, strings.ToLower(string(p.Protocol))), + Port: int32(p.Target), + TargetPort: intstr.FromInt(int(p.Target)), + Protocol: toProtocol(p.Protocol), + }) + } + + return &core.Service{ + ObjectMeta: meta.ObjectMeta{ + Name: service.Name, + }, + Spec: core.ServiceSpec{ + Selector: map[string]string{"com.docker.compose.service": service.Name}, + Ports: ports, + Type: mapServiceToServiceType(service, model), + }, + } +} + +func mapServiceToServiceType(service types.ServiceConfig, model *compose.Project) core.ServiceType { + serviceType := core.ServiceTypeClusterIP + if len(service.Networks) == 0 { + // service is implicitly attached to "default" network + serviceType = core.ServiceTypeLoadBalancer + } + for name := range service.Networks { + if !model.Networks[name].Internal { + serviceType = core.ServiceTypeLoadBalancer + } + } + for _, port := range service.Ports { + if port.Published != 0 { + serviceType = core.ServiceTypeNodePort + } + } + return serviceType +} + +func mapToDeployment(service types.ServiceConfig, model *compose.Project) (*apps.Deployment, error) { + labels := map[string]string{ + "com.docker.compose.service": service.Name, + "com.docker.compose.project": model.Name, + } + podTemplate, err := toPodTemplate(service, labels, model) + if err != nil { + return nil, err + } + + return &apps.Deployment{ + ObjectMeta: meta.ObjectMeta{ + Name: service.Name, + Labels: labels, + }, + Spec: apps.DeploymentSpec{ + Replicas: toReplicas(service.Deploy), + Strategy: toDeploymentStrategy(service.Deploy), + Template: podTemplate, + }, + }, nil +} + +func mapToDaemonset(service types.ServiceConfig, model *compose.Project) (*apps.DaemonSet, error) { + labels := map[string]string{ + "com.docker.compose.service": service.Name, + "com.docker.compose.project": model.Name, + } + podTemplate, err := toPodTemplate(service, labels, model) + if err != nil { + return nil, err + } + + return &apps.DaemonSet{ + ObjectMeta: meta.ObjectMeta{ + Name: service.Name, + Labels: labels, + }, + Spec: apps.DaemonSetSpec{ + Template: podTemplate, + }, + }, nil +} + + +func toReplicas(deploy *types.DeployConfig) *int32 { + v := int32(1) + if deploy != nil { + v = int32(*deploy.Replicas) + } + return &v +} + +func toDeploymentStrategy(deploy *types.DeployConfig) apps.DeploymentStrategy { + if deploy == nil || deploy.UpdateConfig == nil { + return apps.DeploymentStrategy{ + Type: apps.RecreateDeploymentStrategyType, + } + } + return apps.DeploymentStrategy{ + Type: apps.RollingUpdateDeploymentStrategyType, + RollingUpdate: &apps.RollingUpdateDeployment{ + MaxUnavailable: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: int32(*deploy.UpdateConfig.Parallelism), + }, + MaxSurge: nil, + }, + } +} + +func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object { + return &core.PersistentVolumeClaim{ + ObjectMeta: meta.ObjectMeta{ + Name: vol.Source, + Labels: map[string]string{"com.docker.compose.service": service.Name}, + }, + Spec: core.PersistentVolumeClaimSpec{ + VolumeName: vol.Source, + }, + } +} + +// toSecondsOrDefault converts a duration string in seconds and defaults to a +// given value if the duration is nil. +// The supported units are us, ms, s, m and h. +func toSecondsOrDefault(duration *types.Duration, defaultValue int32) int32 { //nolint: unparam + if duration == nil { + return defaultValue + } + return int32(time.Duration(*duration).Seconds()) +} diff --git a/convert/pod.go b/convert/pod.go new file mode 100644 index 000000000..29561c000 --- /dev/null +++ b/convert/pod.go @@ -0,0 +1,342 @@ +package convert + +import ( + "fmt" + "github.com/compose-spec/compose-go/types" + "github.com/docker/docker/api/types/swarm" + "github.com/docker/helm-prototype/pkg/compose" + "sort" + "strconv" + "strings" + "time" + + "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(serviceConfig types.ServiceConfig, labels map[string]string, model *compose.Project) (apiv1.PodTemplateSpec, error) { + tpl := apiv1.PodTemplateSpec{} + 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 + } + limits, err := toResource(serviceConfig.Deploy) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } + requests, err := toResource(serviceConfig.Deploy) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } + volumes, err := toVolumes(serviceConfig, model) + if err != nil { + return apiv1.PodTemplateSpec{}, err + } + volumeMounts, err := toVolumeMounts(serviceConfig, model) + 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 + // FIXME 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 toImagePullPolicy(image string, specifiedPolicy string) (apiv1.PullPolicy, error) { + if specifiedPolicy == "" { + if strings.HasSuffix(image, ":latest") { + return apiv1.PullAlways, nil + } + return apiv1.PullIfNotPresent, nil + } + switch apiv1.PullPolicy(specifiedPolicy) { + case apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever: + return apiv1.PullPolicy(specifiedPolicy), nil + default: + return "", errors.Errorf("invalid pull policy %q, must be %q, %q or %q", specifiedPolicy, apiv1.PullAlways, apiv1.PullIfNotPresent, apiv1.PullNever) + } +} + +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(deploy *types.DeployConfig) (apiv1.ResourceList, error) { + if deploy == nil || deploy.Resources.Limits == nil { + return nil, nil + } + + res := deploy.Resources.Limits + 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 +} + +//nolint: unparam +func forceRestartPolicy(podTemplate apiv1.PodTemplateSpec, forcedRestartPolicy apiv1.RestartPolicy) apiv1.PodTemplateSpec { + if podTemplate.Spec.RestartPolicy != "" { + podTemplate.Spec.RestartPolicy = forcedRestartPolicy + } + + return podTemplate +} diff --git a/convert/volumes.go b/convert/volumes.go new file mode 100644 index 000000000..d3828c2e7 --- /dev/null +++ b/convert/volumes.go @@ -0,0 +1,228 @@ +package convert + +import ( + "fmt" + "github.com/compose-spec/compose-go/types" + "github.com/docker/helm-prototype/pkg/compose" + "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" + apiv1 "k8s.io/api/core/v1" +) + +const dockerSock = "/var/run/docker.sock" + +type volumeSpec struct { + mount apiv1.VolumeMount + source *apiv1.VolumeSource +} + +func hasPersistentVolumes(s types.ServiceConfig) bool { + for _, volume := range s.Volumes { + if volume.Type == "volume" { + return true + } + } + + return false +} + +func toVolumeSpecs(s types.ServiceConfig, model *compose.Project) ([]volumeSpec, error) { + var specs []volumeSpec + for i, m := range s.Volumes { + var source *apiv1.VolumeSource + name := fmt.Sprintf("mount-%d", i) + subpath := "" + if m.Source == dockerSock && m.Target == dockerSock { + subpath = "docker.sock" + source = hostPathVolume("/var/run") + } else if strings.HasSuffix(m.Source, ".git") { + source = gitVolume(m.Source) + } else if m.Type == "volume" { + if m.Source != "" { + name = m.Source + } + } else { + // bind mount + if !filepath.IsAbs(m.Source) { + return nil, errors.Errorf("%s: only absolute paths can be specified in mount source", m.Source) + } + if m.Source == "/" { + source = hostPathVolume("/") + } else { + parent, file := filepath.Split(m.Source) + if parent != "/" { + parent = strings.TrimSuffix(parent, "/") + } + source = hostPathVolume(parent) + subpath = file + } + } + + specs = append(specs, volumeSpec{ + source: source, + mount: volumeMount(name, m.Target, m.ReadOnly, subpath), + }) + } + + for i, m := range s.Tmpfs { + name := fmt.Sprintf("tmp-%d", i) + + specs = append(specs, volumeSpec{ + source: emptyVolumeInMemory(), + mount: volumeMount(name, m, false, ""), + }) + } + + for i, s := range s.Secrets { + name := fmt.Sprintf("secret-%d", i) + + target := path.Join("/run/secrets", or(s.Target, s.Source)) + subPath := name + readOnly := true + + specs = append(specs, volumeSpec{ + source: secretVolume(s, model.Secrets[name], subPath), + mount: volumeMount(name, target, readOnly, subPath), + }) + } + + for i, c := range s.Configs { + name := fmt.Sprintf("config-%d", i) + + target := or(c.Target, "/"+c.Source) + subPath := name + readOnly := true + + specs = append(specs, volumeSpec{ + source: configVolume(c, model.Configs[name], subPath), + mount: volumeMount(name, target, readOnly, subPath), + }) + } + + return specs, nil +} + +func or(v string, defaultValue string) string { + if v != "" && v != "." { + return v + } + + return defaultValue +} + +func toVolumeMounts(s types.ServiceConfig, model *compose.Project) ([]apiv1.VolumeMount, error) { + var mounts []apiv1.VolumeMount + specs, err := toVolumeSpecs(s, model) + if err != nil { + return nil, err + } + for _, spec := range specs { + mounts = append(mounts, spec.mount) + } + return mounts, nil +} + +func toVolumes(s types.ServiceConfig, model *compose.Project) ([]apiv1.Volume, error) { + var volumes []apiv1.Volume + specs, err := toVolumeSpecs(s, model) + if err != nil { + return nil, err + } + for _, spec := range specs { + if spec.source == nil { + continue + } + volumes = append(volumes, apiv1.Volume{ + Name: spec.mount.Name, + VolumeSource: *spec.source, + }) + } + return volumes, nil +} + +func gitVolume(path string) *apiv1.VolumeSource { + return &apiv1.VolumeSource{ + GitRepo: &apiv1.GitRepoVolumeSource{ + Repository: filepath.ToSlash(path), + }, + } +} + +func hostPathVolume(path string) *apiv1.VolumeSource { + return &apiv1.VolumeSource{ + HostPath: &apiv1.HostPathVolumeSource{ + Path: path, + }, + } +} + +func defaultMode(mode *uint32) *int32 { + var defaultMode *int32 + + if mode != nil { + signedMode := int32(*mode) + defaultMode = &signedMode + } + + return defaultMode +} + +func secretVolume(config types.ServiceSecretConfig, topLevelSecret types.SecretConfig, subPath string) *apiv1.VolumeSource { + return &apiv1.VolumeSource{ + Secret: &apiv1.SecretVolumeSource{ + SecretName: config.Source, + Items: []apiv1.KeyToPath{ + { + Key: toKey(topLevelSecret.File), + Path: subPath, + Mode: defaultMode(config.Mode), + }, + }, + }, + } +} + +func volumeMount(name, path string, readOnly bool, subPath string) apiv1.VolumeMount { + return apiv1.VolumeMount{ + Name: name, + MountPath: path, + ReadOnly: readOnly, + SubPath: subPath, + } +} + +func configVolume(config types.ServiceConfigObjConfig, topLevelConfig types.ConfigObjConfig, subPath string) *apiv1.VolumeSource { + return &apiv1.VolumeSource{ + ConfigMap: &apiv1.ConfigMapVolumeSource{ + LocalObjectReference: apiv1.LocalObjectReference{ + Name: config.Source, + }, + Items: []apiv1.KeyToPath{ + { + Key: toKey(topLevelConfig.File), + Path: subPath, + Mode: defaultMode(config.Mode), + }, + }, + }, + } +} + +func toKey(file string) string { + if file != "" { + return path.Base(file) + } + + return "file" // TODO: hard-coded key for external configs +} + +func emptyVolumeInMemory() *apiv1.VolumeSource { + return &apiv1.VolumeSource{ + EmptyDir: &apiv1.EmptyDirVolumeSource{ + Medium: apiv1.StorageMediumMemory, + }, + } +} diff --git a/transform/kube.go b/transform/kube.go deleted file mode 100644 index 2aa738354..000000000 --- a/transform/kube.go +++ /dev/null @@ -1,72 +0,0 @@ -package transform - -import ( - "fmt" - "github.com/compose-spec/compose-go/types" - "github.com/docker/helm-prototype/pkg/compose" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" - core "k8s.io/api/core/v1" - apps "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func MapToKubernetesObjects(model *compose.Project) (map[string]runtime.Object, error) { - objects := map[string]runtime.Object{} - for _, service := range model.Services { - objects[fmt.Sprintf("%s-service.yaml", service.Name)] = mapToService(service) - objects[fmt.Sprintf("%s-deployment.yaml", service.Name)] = mapToDeployment(service) - for _, vol := range service.Volumes { - if vol.Type == "volume" { - objects[fmt.Sprintf("%s-persistentvolumeclain.yaml", service.Name)] = mapToPVC(service, vol) - } - } - } - return objects, nil -} - -func mapToService(service types.ServiceConfig) *core.Service { - return &core.Service{ - ObjectMeta: meta.ObjectMeta{ - Name: service.Name, - }, - Spec: core.ServiceSpec{ - Selector: map[string]string{"com.docker.compose.service": service.Name}, - }, - } -} - -func mapToDeployment(service types.ServiceConfig) *apps.Deployment { - return &apps.Deployment{ - ObjectMeta: meta.ObjectMeta{ - Name: service.Name, - Labels: map[string]string{"com.docker.compose.service": service.Name}, - }, - Spec: apps.DeploymentSpec{ - Template: core.PodTemplateSpec{ - ObjectMeta: meta.ObjectMeta{ - Labels: map[string]string{"com.docker.compose.service": service.Name}, - }, - Spec: core.PodSpec{ - Containers: []core.Container{ - { - Name: service.Name, - Image: service.Image, - }, - }, - }, - }, - }, - } -} - -func mapToPVC(service types.ServiceConfig, vol types.ServiceVolumeConfig) runtime.Object { - return &core.PersistentVolumeClaim{ - ObjectMeta: meta.ObjectMeta{ - Name: vol.Source, - Labels: map[string]string{"com.docker.compose.service": service.Name}, - }, - Spec: core.PersistentVolumeClaimSpec{ - VolumeName: vol.Source, - }, - } -}