diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go index f0553a92..d49c4697 100644 --- a/pkg/loader/compose/v3.go +++ b/pkg/loader/compose/v3.go @@ -31,6 +31,7 @@ import ( "os" + "fmt" "github.com/kubernetes/kompose/pkg/kobject" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -66,44 +67,55 @@ func parseV3(files []string) (kobject.KomposeObject, error) { 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, - } - // get environment variables env, err := buildEnvironment() if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "cannot build environment variables") } - // Config details - configDetails := types.ConfigDetails{ - WorkingDir: workingDir, - ConfigFiles: []types.ConfigFile{configFile}, - Environment: env, - } + var config *types.Config + for _, file := range files { + // Load and then parse the YAML first! + loadedFile, err := ioutil.ReadFile(file) + if err != nil { + return kobject.KomposeObject{}, err + } - // 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 + // Parse the Compose File + parsedComposeFile, err := loader.ParseYAML(loadedFile) + if err != nil { + return kobject.KomposeObject{}, err + } + + // Config file + configFile := types.ConfigFile{ + Filename: file, + Config: parsedComposeFile, + } + + // Config details + configDetails := types.ConfigDetails{ + WorkingDir: workingDir, + ConfigFiles: []types.ConfigFile{configFile}, + Environment: env, + } + + // 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 + currentConfig, err := loader.Load(configDetails) + if err != nil { + return kobject.KomposeObject{}, err + } + if config == nil { + config = currentConfig + } else { + config, err = mergeComposeObject(config, currentConfig) + if err != nil { + return kobject.KomposeObject{}, err + } + } } // TODO: Check all "unsupported" keys and output details @@ -398,3 +410,165 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose return komposeObject, nil } + +func mergeComposeObject(oldCompose *types.Config, newCompose *types.Config) (*types.Config, error) { + if oldCompose == nil || newCompose == nil { + return nil, fmt.Errorf("Merge multiple compose error, compose config is nil") + } + oldComposeServiceNameMap := make(map[string]int, len(oldCompose.Services)) + for index, service := range oldCompose.Services { + oldComposeServiceNameMap[service.Name] = index + } + + for _, service := range newCompose.Services { + index := 0 + if tmpIndex, ok := oldComposeServiceNameMap[service.Name]; !ok { + oldCompose.Services = append(oldCompose.Services, service) + continue + } else { + index = tmpIndex + } + tmpOldService := oldCompose.Services[index] + if service.Build.Dockerfile != "" { + tmpOldService.Build = service.Build + } + if len(service.CapAdd) != 0 { + tmpOldService.CapAdd = service.CapAdd + } + if len(service.CapDrop) != 0 { + tmpOldService.CapDrop = service.CapDrop + } + if service.CgroupParent != "" { + tmpOldService.CgroupParent = service.CgroupParent + } + if len(service.Command) != 0 { + tmpOldService.Command = service.Command + } + if len(service.Configs) != 0 { + tmpOldService.Configs = service.Configs + } + if service.ContainerName != "" { + tmpOldService.ContainerName = service.ContainerName + } + if service.CredentialSpec.File != "" || service.CredentialSpec.Registry != "" { + tmpOldService.CredentialSpec = service.CredentialSpec + } + if len(service.DependsOn) != 0 { + tmpOldService.DependsOn = service.DependsOn + } + if service.Deploy.Mode != "" { + tmpOldService.Deploy = service.Deploy + } + if len(service.Devices) != 0 { + tmpOldService.Devices = service.Devices + } + if len(service.DNS) != 0 { + tmpOldService.DNS = service.DNS + } + if len(service.DNSSearch) != 0 { + tmpOldService.DNSSearch = service.DNSSearch + } + if service.DomainName != "" { + tmpOldService.DomainName = service.DomainName + } + if len(service.Entrypoint) != 0 { + tmpOldService.Entrypoint = service.Entrypoint + } + if len(service.Environment) != 0 { + tmpOldService.Environment = service.Environment + } + if len(service.EnvFile) != 0 { + tmpOldService.EnvFile = service.EnvFile + } + if len(service.Expose) != 0 { + tmpOldService.Expose = service.Expose + } + if len(service.ExternalLinks) != 0 { + tmpOldService.ExternalLinks = service.ExternalLinks + } + if len(service.ExtraHosts) != 0 { + tmpOldService.ExtraHosts = service.ExtraHosts + } + if service.Hostname != "" { + tmpOldService.Hostname = service.Hostname + } + if service.HealthCheck != nil { + tmpOldService.HealthCheck = service.HealthCheck + } + if service.Image != "" { + tmpOldService.Image = service.Image + } + if service.Ipc != "" { + tmpOldService.Ipc = service.Ipc + } + if len(service.Labels) != 0 { + tmpOldService.Labels = service.Labels + } + if len(service.Links) != 0 { + tmpOldService.Links = service.Links + } + if service.Logging != nil { + tmpOldService.Logging = service.Logging + } + if service.MacAddress != "" { + tmpOldService.MacAddress = service.MacAddress + } + if service.NetworkMode != "" { + tmpOldService.NetworkMode = service.NetworkMode + } + if len(service.Networks) != 0 { + tmpOldService.Networks = service.Networks + } + if service.Pid != "" { + tmpOldService.Pid = service.Pid + } + if len(service.Ports) != 0 { + tmpOldService.Ports = service.Ports + } + if service.Privileged != tmpOldService.Privileged { + tmpOldService.Privileged = service.Privileged + } + if service.ReadOnly != tmpOldService.ReadOnly { + tmpOldService.ReadOnly = service.ReadOnly + } + if service.Restart != "" { + tmpOldService.Restart = service.Restart + } + if len(service.Secrets) != 0 { + tmpOldService.Secrets = service.Secrets + } + if len(service.SecurityOpt) != 0 { + tmpOldService.SecurityOpt = service.SecurityOpt + } + if service.StdinOpen != tmpOldService.StdinOpen { + tmpOldService.StdinOpen = service.StdinOpen + } + if service.StopGracePeriod != nil { + tmpOldService.StopGracePeriod = service.StopGracePeriod + } + if service.StopSignal != "" { + tmpOldService.StopSignal = service.StopSignal + } + if len(service.Tmpfs) != 0 { + tmpOldService.Tmpfs = service.Tmpfs + } + if service.Tty != tmpOldService.Tty { + tmpOldService.Tty = service.Tty + } + if len(service.Ulimits) != 0 { + tmpOldService.Ulimits = service.Ulimits + } + if service.User != "" { + tmpOldService.User = service.User + } + if len(service.Volumes) != 0 { + tmpOldService.Volumes = service.Volumes + } + if service.WorkingDir != "" { + tmpOldService.WorkingDir = service.WorkingDir + } + oldCompose.Services[index] = tmpOldService + } + + return oldCompose, nil +} diff --git a/script/test/cmd/tests.sh b/script/test/cmd/tests.sh index 34998bf3..8f8c9157 100755 --- a/script/test/cmd/tests.sh +++ b/script/test/cmd/tests.sh @@ -122,7 +122,23 @@ cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/mem-limit/docker-compose-mb.y sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/mem-limit/output-mb-k8s-template.json > /tmp/output-k8s.json convert::expect_success "$cmd" "/tmp/output-k8s.json" +###### +# Tests merge multiple docker-compose files +cmd="kompose convert -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/base.yml -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/dev.yml --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/output-base-template.json > /tmp/output-k8s.json +convert::expect_success "$cmd" "/tmp/output-k8s.json" +cmd="kompose convert -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-port-base.yml -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-port-prod.yml --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/output-compose-port-template.json > /tmp/output-k8s.json +convert::expect_success "$cmd" "/tmp/output-k8s.json" + +cmd="kompose convert -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-port-base.yml -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-new-service-prob.yml --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/output-compose-new-service-template.json > /tmp/output-k8s.json +convert::expect_success "$cmd" "/tmp/output-k8s.json" + +cmd="kompose --provider=openshift convert -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-port-base.yml -f $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/compose-new-service-prob.yml --stdout -j" +sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" $KOMPOSE_ROOT/script/test/fixtures/merge-multiple-compose/output-openshift-template.json > /tmp/output-os.json +convert::expect_success "$cmd" "/tmp/output-os.json" ###### # Tests related to docker-compose file in /script/test/fixtures/ports-with-proto diff --git a/script/test/fixtures/merge-multiple-compose/base.yml b/script/test/fixtures/merge-multiple-compose/base.yml new file mode 100644 index 00000000..ef719711 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/base.yml @@ -0,0 +1,12 @@ +version: '3' + +services: + web: + image: richarvey/nginx-php-fpm + environment: + - ERRORS=0 + - HIDE_NGINX_HEADERS=0 + - REMOVE_FILES=0 + - RUN_SCRIPTS=0 + - PHP_ERRORS_STDERR=0 + - ENABLE_XDEBUG=0 \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/compose-new-service-prob.yml b/script/test/fixtures/merge-multiple-compose/compose-new-service-prob.yml new file mode 100644 index 00000000..28c08fe2 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/compose-new-service-prob.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + server: + ports: + - 5000:5000 + new-my-service: + image: nginx \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/compose-port-base.yml b/script/test/fixtures/merge-multiple-compose/compose-port-base.yml new file mode 100644 index 00000000..124fc3bd --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/compose-port-base.yml @@ -0,0 +1,11 @@ +version: '3' + +services: + server: + image: test + container_name: test_server + build: + context: . + dockerfile: Dockerfile-dev + ports: + - 3000:3000 \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/compose-port-prod.yml b/script/test/fixtures/merge-multiple-compose/compose-port-prod.yml new file mode 100644 index 00000000..fb3dc5b5 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/compose-port-prod.yml @@ -0,0 +1,6 @@ +version: '3' + +services: + server: + ports: + - 5000:5000 \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/dev.yml b/script/test/fixtures/merge-multiple-compose/dev.yml new file mode 100644 index 00000000..fe1f7e7e --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/dev.yml @@ -0,0 +1,11 @@ +version: '3' + +services: + web: + environment: + - ERRORS=1 + - HIDE_NGINX_HEADERS=0 + - REMOVE_FILES=0 + - RUN_SCRIPTS=1 + - PHP_ERRORS_STDERR=1 + - ENABLE_XDEBUG=1 \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/output-base-template.json b/script/test/fixtures/merge-multiple-compose/output-base-template.json new file mode 100644 index 00000000..b0e82493 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/output-base-template.json @@ -0,0 +1,71 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "web", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "web" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "web" + } + }, + "spec": { + "containers": [ + { + "name": "web", + "image": "richarvey/nginx-php-fpm", + "env": [ + { + "name": "ENABLE_XDEBUG", + "value": "1" + }, + { + "name": "ERRORS", + "value": "1" + }, + { + "name": "HIDE_NGINX_HEADERS", + "value": "0" + }, + { + "name": "PHP_ERRORS_STDERR", + "value": "1" + }, + { + "name": "REMOVE_FILES", + "value": "0" + }, + { + "name": "RUN_SCRIPTS", + "value": "1" + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/output-compose-new-service-template.json b/script/test/fixtures/merge-multiple-compose/output-compose-new-service-template.json new file mode 100644 index 00000000..c6642f8c --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/output-compose-new-service-template.json @@ -0,0 +1,118 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "5000", + "port": 5000, + "targetPort": 5000 + } + ], + "selector": { + "io.kompose.service": "server" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "new-my-service", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "new-my-service" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "new-my-service" + } + }, + "spec": { + "containers": [ + { + "name": "new-my-service", + "image": "nginx", + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + } + }, + "spec": { + "containers": [ + { + "name": "test_server", + "image": "test", + "ports": [ + { + "containerPort": 5000 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/output-compose-port-template.json b/script/test/fixtures/merge-multiple-compose/output-compose-port-template.json new file mode 100644 index 00000000..4f0f0816 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/output-compose-port-template.json @@ -0,0 +1,80 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "5000", + "port": 5000, + "targetPort": 5000 + } + ], + "selector": { + "io.kompose.service": "server" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "extensions/v1beta1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + } + }, + "spec": { + "containers": [ + { + "name": "test_server", + "image": "test", + "ports": [ + { + "containerPort": 5000 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} \ No newline at end of file diff --git a/script/test/fixtures/merge-multiple-compose/output-openshift-template.json b/script/test/fixtures/merge-multiple-compose/output-openshift-template.json new file mode 100644 index 00000000..971c3b49 --- /dev/null +++ b/script/test/fixtures/merge-multiple-compose/output-openshift-template.json @@ -0,0 +1,222 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "ports": [ + { + "name": "5000", + "port": 5000, + "targetPort": 5000 + } + ], + "selector": { + "io.kompose.service": "server" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "new-my-service", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "new-my-service" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "new-my-service" + ], + "from": { + "kind": "ImageStreamTag", + "name": "new-my-service:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "new-my-service" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "new-my-service" + } + }, + "spec": { + "containers": [ + { + "name": "new-my-service", + "image": " ", + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "new-my-service", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "new-my-service" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "nginx" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + }, + "annotations": { + "kompose.cmd": "%CMD%", + "kompose.version": "%VERSION%" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "test_server" + ], + "from": { + "kind": "ImageStreamTag", + "name": "server:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "server" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + } + }, + "spec": { + "containers": [ + { + "name": "test_server", + "image": " ", + "ports": [ + { + "containerPort": 5000 + } + ], + "resources": {} + } + ], + "restartPolicy": "Always" + } + } + }, + "status": {} + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "server", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "server" + } + }, + "spec": { + "tags": [ + { + "name": "latest", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "test" + }, + "generation": null, + "importPolicy": {} + } + ] + }, + "status": { + "dockerImageRepository": "" + } + } + ] +} \ No newline at end of file