From a8b019f30bd95a24384bbf77764fe2cb5a5c3104 Mon Sep 17 00:00:00 2001 From: Charlie Drage Date: Tue, 6 Jun 2017 12:48:38 -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 | 317 +++++++++- pkg/loader/compose/compose_test.go | 37 ++ script/test/cmd/tests.sh | 18 + .../v3/docker-compose-full-example.yaml | 268 +++++++++ script/test/fixtures/v3/docker-compose.yaml | 24 + script/test/fixtures/v3/example1.env | 8 + script/test/fixtures/v3/example2.env | 1 + .../fixtures/v3/output-k8s-full-example.json | 509 ++++++++++++++++ script/test/fixtures/v3/output-k8s.json | 219 +++++++ .../fixtures/v3/output-os-full-example.json | 560 ++++++++++++++++++ script/test/fixtures/v3/output-os.json | 375 ++++++++++++ 11 files changed, 2330 insertions(+), 6 deletions(-) create mode 100644 script/test/fixtures/v3/docker-compose-full-example.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-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 diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index aaeb444bc1..0c0fe8a105 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -18,6 +18,7 @@ package compose import ( "fmt" + "io/ioutil" "net" "os" "path/filepath" @@ -25,13 +26,17 @@ import ( "strconv" "strings" + libcomposeyaml "github.com/docker/libcompose/yaml" "k8s.io/kubernetes/pkg/api" log "github.com/Sirupsen/logrus" + "github.com/docker/cli/cli/compose/loader" + "github.com/docker/cli/cli/compose/types" "github.com/docker/libcompose/config" "github.com/docker/libcompose/lookup" "github.com/docker/libcompose/project" "github.com/fatih/structs" + "github.com/ghodss/yaml" "github.com/kubernetes-incubator/kompose/pkg/kobject" "github.com/pkg/errors" ) @@ -177,6 +182,51 @@ func loadEnvVars(envars []string) []kobject.EnvVar { return envs } +// 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 := 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 +} + // Load ports from compose file func loadPorts(composePorts []string) ([]kobject.Ports, error) { ports := []kobject.Ports{} @@ -272,12 +322,131 @@ func loadPorts(composePorts []string) ([]kobject.Ports, error) { return ports, nil } -// LoadFile loads compose file into KomposeObject +// LoadFile loads a compose file into KomposeObject func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { - komposeObject := kobject.KomposeObject{ - ServiceConfigs: make(map[string]kobject.ServiceConfig), - LoadedFrom: "compose", + + // Load the json / yaml file in order to get the version value + var version string + + 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") + } + + // Check that the previous file loaded matches. + if len(files) > 0 && version != "" && version != composeVersion { + return kobject.KomposeObject{}, errors.New("All files passed must be the same version") + } + version = composeVersion + } + + log.Debugf("Docker Compose version: %s", version) + + // 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 := parseV1V2ComposeWithLibcompose(files) + if err != nil { + return kobject.KomposeObject{}, err + } + return komposeObject, nil + // Use docker/cli for 3 + case "3", "3.0": + komposeObject, err := parseV3WithDockerCLI(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) } + +} + +// 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 +} + +// 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 parseV3WithDockerCLI(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 +} + +// Parse Docker Compose with libcompose (only supports v1 and v2). Eventually we will +// switch to using only libcompose once v3 is supported. +func parseV1V2ComposeWithLibcompose(files []string) (kobject.KomposeObject, error) { + + // Gather the appropriate context for parsing context := &project.Context{} context.ComposeFiles = files @@ -300,7 +469,7 @@ func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { } } - // load compose file into composeObject + // Load the context and let's start parsing composeObject := project.NewProject(context, nil, nil) err := composeObject.Parse() if err != nil { @@ -312,6 +481,124 @@ func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { 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 +} + +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 + var envKeys []string + for key := range composeServiceConfig.Environment { + envKeys = append(envKeys, key) + } + envs := loadEnvVars(envKeys) + serviceConfig.Environment = envs + + // 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 +} + +// 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 @@ -388,7 +675,6 @@ func (c *Compose) LoadFile(files []string) (kobject.KomposeObject, error) { log.Infof("Service name in docker-compose has been changed from %q to %q", name, normalizeServiceNames(name)) } } - return komposeObject, nil } @@ -408,3 +694,22 @@ func handleServiceType(ServiceType string) (string, error) { func normalizeServiceNames(svcName string) string { return strings.Replace(svcName, "_", "-", -1) } + +func getVersionFromFile(file string) (string, error) { + type ComposeVersion struct { + Version string `json:"version"` // This affects YAML as well + } + var version ComposeVersion + + loadedFile, err := ioutil.ReadFile(file) + if err != nil { + return "", err + } + + err = yaml.Unmarshal(loadedFile, &version) + if err != nil { + return "", err + } + + return version.Version, nil +} diff --git a/pkg/loader/compose/compose_test.go b/pkg/loader/compose/compose_test.go index 6960f4af77..9b8b566b88 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/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 04256cf99d..ef24e42a80 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -249,4 +249,22 @@ 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 the Kompose example we use + +# 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-full-example.yaml b/script/test/fixtures/v3/docker-compose-full-example.yaml new file mode 100644 index 0000000000..68ed34d376 --- /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.yaml b/script/test/fixtures/v3/docker-compose.yaml new file mode 100644 index 0000000000..0400e2d9d8 --- /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 0000000000..3e7a059613 --- /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 0000000000..0920d5ab05 --- /dev/null +++ b/script/test/fixtures/v3/example2.env @@ -0,0 +1 @@ +BAR=2 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 0000000000..2acbdd929d --- /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 0000000000..ae39823ddf --- /dev/null +++ b/script/test/fixtures/v3/output-k8s.json @@ -0,0 +1,219 @@ +{ + "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" + } + ], + "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" + } + ], + "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 0000000000..845a07fa64 --- /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 0000000000..ccb00d9ad8 --- /dev/null +++ b/script/test/fixtures/v3/output-os.json @@ -0,0 +1,375 @@ +{ + "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" + } + ], + "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" + } + ], + "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": "" + } + } + ] +}