diff --git a/docs/user-guide.md b/docs/user-guide.md index 1b8e9ac2..be213f5b 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -211,6 +211,9 @@ The currently supported options are: | kompose.cronjob.schedule | kubernetes cronjob schedule (for example: '1 * * * *') | | kompose.cronjob.concurrency_policy | 'Forbid' / 'Allow' / 'Never' / '' | | kompose.cronjob.backoff_limit | kubernetes cronjob backoff limit (for example: '6') | +| kompose.init.containers.name | kubernetes init container name | +| kompose.init.containers.image | kubernetes init container image | +| kompose.init.containers.command | kubernetes init container commands | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. @@ -467,6 +470,48 @@ services: labels: kompose.volume.sub-path: pg-data ``` + +- `kompose.init.containers.name` is used to specify the name of the Init Containers for a Pod [Init Container Name](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + +For example: + +```yaml +version: '3' +services: + example-service: + image: example-image + labels: + kompose.init.containers.name: "initcontainername" +``` + +- `kompose.init.containers.image` defines image to use for the Init Containers [Init Container Image](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + +For example: + +```yaml +version: '3' +services: + example-service: + image: example-image + labels: + kompose.init.containers.image: perl +``` + + +- `kompose.init.containers.command` defines the command that the Init Containers will run after they are started [Init Container Command](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/) + +For example: + +```yaml +version: '3' +services: + example-service: + image: example-image + labels: + kompose.init.containers.command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"] + kompose.init.containers.image: perl +``` + ## Restart If you want to create normal pods without controller you can use `restart` construct of compose to define that. Follow table below to see what happens on the `restart` value. diff --git a/go.mod b/go.mod index e7d942a7..775aec3e 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 - golang.org/x/tools v0.16.1 + golang.org/x/tools v0.19.0 gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.1 k8s.io/api v0.28.4 @@ -62,10 +62,10 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/mod v0.16.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index d2da6d99..a748849a 100644 --- a/go.sum +++ b/go.sum @@ -333,8 +333,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= +golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -367,8 +367,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -388,8 +388,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= -golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -435,10 +435,10 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -502,8 +502,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw= +golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 014579d5..4217d663 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -144,6 +144,7 @@ type ServiceConfig struct { ExposeService string `compose:"kompose.service.expose"` ExposeServicePath string `compose:"kompose.service.expose.path"` BuildLabels map[string]string `compose:"build-labels"` + BuildTarget string `compose:""` ExposeServiceTLS string `compose:"kompose.service.expose.tls-secret"` ExposeServiceIngressClassName string `compose:"kompose.service.expose.ingress-class-name"` ImagePullSecret string `compose:"kompose.image-pull-secret"` diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index 86926f1e..e1e088aa 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -559,6 +559,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Project) (kobject.Kompos serviceConfig.Dockerfile = composeServiceConfig.Build.Dockerfile serviceConfig.BuildArgs = composeServiceConfig.Build.Args serviceConfig.BuildLabels = composeServiceConfig.Build.Labels + serviceConfig.BuildTarget = composeServiceConfig.Build.Target } // env diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index 98713294..67136476 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -88,6 +88,12 @@ const ( LabelCronJobConcurrencyPolicy = "kompose.cronjob.concurrency_policy" // LabelCronJobBackoffLimit defines the job backoff limit LabelCronJobBackoffLimit = "kompose.cronjob.backoff_limit" + // LabelInitContainerName defines name resource + LabelInitContainerName = "kompose.init.containers.name" + // LabelInitContainerImage defines image to pull + LabelInitContainerImage = "kompose.init.containers.image" + // LabelInitContainerCommand defines commands + LabelInitContainerCommand = "kompose.init.containers.command" // LabelNameOverride defines the override resource name LabelNameOverride = "kompose.service.name_override" ) diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 890e3455..064516e1 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -654,7 +654,7 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok { template.Spec.ServiceAccountName = serviceAccountName } - + fillInitContainers(template, service) return nil } @@ -985,3 +985,48 @@ func reformatSecretConfigUnderscoreWithDash(secretConfig types.ServiceSecretConf return newSecretConfig } + +// fillInitContainers looks for an initContainer resources and its passed as labels +// if there is no image, it does not fill the initContainer +// https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +func fillInitContainers(template *api.PodTemplateSpec, service kobject.ServiceConfig) { + resourceImage, exist := service.Labels[compose.LabelInitContainerImage] + if !exist || resourceImage == "" { + return + } + resourceName, exist := service.Labels[compose.LabelInitContainerName] + if !exist || resourceName == "" { + resourceName = "init-service" + } + + template.Spec.InitContainers = append(template.Spec.InitContainers, api.Container{ + Name: resourceName, + Command: parseContainerCommandsFromStr(service.Labels[compose.LabelInitContainerCommand]), + Image: resourceImage, + }) +} + +// parseContainerCommandsFromStr parses a string containing comma-separated commands +// returns a slice of strings or a single command +// example: +// [ "bundle", "exec", "thin", "-p", "3000" ] +// +// example: +// [ "bundle exec thin -p 3000" ] +func parseContainerCommandsFromStr(line string) []string { + if line == "" { + return []string{} + } + var commands []string + if strings.Contains(line, ",") { + line = strings.TrimSpace(strings.Trim(line, "[]")) + commands = strings.Split(line, ",") + // remove space "' + for i := range commands { + commands[i] = strings.TrimSpace(strings.Trim(commands[i], `"' `)) + } + } else { + commands = append(commands, line) + } + return commands +} diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 637d7f2b..d70d06d9 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -29,6 +29,7 @@ import ( "github.com/kubernetes/kompose/pkg/testutils" "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" + api "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" ) @@ -738,3 +739,209 @@ func TestRemoveEmptyInterfaces(t *testing.T) { }) } } + +func Test_parseContainerCommandsFromStr(t *testing.T) { + tests := []struct { + name string + line string + want []string + }{ + { + name: "line command without spaces in between", + line: `[ "bundle", "exec", "thin", "-p", "3000" ]`, + want: []string{ + "bundle", "exec", "thin", "-p", "3000", + }, + }, + { + name: `line command spaces inside ""`, + line: `[ " bundle ", " exec ", " thin ", " -p ", "3000" ]`, + want: []string{ + "bundle", "exec", "thin", "-p", "3000", + }, + }, + { + name: `more use cases for line command spaces inside ""`, + line: `[ " bundle ", "exec ", " thin ", " -p ", "3000 " ]`, + want: []string{ + "bundle", "exec", "thin", "-p", "3000", + }, + }, + { + name: `line command without [] and ""`, + line: `bundle exec thin -p 3000`, + want: []string{ + "bundle exec thin -p 3000", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseContainerCommandsFromStr(tt.line); !reflect.DeepEqual(got, tt.want) { + t.Errorf("parseContainerCommandsFromStr() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_fillInitContainers(t *testing.T) { + type args struct { + template *api.PodTemplateSpec + service kobject.ServiceConfig + } + tests := []struct { + name string + args args + want []corev1.Container + }{ + { + name: "Testing init container are generated from labels with ,", + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "name", + compose.LabelInitContainerImage: "image", + compose.LabelInitContainerCommand: `[ "bundle", "exec", "thin", "-p", "3000" ]`, + }, + }, + }, + want: []corev1.Container{ + { + Name: "name", + Image: "image", + Command: []string{ + "bundle", "exec", "thin", "-p", "3000", + }, + }, + }, + }, + { + name: "Testing init container are generated from labels without ,", + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "name", + compose.LabelInitContainerImage: "image", + compose.LabelInitContainerCommand: `bundle exec thin -p 3000`, + }, + }, + }, + want: []corev1.Container{ + { + Name: "name", + Image: "image", + Command: []string{ + `bundle exec thin -p 3000`, + }, + }, + }, + }, + { + name: `Testing init container with long command with vars inside and ''`, + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "init-myservice", + compose.LabelInitContainerImage: "busybox:1.28", + compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, + }, + }, + }, + want: []corev1.Container{ + { + Name: "init-myservice", + Image: "busybox:1.28", + Command: []string{ + "sh", "-c", `until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done`, + }, + }, + }, + }, + { + name: `without image`, + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "init-myservice", + compose.LabelInitContainerImage: "", + compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, + }, + }, + }, + want: nil, + }, + { + name: `Testing init container without name`, + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "", + compose.LabelInitContainerImage: "busybox:1.28", + compose.LabelInitContainerCommand: `['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]`, + }, + }, + }, + want: []corev1.Container{ + { + Name: "init-service", + Image: "busybox:1.28", + Command: []string{ + "sh", "-c", `until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done`, + }, + }, + }, + }, + { + name: `Testing init container without command`, + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "init-service", + compose.LabelInitContainerImage: "busybox:1.28", + compose.LabelInitContainerCommand: ``, + }, + }, + }, + want: []corev1.Container{ + { + Name: "init-service", + Image: "busybox:1.28", + Command: []string{}, + }, + }, + }, + { + name: `Testing init container without command`, + args: args{ + template: &api.PodTemplateSpec{}, + service: kobject.ServiceConfig{ + Labels: map[string]string{ + compose.LabelInitContainerName: "init-service", + compose.LabelInitContainerImage: "busybox:1.28", + }, + }, + }, + want: []corev1.Container{ + { + Name: "init-service", + Image: "busybox:1.28", + Command: []string{}, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fillInitContainers(tt.args.template, tt.args.service) + if !reflect.DeepEqual(tt.args.template.Spec.InitContainers, tt.want) { + t.Errorf("Test_fillInitContainers Fail got %v, want %v", tt.args.template.Spec.InitContainers, tt.want) + } + }) + } +} diff --git a/pkg/transformer/utils.go b/pkg/transformer/utils.go index 97598f15..db046662 100644 --- a/pkg/transformer/utils.go +++ b/pkg/transformer/utils.go @@ -396,7 +396,7 @@ func BuildDockerImage(service kobject.ServiceConfig, name string) error { // Use the build struct function to build the image // Build the image! build := docker.Build{Client: *client} - err = build.BuildImage(imagePath, imageName, service.Dockerfile, buildargs) + err = build.BuildImage(imagePath, imageName, service.Dockerfile, buildargs, service.BuildTarget) if err != nil { return err diff --git a/pkg/utils/docker/build.go b/pkg/utils/docker/build.go index 832cd75b..54bd038d 100644 --- a/pkg/utils/docker/build.go +++ b/pkg/utils/docker/build.go @@ -43,16 +43,16 @@ in order to make building easier. if the DOCKER_BUILDKIT is '1', then we will use the docker CLI to build the image */ -func (c *Build) BuildImage(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg) error { +func (c *Build) BuildImage(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, buildTarget string) error { log.Infof("Building image '%s' from directory '%s'", image, path.Base(source)) outputBuffer := bytes.NewBuffer(nil) var err error if usecli, _ := strconv.ParseBool(os.Getenv("DOCKER_BUILDKIT")); usecli { - err = buildDockerCli(source, image, dockerfile, buildargs, outputBuffer) + err = buildDockerCli(source, image, dockerfile, buildargs, outputBuffer, buildTarget) } else { - err = c.buildDockerClient(source, image, dockerfile, buildargs, outputBuffer) + err = c.buildDockerClient(source, image, dockerfile, buildargs, outputBuffer, buildTarget) } log.Debugf("Image %s build output:\n%s", image, outputBuffer) @@ -66,7 +66,7 @@ func (c *Build) BuildImage(source string, image string, dockerfile string, build return nil } -func (c *Build) buildDockerClient(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer) error { +func (c *Build) buildDockerClient(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer, buildTarget string) error { // Create a temporary file for tarball image packaging tmpFile, err := os.CreateTemp(os.TempDir(), "kompose-image-build-") if err != nil { @@ -93,13 +93,14 @@ func (c *Build) buildDockerClient(source string, image string, dockerfile string OutputStream: outputBuffer, Dockerfile: dockerfile, BuildArgs: buildargs, + Target: buildTarget, } // Build it! return c.Client.BuildImage(opts) } -func buildDockerCli(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer) error { +func buildDockerCli(source string, image string, dockerfile string, buildargs []dockerlib.BuildArg, outputBuffer *bytes.Buffer, buildTarget string) error { args := []string{"build", "-t", image} if dockerfile != "" { @@ -111,6 +112,9 @@ func buildDockerCli(source string, image string, dockerfile string, buildargs [] } args = append(args, source) + if buildTarget != "" { + args = append(args, fmt.Sprintf("--target=%s", buildTarget)) + } cmd := exec.Command("docker", args...) cmd.Stdout = outputBuffer diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 61c1f7e1..715ad237 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -339,3 +339,8 @@ os_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/compos os_output="$KOMPOSE_ROOT/script/test/fixtures/resources-lowercase/output-os.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" || exit 1 convert::expect_success "$os_cmd" "$os_output" || exit 1 + +# Test resources to generate initcontainer +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/initcontainer/compose.yaml convert --stdout --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/initcontainer/output-k8s.yaml" +convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" || exit 1 diff --git a/script/test/fixtures/initcontainer/compose.yaml b/script/test/fixtures/initcontainer/compose.yaml new file mode 100644 index 00000000..dcc8f756 --- /dev/null +++ b/script/test/fixtures/initcontainer/compose.yaml @@ -0,0 +1,8 @@ +version: "3" +services: + web: + image: nginx + labels: + kompose.init.containers.name: "init-myservice" + kompose.init.containers.image: "busybox:1.28" + kompose.init.containers.command: '["sh", "-c", "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]' \ No newline at end of file diff --git a/script/test/fixtures/initcontainer/output-k8s.yaml b/script/test/fixtures/initcontainer/output-k8s.yaml new file mode 100644 index 00000000..68dd7d88 --- /dev/null +++ b/script/test/fixtures/initcontainer/output-k8s.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + io.kompose.service: web + name: web +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: web + template: + metadata: + labels: + io.kompose.network/initcontainer-default: "true" + io.kompose.service: web + spec: + containers: + - image: nginx + name: web + initContainers: + - command: + - sh + - -c + - until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done + image: busybox:1.28 + name: init-myservice + restartPolicy: Always \ No newline at end of file