From c9c10803069c3cc72bcef8f32f6c0c498eb51868 Mon Sep 17 00:00:00 2001 From: Jose Luis <2064537+sosan@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:49:09 +0200 Subject: [PATCH] adds a label for initContainers, test and user-guide (#1840) * adds a label for initContainers, test and user-guide Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> * solved suggestion version from 2 to 3, coments Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> * test e2e Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> * add target to kompose build Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> * chore(deps)(deps): bump golang.org/x/tools from 0.16.1 to 0.19.0 (#1836) Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.16.1 to 0.19.0. - [Release notes](https://github.com/golang/tools/releases) - [Commits](https://github.com/golang/tools/compare/v0.16.1...v0.19.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * changed var names contaers to containers changed labels to kompose.init.container.name, kompose.init.container.image, kompose.init.container.command fixed documentation, tests and e2e also merged main with new commits to this branch Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> --------- Signed-off-by: jose luis <2064537+sosan@users.noreply.github.com> Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kubernetes Prow Robot <20407524+k8s-ci-robot@users.noreply.github.com> --- docs/user-guide.md | 45 ++++ pkg/loader/compose/utils.go | 6 + pkg/transformer/kubernetes/k8sutils.go | 47 +++- pkg/transformer/kubernetes/k8sutils_test.go | 207 ++++++++++++++++++ script/test/cmd/tests_new.sh | 5 + .../test/fixtures/initcontainer/compose.yaml | 8 + .../fixtures/initcontainer/output-k8s.yaml | 29 +++ 7 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 script/test/fixtures/initcontainer/compose.yaml create mode 100644 script/test/fixtures/initcontainer/output-k8s.yaml diff --git a/docs/user-guide.md b/docs/user-guide.md index 1b8e9ac20..be213f5b2 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/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index a101458c8..2bdccac12 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -87,6 +87,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" ) // load environment variables from compose file diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 890e34559..064516e1f 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 637d7f2b0..d70d06d98 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/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 61c1f7e1a..715ad2379 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 000000000..dcc8f7564 --- /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 000000000..68dd7d887 --- /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