From 2b58307191688b21dd0d9e7dca38ce130f788f60 Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Tue, 13 Jun 2017 09:44:48 -0400 Subject: [PATCH] Add v3 support This does a major refactor on the compose.go functions as well as brings in a new era of v3 support to Kompose. Similar to how we utilize libcompose, we utilize docker/cli's "stack deploy" code which has a built-in v3 parser. We convert the parsed structure to our own and then convert it to Kubernetes/OpenShift artifacts. --- pkg/loader/compose/compose.go | 303 ++-------- pkg/loader/compose/compose_test.go | 37 ++ pkg/loader/compose/utils.go | 102 ++++ pkg/loader/compose/v1v2.go | 268 +++++++++ pkg/loader/compose/v3.go | 234 ++++++++ script/test/cmd/tests.sh | 25 + .../test/fixtures/v3/docker-compose-env.yaml | 6 + .../v3/docker-compose-full-example.yaml | 268 +++++++++ .../fixtures/v3/docker-compose-volumes.yaml | 8 + script/test/fixtures/v3/docker-compose.yaml | 24 + script/test/fixtures/v3/example1.env | 8 + script/test/fixtures/v3/example2.env | 1 + script/test/fixtures/v3/output-env-k8s.json | 74 +++ .../fixtures/v3/output-k8s-full-example.json | 509 ++++++++++++++++ script/test/fixtures/v3/output-k8s.json | 221 +++++++ .../fixtures/v3/output-os-full-example.json | 560 ++++++++++++++++++ script/test/fixtures/v3/output-os.json | 377 ++++++++++++ .../test/fixtures/v3/output-volumes-k8s.json | 106 ++++ 18 files changed, 2871 insertions(+), 260 deletions(-) create mode 100644 pkg/loader/compose/utils.go create mode 100644 pkg/loader/compose/v1v2.go create mode 100644 pkg/loader/compose/v3.go create mode 100644 script/test/fixtures/v3/docker-compose-env.yaml create mode 100644 script/test/fixtures/v3/docker-compose-full-example.yaml create mode 100644 script/test/fixtures/v3/docker-compose-volumes.yaml create mode 100644 script/test/fixtures/v3/docker-compose.yaml create mode 100644 script/test/fixtures/v3/example1.env create mode 100644 script/test/fixtures/v3/example2.env create mode 100644 script/test/fixtures/v3/output-env-k8s.json create mode 100644 script/test/fixtures/v3/output-k8s-full-example.json create mode 100644 script/test/fixtures/v3/output-k8s.json create mode 100644 script/test/fixtures/v3/output-os-full-example.json create mode 100644 script/test/fixtures/v3/output-os.json create mode 100644 script/test/fixtures/v3/output-volumes-k8s.json diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index aaeb444bc..79c2f71b3 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -18,18 +18,13 @@ package compose import ( "fmt" - "net" - "os" - "path/filepath" + "io/ioutil" "reflect" - "strconv" "strings" - "k8s.io/kubernetes/pkg/api" + yaml "gopkg.in/yaml.v2" log "github.com/Sirupsen/logrus" - "github.com/docker/libcompose/config" - "github.com/docker/libcompose/lookup" "github.com/docker/libcompose/project" "github.com/fatih/structs" "github.com/kubernetes-incubator/kompose/pkg/kobject" @@ -134,277 +129,65 @@ func checkUnsupportedKey(composeProject *project.Project) []string { return keysFound } -// load environment variables from compose file -func loadEnvVars(envars []string) []kobject.EnvVar { - envs := []kobject.EnvVar{} - for _, e := range envars { - character := "" - equalPos := strings.Index(e, "=") - colonPos := strings.Index(e, ":") - switch { - case equalPos == -1 && colonPos == -1: - character = "" - case equalPos == -1 && colonPos != -1: - character = ":" - case equalPos != -1 && colonPos == -1: - character = "=" - case equalPos != -1 && colonPos != -1: - if equalPos > colonPos { - character = ":" - } else { - character = "=" - } - } - - if character == "" { - envs = append(envs, kobject.EnvVar{ - Name: e, - Value: os.Getenv(e), - }) - } else { - values := strings.SplitN(e, character, 2) - // try to get value from os env - if values[1] == "" { - values[1] = os.Getenv(values[0]) - } - envs = append(envs, kobject.EnvVar{ - Name: values[0], - Value: values[1], - }) - } - } - - return envs -} - -// Load ports from compose file -func loadPorts(composePorts []string) ([]kobject.Ports, error) { - ports := []kobject.Ports{} - character := ":" +// LoadFile loads a compose file into KomposeObject +func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { - // For each port listed - for _, port := range composePorts { + // Load the json / yaml file in order to get the version value + var version string - // Get the TCP / UDP protocol. Checks to see if it splits in 2 with '/' character. - // ex. 15000:15000/tcp - // else, set a default protocol of using TCP - proto := api.ProtocolTCP - protocolCheck := strings.Split(port, "/") - if len(protocolCheck) == 2 { - if strings.EqualFold("tcp", protocolCheck[1]) { - proto = api.ProtocolTCP - } else if strings.EqualFold("udp", protocolCheck[1]) { - proto = api.ProtocolUDP - } else { - return nil, fmt.Errorf("invalid protocol %q", protocolCheck[1]) - } + for _, file := range files { + composeVersion, err := getVersionFromFile(file) + if err != nil { + return kobject.KomposeObject{}, errors.Wrap(err, "Unable to load yaml/json file for version parsing") } - // Split up the ports / IP without the "/tcp" or "/udp" appended to it - justPorts := strings.Split(protocolCheck[0], character) - - if len(justPorts) == 3 { - // ex. 127.0.0.1:80:80 - - // Get the IP address - hostIP := justPorts[0] - ip := net.ParseIP(hostIP) - if ip.To4() == nil && ip.To16() == nil { - return nil, fmt.Errorf("%q contains an invalid IPv4 or IPv6 IP address", port) - } - - // Get the host port - hostPortInt, err := strconv.Atoi(justPorts[1]) - if err != nil { - return nil, fmt.Errorf("invalid host port %q valid example: 127.0.0.1:80:80", port) - } - - // Get the container port - containerPortInt, err := strconv.Atoi(justPorts[2]) - if err != nil { - return nil, fmt.Errorf("invalid container port %q valid example: 127.0.0.1:80:80", port) - } - - // Convert to a kobject struct with ports as well as IP - ports = append(ports, kobject.Ports{ - HostPort: int32(hostPortInt), - ContainerPort: int32(containerPortInt), - HostIP: hostIP, - Protocol: proto, - }) - - } else if len(justPorts) == 2 { - // ex. 80:80 - - // Get the host port - hostPortInt, err := strconv.Atoi(justPorts[0]) - if err != nil { - return nil, fmt.Errorf("invalid host port %q valid example: 80:80", port) - } - - // Get the container port - containerPortInt, err := strconv.Atoi(justPorts[1]) - if err != nil { - return nil, fmt.Errorf("invalid container port %q valid example: 80:80", port) - } - - // Convert to a kobject struct and add to the list of ports - ports = append(ports, kobject.Ports{ - HostPort: int32(hostPortInt), - ContainerPort: int32(containerPortInt), - Protocol: proto, - }) - - } else { - // ex. 80 - - containerPortInt, err := strconv.Atoi(justPorts[0]) - if err != nil { - return nil, fmt.Errorf("invalid container port %q valid example: 80", port) - } - ports = append(ports, kobject.Ports{ - ContainerPort: int32(containerPortInt), - Protocol: proto, - }) + // Check that the previous file loaded matches. + if len(files) > 0 && version != "" && version != composeVersion { + return kobject.KomposeObject{}, errors.New("All Docker Compose files must be of the same version") } - - } - return ports, nil -} - -// LoadFile loads compose file into KomposeObject -func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { - komposeObject := kobject.KomposeObject{ - ServiceConfigs: make(map[string]kobject.ServiceConfig), - LoadedFrom: "compose", + version = composeVersion } - context := &project.Context{} - context.ComposeFiles = files - if context.ResourceLookup == nil { - context.ResourceLookup = &lookup.FileResourceLookup{} - } + log.Debugf("Docker Compose version: %s", version) - if context.EnvironmentLookup == nil { - cwd, err := os.Getwd() + // Convert based on version + switch version { + // Use libcompose for 1 or 2 + // If blank, it's assumed it's 1 or 2 + case "", "1", "1.0", "2", "2.0": + komposeObject, err := parseV1V2(files) if err != nil { - return kobject.KomposeObject{}, nil + return kobject.KomposeObject{}, err } - context.EnvironmentLookup = &lookup.ComposableEnvLookup{ - Lookups: []config.EnvironmentLookup{ - &lookup.EnvfileLookup{ - Path: filepath.Join(cwd, ".env"), - }, - &lookup.OsEnvLookup{}, - }, + return komposeObject, nil + // Use docker/cli for 3 + case "3", "3.0": + komposeObject, err := parseV3(files) + if err != nil { + return kobject.KomposeObject{}, err } + return komposeObject, nil + default: + return kobject.KomposeObject{}, fmt.Errorf("Version %s of Docker Compose is not supported. Please use version 1, 2 or 3", version) } - // load compose file into composeObject - composeObject := project.NewProject(context, nil, nil) - err := composeObject.Parse() - if err != nil { - return kobject.KomposeObject{}, errors.Wrap(err, "composeObject.Parse() failed, Failed to load compose file") - } +} - noSupKeys := checkUnsupportedKey(composeObject) - for _, keyName := range noSupKeys { - log.Warningf("Unsupported %s key - ignoring", keyName) +func getVersionFromFile(file string) (string, error) { + type ComposeVersion struct { + Version string `json:"version"` // This affects YAML as well } + var version ComposeVersion - for name, composeServiceConfig := range composeObject.ServiceConfigs.All() { - serviceConfig := kobject.ServiceConfig{} - serviceConfig.Image = composeServiceConfig.Image - serviceConfig.Build = composeServiceConfig.Build.Context - newName := normalizeServiceNames(composeServiceConfig.ContainerName) - serviceConfig.ContainerName = newName - if newName != composeServiceConfig.ContainerName { - log.Infof("Container name in service %q has been changed from %q to %q", name, composeServiceConfig.ContainerName, newName) - } - serviceConfig.Command = composeServiceConfig.Entrypoint - serviceConfig.Args = composeServiceConfig.Command - serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile - serviceConfig.BuildArgs = composeServiceConfig.Build.Args - - envs := loadEnvVars(composeServiceConfig.Environment) - serviceConfig.Environment = envs - - //Validate dockerfile path - if filepath.IsAbs(serviceConfig.Dockerfile) { - log.Fatalf("%q defined in service %q is an absolute path, it must be a relative path.", serviceConfig.Dockerfile, name) - } - - // load ports - ports, err := loadPorts(composeServiceConfig.Ports) - if err != nil { - return kobject.KomposeObject{}, errors.Wrap(err, "loadPorts failed. "+name+" failed to load ports from compose file") - } - serviceConfig.Port = ports - - serviceConfig.WorkingDir = composeServiceConfig.WorkingDir - - if composeServiceConfig.Volumes != nil { - for _, volume := range composeServiceConfig.Volumes.Volumes { - v := normalizeServiceNames(volume.String()) - serviceConfig.Volumes = append(serviceConfig.Volumes, v) - } - } - - // canonical "Custom Labels" handler - // Labels used to influence conversion of kompose will be handled - // from here for docker-compose. Each loader will have such handler. - for key, value := range composeServiceConfig.Labels { - switch key { - case "kompose.service.type": - serviceType, err := handleServiceType(value) - if err != nil { - return kobject.KomposeObject{}, errors.Wrap(err, "handleServiceType failed") - } - - serviceConfig.ServiceType = serviceType - case "kompose.service.expose": - serviceConfig.ExposeService = strings.ToLower(value) - } - } - - // convert compose labels to annotations - serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) - serviceConfig.CPUQuota = int64(composeServiceConfig.CPUQuota) - serviceConfig.CapAdd = composeServiceConfig.CapAdd - serviceConfig.CapDrop = composeServiceConfig.CapDrop - serviceConfig.Pid = composeServiceConfig.Pid - serviceConfig.Expose = composeServiceConfig.Expose - serviceConfig.Privileged = composeServiceConfig.Privileged - serviceConfig.Restart = composeServiceConfig.Restart - serviceConfig.User = composeServiceConfig.User - serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom - serviceConfig.Stdin = composeServiceConfig.StdinOpen - serviceConfig.Tty = composeServiceConfig.Tty - serviceConfig.MemLimit = composeServiceConfig.MemLimit - serviceConfig.TmpFs = composeServiceConfig.Tmpfs - serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod - komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig - if normalizeServiceNames(name) != name { - log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) - } + loadedFile, err := ioutil.ReadFile(file) + if err != nil { + return "", err } - return komposeObject, nil -} - -func handleServiceType(ServiceType string) (string, error) { - switch strings.ToLower(ServiceType) { - case "", "clusterip": - return string(api.ServiceTypeClusterIP), nil - case "nodeport": - return string(api.ServiceTypeNodePort), nil - case "loadbalancer": - return string(api.ServiceTypeLoadBalancer), nil - default: - return "", errors.New("Unknown value " + ServiceType + " , supported values are 'NodePort, ClusterIP or LoadBalancer'") + err = yaml.Unmarshal(loadedFile, &version) + if err != nil { + return "", err } -} -func normalizeServiceNames(svcName string) string { - return strings.Replace(svcName, "_", "-", -1) + return version.Version, nil } diff --git a/pkg/loader/compose/compose_test.go b/pkg/loader/compose/compose_test.go index 6960f4af7..9b8b566b8 100644 --- a/pkg/loader/compose/compose_test.go +++ b/pkg/loader/compose/compose_test.go @@ -25,12 +25,49 @@ import ( "github.com/kubernetes-incubator/kompose/pkg/kobject" "k8s.io/kubernetes/pkg/api" + "github.com/docker/cli/cli/compose/types" "github.com/docker/libcompose/config" "github.com/docker/libcompose/project" "github.com/docker/libcompose/yaml" "github.com/pkg/errors" ) +func TestLoadV3Volumes(t *testing.T) { + vol := types.ServiceVolumeConfig{ + Type: "volume", + Source: "/tmp/foobar", + Target: "/tmp/foobar", + ReadOnly: true, + } + volumes := []types.ServiceVolumeConfig{vol} + output := loadV3Volumes(volumes) + expected := "/tmp/foobar:/tmp/foobar:ro" + + if output[0] != expected { + t.Errorf("Expected %s, got %s", expected, output[0]) + } + +} + +func TestLoadV3Ports(t *testing.T) { + port := types.ServicePortConfig{ + Target: 80, + Published: 80, + Protocol: "TCP", + } + ports := []types.ServicePortConfig{port} + output := loadV3Ports(ports) + expected := kobject.Ports{ + HostPort: 80, + ContainerPort: 80, + Protocol: api.Protocol("TCP"), + } + + if output[0] != expected { + t.Errorf("Expected %s, got %s", expected, output[0]) + } +} + // Test if service types are parsed properly on user input // give a service type and expect correct input func TestHandleServiceType(t *testing.T) { diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go new file mode 100644 index 000000000..5dff82d22 --- /dev/null +++ b/pkg/loader/compose/utils.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 compose + +import ( + "os" + "path/filepath" + "strings" + + "github.com/kubernetes-incubator/kompose/pkg/kobject" + "github.com/pkg/errors" + "k8s.io/kubernetes/pkg/api" +) + +// load environment variables from compose file +func loadEnvVars(envars []string) []kobject.EnvVar { + envs := []kobject.EnvVar{} + for _, e := range envars { + character := "" + equalPos := strings.Index(e, "=") + colonPos := strings.Index(e, ":") + switch { + case equalPos == -1 && colonPos == -1: + character = "" + case equalPos == -1 && colonPos != -1: + character = ":" + case equalPos != -1 && colonPos == -1: + character = "=" + case equalPos != -1 && colonPos != -1: + if equalPos > colonPos { + character = ":" + } else { + character = "=" + } + } + + if character == "" { + envs = append(envs, kobject.EnvVar{ + Name: e, + Value: os.Getenv(e), + }) + } else { + values := strings.SplitN(e, character, 2) + // try to get value from os env + if values[1] == "" { + values[1] = os.Getenv(values[0]) + } + envs = append(envs, kobject.EnvVar{ + Name: values[0], + Value: values[1], + }) + } + } + + return envs +} + +// getComposeFileDir returns compose file directory +// Assume all the docker-compose files are in the same directory +// TODO: fix (check if file exists) +func getComposeFileDir(inputFiles []string) (string, error) { + inputFile := inputFiles[0] + if strings.Index(inputFile, "/") != 0 { + workDir, err := os.Getwd() + if err != nil { + return "", errors.Wrap(err, "Unable to retrieve compose file directory") + } + inputFile = filepath.Join(workDir, inputFile) + } + return filepath.Dir(inputFile), nil +} + +func handleServiceType(ServiceType string) (string, error) { + switch strings.ToLower(ServiceType) { + case "", "clusterip": + return string(api.ServiceTypeClusterIP), nil + case "nodeport": + return string(api.ServiceTypeNodePort), nil + case "loadbalancer": + return string(api.ServiceTypeLoadBalancer), nil + default: + return "", errors.New("Unknown value " + ServiceType + " , supported values are 'NodePort, ClusterIP or LoadBalancer'") + } +} + +func normalizeServiceNames(svcName string) string { + return strings.Replace(svcName, "_", "-", -1) +} diff --git a/pkg/loader/compose/v1v2.go b/pkg/loader/compose/v1v2.go new file mode 100644 index 000000000..ee041d93b --- /dev/null +++ b/pkg/loader/compose/v1v2.go @@ -0,0 +1,268 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 compose + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strconv" + "strings" + + "k8s.io/kubernetes/pkg/api" + + log "github.com/Sirupsen/logrus" + "github.com/docker/libcompose/config" + "github.com/docker/libcompose/lookup" + "github.com/docker/libcompose/project" + "github.com/kubernetes-incubator/kompose/pkg/kobject" + "github.com/pkg/errors" +) + +// Parse Docker Compose with libcompose (only supports v1 and v2). Eventually we will +// switch to using only libcompose once v3 is supported. +func parseV1V2(files []string) (kobject.KomposeObject, error) { + + // Gather the appropriate context for parsing + context := &project.Context{} + context.ComposeFiles = files + + if context.ResourceLookup == nil { + context.ResourceLookup = &lookup.FileResourceLookup{} + } + + if context.EnvironmentLookup == nil { + cwd, err := os.Getwd() + if err != nil { + return kobject.KomposeObject{}, nil + } + context.EnvironmentLookup = &lookup.ComposableEnvLookup{ + Lookups: []config.EnvironmentLookup{ + &lookup.EnvfileLookup{ + Path: filepath.Join(cwd, ".env"), + }, + &lookup.OsEnvLookup{}, + }, + } + } + + // Load the context and let's start parsing + composeObject := project.NewProject(context, nil, nil) + err := composeObject.Parse() + if err != nil { + return kobject.KomposeObject{}, errors.Wrap(err, "composeObject.Parse() failed, Failed to load compose file") + } + + noSupKeys := checkUnsupportedKey(composeObject) + for _, keyName := range noSupKeys { + log.Warningf("Unsupported %s key - ignoring", keyName) + } + + // Map the parsed struct to a struct we understand (kobject) + komposeObject, err := libComposeToKomposeMapping(composeObject) + if err != nil { + return kobject.KomposeObject{}, err + } + + return komposeObject, nil +} + +// Load ports from compose file +func loadPorts(composePorts []string) ([]kobject.Ports, error) { + ports := []kobject.Ports{} + character := ":" + + // For each port listed + for _, port := range composePorts { + + // Get the TCP / UDP protocol. Checks to see if it splits in 2 with '/' character. + // ex. 15000:15000/tcp + // else, set a default protocol of using TCP + proto := api.ProtocolTCP + protocolCheck := strings.Split(port, "/") + if len(protocolCheck) == 2 { + if strings.EqualFold("tcp", protocolCheck[1]) { + proto = api.ProtocolTCP + } else if strings.EqualFold("udp", protocolCheck[1]) { + proto = api.ProtocolUDP + } else { + return nil, fmt.Errorf("invalid protocol %q", protocolCheck[1]) + } + } + + // Split up the ports / IP without the "/tcp" or "/udp" appended to it + justPorts := strings.Split(protocolCheck[0], character) + + if len(justPorts) == 3 { + // ex. 127.0.0.1:80:80 + + // Get the IP address + hostIP := justPorts[0] + ip := net.ParseIP(hostIP) + if ip.To4() == nil && ip.To16() == nil { + return nil, fmt.Errorf("%q contains an invalid IPv4 or IPv6 IP address", port) + } + + // Get the host port + hostPortInt, err := strconv.Atoi(justPorts[1]) + if err != nil { + return nil, fmt.Errorf("invalid host port %q valid example: 127.0.0.1:80:80", port) + } + + // Get the container port + containerPortInt, err := strconv.Atoi(justPorts[2]) + if err != nil { + return nil, fmt.Errorf("invalid container port %q valid example: 127.0.0.1:80:80", port) + } + + // Convert to a kobject struct with ports as well as IP + ports = append(ports, kobject.Ports{ + HostPort: int32(hostPortInt), + ContainerPort: int32(containerPortInt), + HostIP: hostIP, + Protocol: proto, + }) + + } else if len(justPorts) == 2 { + // ex. 80:80 + + // Get the host port + hostPortInt, err := strconv.Atoi(justPorts[0]) + if err != nil { + return nil, fmt.Errorf("invalid host port %q valid example: 80:80", port) + } + + // Get the container port + containerPortInt, err := strconv.Atoi(justPorts[1]) + if err != nil { + return nil, fmt.Errorf("invalid container port %q valid example: 80:80", port) + } + + // Convert to a kobject struct and add to the list of ports + ports = append(ports, kobject.Ports{ + HostPort: int32(hostPortInt), + ContainerPort: int32(containerPortInt), + Protocol: proto, + }) + + } else { + // ex. 80 + + containerPortInt, err := strconv.Atoi(justPorts[0]) + if err != nil { + return nil, fmt.Errorf("invalid container port %q valid example: 80", port) + } + ports = append(ports, kobject.Ports{ + ContainerPort: int32(containerPortInt), + Protocol: proto, + }) + } + + } + return ports, nil +} + +// Uses libcompose's APIProject type and converts it to a Kompose object for us to understand +func libComposeToKomposeMapping(composeObject *project.Project) (kobject.KomposeObject, error) { + + // Initialize what's going to be returned + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + LoadedFrom: "compose", + } + + // Here we "clean up" the service configuration so we return something that includes + // all relevant information as well as avoid the unsupported keys as well. + for name, composeServiceConfig := range composeObject.ServiceConfigs.All() { + serviceConfig := kobject.ServiceConfig{} + serviceConfig.Image = composeServiceConfig.Image + serviceConfig.Build = composeServiceConfig.Build.Context + newName := normalizeServiceNames(composeServiceConfig.ContainerName) + serviceConfig.ContainerName = newName + if newName != composeServiceConfig.ContainerName { + log.Infof("Container name in service %q has been changed from %q to %q", name, composeServiceConfig.ContainerName, newName) + } + serviceConfig.Command = composeServiceConfig.Entrypoint + serviceConfig.Args = composeServiceConfig.Command + serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile + serviceConfig.BuildArgs = composeServiceConfig.Build.Args + + envs := loadEnvVars(composeServiceConfig.Environment) + serviceConfig.Environment = envs + + //Validate dockerfile path + if filepath.IsAbs(serviceConfig.Dockerfile) { + log.Fatalf("%q defined in service %q is an absolute path, it must be a relative path.", serviceConfig.Dockerfile, name) + } + + // load ports + ports, err := loadPorts(composeServiceConfig.Ports) + if err != nil { + return kobject.KomposeObject{}, errors.Wrap(err, "loadPorts failed. "+name+" failed to load ports from compose file") + } + serviceConfig.Port = ports + + serviceConfig.WorkingDir = composeServiceConfig.WorkingDir + + if composeServiceConfig.Volumes != nil { + for _, volume := range composeServiceConfig.Volumes.Volumes { + v := normalizeServiceNames(volume.String()) + serviceConfig.Volumes = append(serviceConfig.Volumes, v) + } + } + + // canonical "Custom Labels" handler + // Labels used to influence conversion of kompose will be handled + // from here for docker-compose. Each loader will have such handler. + for key, value := range composeServiceConfig.Labels { + switch key { + case "kompose.service.type": + serviceType, err := handleServiceType(value) + if err != nil { + return kobject.KomposeObject{}, errors.Wrap(err, "handleServiceType failed") + } + + serviceConfig.ServiceType = serviceType + case "kompose.service.expose": + serviceConfig.ExposeService = strings.ToLower(value) + } + } + + // convert compose labels to annotations + serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) + serviceConfig.CPUQuota = int64(composeServiceConfig.CPUQuota) + serviceConfig.CapAdd = composeServiceConfig.CapAdd + serviceConfig.CapDrop = composeServiceConfig.CapDrop + serviceConfig.Pid = composeServiceConfig.Pid + serviceConfig.Expose = composeServiceConfig.Expose + serviceConfig.Privileged = composeServiceConfig.Privileged + serviceConfig.Restart = composeServiceConfig.Restart + serviceConfig.User = composeServiceConfig.User + serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom + serviceConfig.Stdin = composeServiceConfig.StdinOpen + serviceConfig.Tty = composeServiceConfig.Tty + serviceConfig.MemLimit = composeServiceConfig.MemLimit + serviceConfig.TmpFs = composeServiceConfig.Tmpfs + serviceConfig.StopGracePeriod = composeServiceConfig.StopGracePeriod + komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig + if normalizeServiceNames(name) != name { + log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) + } + } + return komposeObject, nil +} diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go new file mode 100644 index 000000000..74c86492e --- /dev/null +++ b/pkg/loader/compose/v3.go @@ -0,0 +1,234 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 compose + +import ( + libcomposeyaml "github.com/docker/libcompose/yaml" + "io/ioutil" + "strings" + + "k8s.io/kubernetes/pkg/api" + + "github.com/docker/cli/cli/compose/loader" + "github.com/docker/cli/cli/compose/types" + + log "github.com/Sirupsen/logrus" + "github.com/kubernetes-incubator/kompose/pkg/kobject" + "github.com/pkg/errors" +) + +// The purpose of this is not to deploy, but to be able to parse +// v3 of Docker Compose into a suitable format. In this case, whatever is returned +// by docker/cli's ServiceConfig +func parseV3(files []string) (kobject.KomposeObject, error) { + + // In order to get V3 parsing to work, we have to go through some preliminary steps + // for us to hack up github.com/docker/cli in order to correctly convert to a kobject.KomposeObject + + // Gather the working directory + workingDir, err := getComposeFileDir(files) + if err != nil { + return kobject.KomposeObject{}, err + } + + // Load and then parse the YAML first! + loadedFile, err := ioutil.ReadFile(files[0]) + if err != nil { + return kobject.KomposeObject{}, err + } + + // Parse the Compose File + parsedComposeFile, err := loader.ParseYAML(loadedFile) + if err != nil { + return kobject.KomposeObject{}, err + } + + // Config file + configFile := types.ConfigFile{ + Filename: files[0], + Config: parsedComposeFile, + } + + // Config details + // Environment is nil as docker/cli loads the appropriate environmental values itself + configDetails := types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: []types.ConfigFile{configFile}, + Environment: nil, + } + + // Actual config + // We load it in order to retrieve the parsed output configuration! + // This will output a github.com/docker/cli ServiceConfig + // Which is similar to our version of ServiceConfig + config, err := loader.Load(configDetails) + if err != nil { + return kobject.KomposeObject{}, err + } + + // TODO: Check all "unsupported" keys and output details + // Specifically, keys such as "volumes_from" are not supported in V3. + + // Finally, we convert the object from docker/cli's ServiceConfig to our appropriate one + komposeObject, err := dockerComposeToKomposeMapping(config) + if err != nil { + return kobject.KomposeObject{}, err + } + + return komposeObject, nil +} + +// Convert the Docker Compose v3 volumes to []string (the old way) +// TODO: Check to see if it's a "bind" or "volume". Ignore for now. +// TODO: Refactor it similar to loadV3Ports +// See: https://docs.docker.com/compose/compose-file/#long-syntax-2 +func loadV3Volumes(volumes []types.ServiceVolumeConfig) []string { + var volArray []string + for _, vol := range volumes { + + // There will *always* be Source when parsing + v := normalizeServiceNames(vol.Source) + + if vol.Target != "" { + v = v + ":" + vol.Target + } + + if vol.ReadOnly { + v = v + ":ro" + } + + volArray = append(volArray, v) + } + return volArray +} + +// Convert Docker Compose v3 ports to kobject.Ports +func loadV3Ports(ports []types.ServicePortConfig) []kobject.Ports { + komposePorts := []kobject.Ports{} + + for _, port := range ports { + + // Convert to a kobject struct with ports + // NOTE: V3 doesn't use IP (they utilize Swarm instead for host-networking). + // Thus, IP is blank. + komposePorts = append(komposePorts, kobject.Ports{ + HostPort: int32(port.Published), + ContainerPort: int32(port.Target), + HostIP: "", + Protocol: api.Protocol(strings.ToUpper(string(port.Protocol))), + }) + + } + + return komposePorts +} + +func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.KomposeObject, error) { + + // Step 1. Initialize what's going to be returned + komposeObject := kobject.KomposeObject{ + ServiceConfigs: make(map[string]kobject.ServiceConfig), + LoadedFrom: "compose", + } + + // Step 2. Parse through the object and conver it to kobject.KomposeObject! + // Here we "clean up" the service configuration so we return something that includes + // all relevant information as well as avoid the unsupported keys as well. + for _, composeServiceConfig := range composeObject.Services { + + // Standard import + // No need to modify before importation + name := composeServiceConfig.Name + serviceConfig := kobject.ServiceConfig{} + serviceConfig.Image = composeServiceConfig.Image + serviceConfig.WorkingDir = composeServiceConfig.WorkingDir + serviceConfig.Annotations = map[string]string(composeServiceConfig.Labels) + serviceConfig.CapAdd = composeServiceConfig.CapAdd + serviceConfig.CapDrop = composeServiceConfig.CapDrop + serviceConfig.Expose = composeServiceConfig.Expose + serviceConfig.Privileged = composeServiceConfig.Privileged + serviceConfig.Restart = composeServiceConfig.Restart + serviceConfig.User = composeServiceConfig.User + serviceConfig.Stdin = composeServiceConfig.StdinOpen + serviceConfig.Tty = composeServiceConfig.Tty + serviceConfig.TmpFs = composeServiceConfig.Tmpfs + serviceConfig.ContainerName = composeServiceConfig.ContainerName + serviceConfig.Command = composeServiceConfig.Entrypoint + serviceConfig.Args = composeServiceConfig.Command + + // This is a bit messy since we use yaml.MemStringorInt + // TODO: Refactor yaml.MemStringorInt in kobject.go to int64 + // Since Deploy.Resources.Limits does not initialize, we must check type Resources before continuing + if (composeServiceConfig.Deploy.Resources != types.Resources{}) { + serviceConfig.MemLimit = libcomposeyaml.MemStringorInt(composeServiceConfig.Deploy.Resources.Limits.MemoryBytes) + } + + // POOF. volumes_From is gone in v3. docker/cli will error out of volumes_from is added in v3 + // serviceConfig.VolumesFrom = composeServiceConfig.VolumesFrom + + // TODO: Build is not yet supported, see: + // https://github.com/docker/cli/blob/master/cli/compose/types/types.go#L9 + // We will have to *manually* add this / parse. + // serviceConfig.Build = composeServiceConfig.Build.Context + // serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile + + // Gather the environment values + // DockerCompose uses map[string]*string while we use []string + // So let's convert that using this hack + for name, value := range composeServiceConfig.Environment { + env := kobject.EnvVar{Name: name, Value: *value} + serviceConfig.Environment = append(serviceConfig.Environment, env) + } + + // Parse the ports + // v3 uses a new format called "long syntax" starting in 3.2 + // https://docs.docker.com/compose/compose-file/#ports + serviceConfig.Port = loadV3Ports(composeServiceConfig.Ports) + + // Parse the volumes + // Again, in v3, we use the "long syntax" for volumes in terms of parsing + // https://docs.docker.com/compose/compose-file/#long-syntax-2 + serviceConfig.Volumes = loadV3Volumes(composeServiceConfig.Volumes) + + // Label handler + // Labels used to influence conversion of kompose will be handled + // from here for docker-compose. Each loader will have such handler. + for key, value := range composeServiceConfig.Labels { + switch key { + case "kompose.service.type": + serviceType, err := handleServiceType(value) + if err != nil { + return kobject.KomposeObject{}, errors.Wrap(err, "handleServiceType failed") + } + + serviceConfig.ServiceType = serviceType + case "kompose.service.expose": + serviceConfig.ExposeService = strings.ToLower(value) + } + } + + // Log if the name will been changed + if normalizeServiceNames(name) != name { + log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) + } + + // Final step, add to the array! + komposeObject.ServiceConfigs[normalizeServiceNames(name)] = serviceConfig + } + + return komposeObject, nil +} diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 04256cf99..2ec00aab2 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -249,4 +249,29 @@ convert::expect_success "kompose --provider=openshift convert --stdout -j" "$KOM # Return back to the original path cd $CURRENT_DIR +# Test V3 Support of Docker Compose + +# Test volumes are passed correctly +convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-volumes.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-volumes-k8s.json" + +# Test environment variables are passed correctly +convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-env.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-env-k8s.json" + +# Test that two files that are different versions fail +convert::expect_failure "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose.yaml -f $KOMPOSE_ROOT/script/test/fixtures/etherpad/docker-compose.yml" + +# Kubernetes +convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-k8s.json" + +# OpenShift +convert::expect_success "kompose convert --provider=openshift --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-os.json" + +# Test the "full example" from https://raw.githubusercontent.com/aanand/compose-file/master/loader/example1.env + +# Kubernetes +convert::expect_success_and_warning "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-full-example.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-k8s-full-example.json" + +# Openshift +convert::expect_success_and_warning "kompose convert --provider=openshift --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/v3/docker-compose-full-example.yaml" "$KOMPOSE_ROOT/script/test/fixtures/v3/output-os-full-example.json" + exit $EXIT_STATUS diff --git a/script/test/fixtures/v3/docker-compose-env.yaml b/script/test/fixtures/v3/docker-compose-env.yaml new file mode 100644 index 000000000..fd233c01d --- /dev/null +++ b/script/test/fixtures/v3/docker-compose-env.yaml @@ -0,0 +1,6 @@ +version: '3' +services: + foo: + image: foo/bar:latest + environment: + FOO: foo diff --git a/script/test/fixtures/v3/docker-compose-full-example.yaml b/script/test/fixtures/v3/docker-compose-full-example.yaml new file mode 100644 index 000000000..68ed34d37 --- /dev/null +++ b/script/test/fixtures/v3/docker-compose-full-example.yaml @@ -0,0 +1,268 @@ +version: "3" + +services: + foo: + cap_add: + - ALL + + cap_drop: + - NET_ADMIN + - SYS_ADMIN + + cgroup_parent: m-executor-abcd + + # String or list + command: bundle exec thin -p 3000 + # command: ["bundle", "exec", "thin", "-p", "3000"] + + container_name: my-web-container + + depends_on: + - db + - redis + + deploy: + mode: replicated + replicas: 6 + labels: [FOO=BAR] + update_config: + parallelism: 3 + delay: 10s + failure_action: continue + monitor: 60s + max_failure_ratio: 0.3 + resources: + limits: + cpus: '0.001' + memory: 50M + reservations: + cpus: '0.0001' + memory: 20M + restart_policy: + condition: on_failure + delay: 5s + max_attempts: 3 + window: 120s + placement: + constraints: [node=foo] + + devices: + - "/dev/ttyUSB0:/dev/ttyUSB0" + + # String or list + # dns: 8.8.8.8 + dns: + - 8.8.8.8 + - 9.9.9.9 + + # String or list + # dns_search: example.com + dns_search: + - dc1.example.com + - dc2.example.com + + domainname: foo.com + + # String or list + # entrypoint: /code/entrypoint.sh -p 3000 + entrypoint: ["/code/entrypoint.sh", "-p", "3000"] + + # Items can be strings or numbers + expose: + - "3000" + - 8000 + + external_links: + - redis_1 + - project_db_1:mysql + - project_db_1:postgresql + + # Mapping or list + # Mapping values must be strings + # extra_hosts: + # somehost: "162.242.195.82" + # otherhost: "50.31.209.229" + extra_hosts: + - "somehost:162.242.195.82" + - "otherhost:50.31.209.229" + + hostname: foo + + healthcheck: + test: echo "hello world" + interval: 10s + timeout: 1s + retries: 5 + + # Any valid image reference - repo, tag, id, sha + image: redis + # image: ubuntu:14.04 + # image: tutum/influxdb + # image: example-registry.com:4000/postgresql + # image: a4bc65fd + # image: busybox@sha256:38a203e1986cf79639cfb9b2e1d6e773de84002feea2d4eb006b52004ee8502d + + ipc: host + + # Mapping or list + # Mapping values can be strings, numbers or null + labels: + com.example.description: "Accounting webapp" + com.example.number: 42 + com.example.empty-label: + # labels: + # - "com.example.description=Accounting webapp" + # - "com.example.number=42" + # - "com.example.empty-label" + + links: + - db + - db:database + - redis + + logging: + driver: syslog + options: + syslog-address: "tcp://192.168.0.42:123" + + mac_address: 02:42:ac:11:65:43 + + # network_mode: "bridge" + # network_mode: "host" + # network_mode: "none" + # Use the network mode of an arbitrary container from another service + # network_mode: "service:db" + # Use the network mode of another container, specified by name or id + # network_mode: "container:some-container" + network_mode: "container:0cfeab0f748b9a743dc3da582046357c6ef497631c1a016d28d2bf9b4f899f7b" + + networks: + some-network: + aliases: + - alias1 + - alias3 + other-network: + ipv4_address: 172.16.238.10 + ipv6_address: 2001:3984:3989::10 + other-other-network: + + pid: "host" + + ports: + - 3000 + - "3000-3005" + - "8000:8000" + - "9090-9091:8080-8081" + - "49100:22" + - "127.0.0.1:8001:8001" + - "127.0.0.1:5000-5010:5000-5010" + + privileged: true + + read_only: true + + restart: always + + security_opt: + - label=level:s0:c100,c200 + - label=type:svirt_apache_t + + stdin_open: true + + stop_grace_period: 20s + + stop_signal: SIGUSR1 + + # String or list + # tmpfs: /run + tmpfs: + - /run + - /tmp + + tty: true + + ulimits: + # Single number or mapping with soft + hard limits + nproc: 65535 + nofile: + soft: 20000 + hard: 40000 + + user: someone + + volumes: + # Just specify a path and let the Engine create a volume + - /var/lib/mysql + # Specify an absolute path mapping + - /opt/data:/var/lib/mysql + # Path on the host, relative to the Compose file + - .:/code + - ./static:/var/www/html + # User-relative path + - ~/configs:/etc/configs/:ro + # Named volume + - datavolume:/var/lib/mysql + + working_dir: /code + +networks: + # Entries can be null, which specifies simply that a network + # called "{project name}_some-network" should be created and + # use the default driver + some-network: + + other-network: + driver: overlay + + driver_opts: + # Values can be strings or numbers + foo: "bar" + baz: 1 + + ipam: + driver: overlay + # driver_opts: + # # Values can be strings or numbers + # com.docker.network.enable_ipv6: "true" + # com.docker.network.numeric_value: 1 + config: + - subnet: 172.16.238.0/24 + # gateway: 172.16.238.1 + - subnet: 2001:3984:3989::/64 + # gateway: 2001:3984:3989::1 + + external-network: + # Specifies that a pre-existing network called "external-network" + # can be referred to within this file as "external-network" + external: true + + other-external-network: + # Specifies that a pre-existing network called "my-cool-network" + # can be referred to within this file as "other-external-network" + external: + name: my-cool-network + +volumes: + # Entries can be null, which specifies simply that a volume + # called "{project name}_some-volume" should be created and + # use the default driver + some-volume: + + other-volume: + driver: flocker + + driver_opts: + # Values can be strings or numbers + foo: "bar" + baz: 1 + + external-volume: + # Specifies that a pre-existing volume called "external-volume" + # can be referred to within this file as "external-volume" + external: true + + other-external-volume: + # Specifies that a pre-existing volume called "my-cool-volume" + # can be referred to within this file as "other-external-volume" + external: + name: my-cool-volume diff --git a/script/test/fixtures/v3/docker-compose-volumes.yaml b/script/test/fixtures/v3/docker-compose-volumes.yaml new file mode 100644 index 000000000..4cf547ac6 --- /dev/null +++ b/script/test/fixtures/v3/docker-compose-volumes.yaml @@ -0,0 +1,8 @@ +version: "3" + +services: + + foobar: + image: foo/bar:latest + volumes: + - /tmp/foo/bar diff --git a/script/test/fixtures/v3/docker-compose.yaml b/script/test/fixtures/v3/docker-compose.yaml new file mode 100644 index 000000000..0400e2d9d --- /dev/null +++ b/script/test/fixtures/v3/docker-compose.yaml @@ -0,0 +1,24 @@ +version: "3" + +services: + + redis-master: + image: gcr.io/google_containers/redis:e2e + ports: + - "6379" + + redis-slave: + image: gcr.io/google_samples/gb-redisslave:v1 + ports: + - "6379" + environment: + - GET_HOSTS_FROM=dns + + frontend: + image: gcr.io/google-samples/gb-frontend:v4 + ports: + - "80:80" + environment: + - GET_HOSTS_FROM=dns + labels: + kompose.service.type: LoadBalancer diff --git a/script/test/fixtures/v3/example1.env b/script/test/fixtures/v3/example1.env new file mode 100644 index 000000000..3e7a05961 --- /dev/null +++ b/script/test/fixtures/v3/example1.env @@ -0,0 +1,8 @@ +# passed through +FOO=1 + +# overridden in example2.env +BAR=1 + +# overridden in full-example.yml +BAZ=1 diff --git a/script/test/fixtures/v3/example2.env b/script/test/fixtures/v3/example2.env new file mode 100644 index 000000000..0920d5ab0 --- /dev/null +++ b/script/test/fixtures/v3/example2.env @@ -0,0 +1 @@ +BAR=2 diff --git a/script/test/fixtures/v3/output-env-k8s.json b/script/test/fixtures/v3/output-env-k8s.json new file mode 100644 index 000000000..0c48d8a93 --- /dev/null +++ b/script/test/fixtures/v3/output-env-k8s.json @@ -0,0 +1,74 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "ports": [ + { + "name": "headless", + "port": 55555, + "targetPort": 0 + } + ], + "selector": { + "io.kompose.service": "foo" + }, + "clusterIP": "None" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "containers": [ + { + "name": "foo", + "image": "foo/bar:latest", + "env": [ + { + "name": "FOO", + "value": "foo" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/v3/output-k8s-full-example.json b/script/test/fixtures/v3/output-k8s-full-example.json new file mode 100644 index 000000000..2acbdd929 --- /dev/null +++ b/script/test/fixtures/v3/output-k8s-full-example.json @@ -0,0 +1,509 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + }, + "annotations": { + "com.example.description": "Accounting webapp", + "com.example.empty-label": "", + "com.example.number": "42" + } + }, + "spec": { + "ports": [ + { + "name": "3000", + "port": 3000, + "targetPort": 3000 + }, + { + "name": "3000", + "port": 3000, + "targetPort": 3000 + }, + { + "name": "3001", + "port": 3001, + "targetPort": 3001 + }, + { + "name": "3002", + "port": 3002, + "targetPort": 3002 + }, + { + "name": "3003", + "port": 3003, + "targetPort": 3003 + }, + { + "name": "3004", + "port": 3004, + "targetPort": 3004 + }, + { + "name": "3005", + "port": 3005, + "targetPort": 3005 + }, + { + "name": "8000", + "port": 8000, + "targetPort": 8000 + }, + { + "name": "9090", + "port": 9090, + "targetPort": 8080 + }, + { + "name": "9091", + "port": 9091, + "targetPort": 8081 + }, + { + "name": "49100", + "port": 49100, + "targetPort": 22 + }, + { + "name": "8001", + "port": 8001, + "targetPort": 8001 + }, + { + "name": "5000", + "port": 5000, + "targetPort": 5000 + }, + { + "name": "5001", + "port": 5001, + "targetPort": 5001 + }, + { + "name": "5002", + "port": 5002, + "targetPort": 5002 + }, + { + "name": "5003", + "port": 5003, + "targetPort": 5003 + }, + { + "name": "5004", + "port": 5004, + "targetPort": 5004 + }, + { + "name": "5005", + "port": 5005, + "targetPort": 5005 + }, + { + "name": "5006", + "port": 5006, + "targetPort": 5006 + }, + { + "name": "5007", + "port": 5007, + "targetPort": 5007 + }, + { + "name": "5008", + "port": 5008, + "targetPort": 5008 + }, + { + "name": "5009", + "port": 5009, + "targetPort": 5009 + }, + { + "name": "5010", + "port": 5010, + "targetPort": 5010 + } + ], + "selector": { + "io.kompose.service": "foo" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + }, + "annotations": { + "com.example.description": "Accounting webapp", + "com.example.empty-label": "", + "com.example.number": "42" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "volumes": [ + { + "name": "foo-claim0", + "persistentVolumeClaim": { + "claimName": "foo-claim0" + } + }, + { + "name": "foo-claim1", + "persistentVolumeClaim": { + "claimName": "foo-claim1" + } + }, + { + "name": "foo-claim2", + "persistentVolumeClaim": { + "claimName": "foo-claim2" + } + }, + { + "name": "foo-claim3", + "persistentVolumeClaim": { + "claimName": "foo-claim3" + } + }, + { + "name": "foo-claim4", + "persistentVolumeClaim": { + "claimName": "foo-claim4", + "readOnly": true + } + }, + { + "name": "datavolume", + "persistentVolumeClaim": { + "claimName": "datavolume" + } + }, + { + "name": "foo-tmpfs0", + "emptyDir": { + "medium": "Memory" + } + }, + { + "name": "foo-tmpfs1", + "emptyDir": { + "medium": "Memory" + } + } + ], + "containers": [ + { + "name": "my-web-container", + "image": "redis", + "command": [ + "/code/entrypoint.sh", + "-p", + "3000" + ], + "args": [ + "bundle", + "exec", + "thin", + "-p", + "3000" + ], + "workingDir": "/code", + "ports": [ + { + "containerPort": 3000 + }, + { + "containerPort": 3000 + }, + { + "containerPort": 3001 + }, + { + "containerPort": 3002 + }, + { + "containerPort": 3003 + }, + { + "containerPort": 3004 + }, + { + "containerPort": 3005 + }, + { + "containerPort": 8000 + }, + { + "containerPort": 8080 + }, + { + "containerPort": 8081 + }, + { + "containerPort": 22 + }, + { + "containerPort": 8001 + }, + { + "containerPort": 5000 + }, + { + "containerPort": 5001 + }, + { + "containerPort": 5002 + }, + { + "containerPort": 5003 + }, + { + "containerPort": 5004 + }, + { + "containerPort": 5005 + }, + { + "containerPort": 5006 + }, + { + "containerPort": 5007 + }, + { + "containerPort": 5008 + }, + { + "containerPort": 5009 + }, + { + "containerPort": 5010 + } + ], + "resources": { + "limits": { + "memory": "52428800" + } + }, + "volumeMounts": [ + { + "name": "foo-claim0", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-claim1", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-claim2", + "mountPath": "/code" + }, + { + "name": "foo-claim3", + "mountPath": "/var/www/html" + }, + { + "name": "foo-claim4", + "readOnly": true, + "mountPath": "/etc/configs/" + }, + { + "name": "datavolume", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-tmpfs0", + "mountPath": "/run" + }, + { + "name": "foo-tmpfs1", + "mountPath": "/tmp" + } + ], + "securityContext": { + "capabilities": { + "add": [ + "ALL" + ], + "drop": [ + "NET_ADMIN", + "SYS_ADMIN" + ] + }, + "privileged": true + }, + "stdin": true, + "tty": true + } + ], + "restartPolicy": "Always" + } + }, + "strategy": { + "type": "Recreate" + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim0", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim0" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim1", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim1" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim2", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim2" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim3", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim3" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim4", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim4" + } + }, + "spec": { + "accessModes": [ + "ReadOnlyMany" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "datavolume", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "datavolume" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/v3/output-k8s.json b/script/test/fixtures/v3/output-k8s.json new file mode 100644 index 000000000..8cabae2b6 --- /dev/null +++ b/script/test/fixtures/v3/output-k8s.json @@ -0,0 +1,221 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "frontend", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + }, + "annotations": { + "kompose.service.type": "LoadBalancer" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "io.kompose.service": "frontend" + }, + "type": "LoadBalancer" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis-master" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis-slave", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis-slave" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "frontend", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + }, + "annotations": { + "kompose.service.type": "LoadBalancer" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + } + }, + "spec": { + "containers": [ + { + "name": "frontend", + "image": "gcr.io/google-samples/gb-frontend:v4", + "ports": [ + { + "containerPort": 80 + } + ], + "env": [ + { + "name": "GET_HOSTS_FROM", + "value": "dns" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "redis-master", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "containers": [ + { + "name": "redis-master", + "image": "gcr.io/google_containers/redis:e2e", + "ports": [ + { + "containerPort": 6379 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "redis-slave", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "containers": [ + { + "name": "redis-slave", + "image": "gcr.io/google_samples/gb-redisslave:v1", + "ports": [ + { + "containerPort": 6379 + } + ], + "env": [ + { + "name": "GET_HOSTS_FROM", + "value": "dns" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/v3/output-os-full-example.json b/script/test/fixtures/v3/output-os-full-example.json new file mode 100644 index 000000000..845a07fa6 --- /dev/null +++ b/script/test/fixtures/v3/output-os-full-example.json @@ -0,0 +1,560 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + }, + "annotations": { + "com.example.description": "Accounting webapp", + "com.example.empty-label": "", + "com.example.number": "42" + } + }, + "spec": { + "ports": [ + { + "name": "3000", + "port": 3000, + "targetPort": 3000 + }, + { + "name": "3000", + "port": 3000, + "targetPort": 3000 + }, + { + "name": "3001", + "port": 3001, + "targetPort": 3001 + }, + { + "name": "3002", + "port": 3002, + "targetPort": 3002 + }, + { + "name": "3003", + "port": 3003, + "targetPort": 3003 + }, + { + "name": "3004", + "port": 3004, + "targetPort": 3004 + }, + { + "name": "3005", + "port": 3005, + "targetPort": 3005 + }, + { + "name": "8000", + "port": 8000, + "targetPort": 8000 + }, + { + "name": "9090", + "port": 9090, + "targetPort": 8080 + }, + { + "name": "9091", + "port": 9091, + "targetPort": 8081 + }, + { + "name": "49100", + "port": 49100, + "targetPort": 22 + }, + { + "name": "8001", + "port": 8001, + "targetPort": 8001 + }, + { + "name": "5000", + "port": 5000, + "targetPort": 5000 + }, + { + "name": "5001", + "port": 5001, + "targetPort": 5001 + }, + { + "name": "5002", + "port": 5002, + "targetPort": 5002 + }, + { + "name": "5003", + "port": 5003, + "targetPort": 5003 + }, + { + "name": "5004", + "port": 5004, + "targetPort": 5004 + }, + { + "name": "5005", + "port": 5005, + "targetPort": 5005 + }, + { + "name": "5006", + "port": 5006, + "targetPort": 5006 + }, + { + "name": "5007", + "port": 5007, + "targetPort": 5007 + }, + { + "name": "5008", + "port": 5008, + "targetPort": 5008 + }, + { + "name": "5009", + "port": 5009, + "targetPort": 5009 + }, + { + "name": "5010", + "port": 5010, + "targetPort": 5010 + } + ], + "selector": { + "io.kompose.service": "foo" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + }, + "annotations": { + "com.example.description": "Accounting webapp", + "com.example.empty-label": "", + "com.example.number": "42" + } + }, + "spec": { + "strategy": { + "type": "Recreate", + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "my-web-container" + ], + "from": { + "kind": "ImageStreamTag", + "name": "foo:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "foo" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "volumes": [ + { + "name": "foo-claim0", + "persistentVolumeClaim": { + "claimName": "foo-claim0" + } + }, + { + "name": "foo-claim1", + "persistentVolumeClaim": { + "claimName": "foo-claim1" + } + }, + { + "name": "foo-claim2", + "persistentVolumeClaim": { + "claimName": "foo-claim2" + } + }, + { + "name": "foo-claim3", + "persistentVolumeClaim": { + "claimName": "foo-claim3" + } + }, + { + "name": "foo-claim4", + "persistentVolumeClaim": { + "claimName": "foo-claim4", + "readOnly": true + } + }, + { + "name": "datavolume", + "persistentVolumeClaim": { + "claimName": "datavolume" + } + }, + { + "name": "foo-tmpfs0", + "emptyDir": { + "medium": "Memory" + } + }, + { + "name": "foo-tmpfs1", + "emptyDir": { + "medium": "Memory" + } + } + ], + "containers": [ + { + "name": "my-web-container", + "image": " ", + "command": [ + "/code/entrypoint.sh", + "-p", + "3000" + ], + "args": [ + "bundle", + "exec", + "thin", + "-p", + "3000" + ], + "workingDir": "/code", + "ports": [ + { + "containerPort": 3000 + }, + { + "containerPort": 3000 + }, + { + "containerPort": 3001 + }, + { + "containerPort": 3002 + }, + { + "containerPort": 3003 + }, + { + "containerPort": 3004 + }, + { + "containerPort": 3005 + }, + { + "containerPort": 8000 + }, + { + "containerPort": 8080 + }, + { + "containerPort": 8081 + }, + { + "containerPort": 22 + }, + { + "containerPort": 8001 + }, + { + "containerPort": 5000 + }, + { + "containerPort": 5001 + }, + { + "containerPort": 5002 + }, + { + "containerPort": 5003 + }, + { + "containerPort": 5004 + }, + { + "containerPort": 5005 + }, + { + "containerPort": 5006 + }, + { + "containerPort": 5007 + }, + { + "containerPort": 5008 + }, + { + "containerPort": 5009 + }, + { + "containerPort": 5010 + } + ], + "resources": { + "limits": { + "memory": "52428800" + } + }, + "volumeMounts": [ + { + "name": "foo-claim0", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-claim1", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-claim2", + "mountPath": "/code" + }, + { + "name": "foo-claim3", + "mountPath": "/var/www/html" + }, + { + "name": "foo-claim4", + "readOnly": true, + "mountPath": "/etc/configs/" + }, + { + "name": "datavolume", + "mountPath": "/var/lib/mysql" + }, + { + "name": "foo-tmpfs0", + "mountPath": "/run" + }, + { + "name": "foo-tmpfs1", + "mountPath": "/tmp" + } + ], + "securityContext": { + "capabilities": { + "add": [ + "ALL" + ], + "drop": [ + "NET_ADMIN", + "SYS_ADMIN" + ] + }, + "privileged": true + }, + "stdin": true, + "tty": true + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "foo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "redis" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim0", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim0" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim1", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim1" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim2", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim2" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim3", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim3" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foo-claim4", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foo-claim4" + } + }, + "spec": { + "accessModes": [ + "ReadOnlyMany" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "datavolume", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "datavolume" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + } + ] +} diff --git a/script/test/fixtures/v3/output-os.json b/script/test/fixtures/v3/output-os.json new file mode 100644 index 000000000..3f9edf230 --- /dev/null +++ b/script/test/fixtures/v3/output-os.json @@ -0,0 +1,377 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "frontend", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + }, + "annotations": { + "kompose.service.type": "LoadBalancer" + } + }, + "spec": { + "ports": [ + { + "name": "80", + "port": 80, + "targetPort": 80 + } + ], + "selector": { + "io.kompose.service": "frontend" + }, + "type": "LoadBalancer" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis-master" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis-slave", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis-slave" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "frontend", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + }, + "annotations": { + "kompose.service.type": "LoadBalancer" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "frontend" + ], + "from": { + "kind": "ImageStreamTag", + "name": "frontend:v4" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "frontend" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + } + }, + "spec": { + "containers": [ + { + "name": "frontend", + "image": " ", + "ports": [ + { + "containerPort": 80 + } + ], + "env": [ + { + "name": "GET_HOSTS_FROM", + "value": "dns" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "frontend", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "frontend" + } + }, + "spec": { + "tags": [ + { + "name": "v4", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "gcr.io/google-samples/gb-frontend:v4" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "redis-master" + ], + "from": { + "kind": "ImageStreamTag", + "name": "redis-master:e2e" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "redis-master" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "containers": [ + { + "name": "redis-master", + "image": " ", + "ports": [ + { + "containerPort": 6379 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "redis-master", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-master" + } + }, + "spec": { + "tags": [ + { + "name": "e2e", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "gcr.io/google_containers/redis:e2e" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "redis-slave", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "redis-slave" + ], + "from": { + "kind": "ImageStreamTag", + "name": "redis-slave:v1" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "redis-slave" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "containers": [ + { + "name": "redis-slave", + "image": " ", + "ports": [ + { + "containerPort": 6379 + } + ], + "env": [ + { + "name": "GET_HOSTS_FROM", + "value": "dns" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "redis-slave", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis-slave" + } + }, + "spec": { + "tags": [ + { + "name": "v1", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "gcr.io/google_samples/gb-redisslave:v1" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + } + ] +} diff --git a/script/test/fixtures/v3/output-volumes-k8s.json b/script/test/fixtures/v3/output-volumes-k8s.json new file mode 100644 index 000000000..2b148595d --- /dev/null +++ b/script/test/fixtures/v3/output-volumes-k8s.json @@ -0,0 +1,106 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "foobar", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foobar" + } + }, + "spec": { + "ports": [ + { + "name": "headless", + "port": 55555, + "targetPort": 0 + } + ], + "selector": { + "io.kompose.service": "foobar" + }, + "clusterIP": "None" + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "foobar", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foobar" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foobar" + } + }, + "spec": { + "volumes": [ + { + "name": "foobar-claim0", + "persistentVolumeClaim": { + "claimName": "foobar-claim0" + } + } + ], + "containers": [ + { + "name": "foobar", + "image": "foo/bar:latest", + "resources": {}, + "volumeMounts": [ + { + "name": "foobar-claim0", + "mountPath": "/tmp/foo/bar" + } + ] + } + ], + "restartPolicy": "Always" + } + }, + "strategy": { + "type": "Recreate" + } + }, + "status": {} + }, + { + "kind": "PersistentVolumeClaim", + "apiVersion": "v1", + "metadata": { + "name": "foobar-claim0", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "foobar-claim0" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "100Mi" + } + } + }, + "status": {} + } + ] +}