diff --git a/docs/user-guide.md b/docs/user-guide.md index 980a2635..f943f0d7 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -200,8 +200,9 @@ The currently supported options are: | kompose.service.healthcheck.liveness.http_get_path | kubernetes liveness httpGet path | | kompose.service.healthcheck.liveness.http_get_port | kubernetes liveness httpGet port | | kompose.service.healthcheck.liveness.tcp_port | kubernetes liveness tcpSocket port | -| kompose.service.external-traffic-policy | 'cluster', 'local', '' | -| kompose.security-context.fsgroup | kubernetes pod security group fsgroup | +| kompose.service.external-traffic-policy | 'cluster', 'local', '' | | +| kompose.security-context.fsgroup | kubernetes pod security group fsgroup | | +| kompose.volume.sub-path | kubernetes volume mount subpath | | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. @@ -446,6 +447,20 @@ services: labels: kompose.security-context.fsgroup: 1001 ``` + +- `kompose.volume.sub-path` defines Kubernetes Container [VolumeMounts Subpath](https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath). + +For example: + +```yaml +version: '3.8' + +services: + pgadmin: + image: postgres + labels: + kompose.volume.sub-path: pg-data +``` ## Restart If you want to create normal pods without controller you can use `restart` construct of docker-compose to define that. Follow table below to see what happens on the `restart` value. diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 50db8ee3..7c2065a4 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -142,6 +142,7 @@ type ServiceConfig struct { MemLimit types.UnitBytes `compose:"mem_limit"` MemReservation types.UnitBytes `compose:""` DeployMode string `compose:""` + VolumeMountSubPath string `compose:"kompose.volume.subpath"` // DeployLabels mapping to kubernetes labels DeployLabels map[string]string `compose:""` DeployUpdateConfig types.UpdateConfig `compose:""` diff --git a/pkg/loader/compose/compose.go b/pkg/loader/compose/compose.go index 1cbf7b04..db3bfab8 100644 --- a/pkg/loader/compose/compose.go +++ b/pkg/loader/compose/compose.go @@ -719,6 +719,8 @@ func parseKomposeLabels(labels map[string]string, serviceConfig *kobject.Service serviceConfig.ImagePullSecret = value case LabelImagePullPolicy: serviceConfig.ImagePullPolicy = value + case LabelContainerVolumeSubpath: + serviceConfig.VolumeMountSubPath = value default: serviceConfig.Labels[key] = value } diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index ce31f3f3..67050223 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -81,6 +81,9 @@ const ( ServiceTypeHeadless = "Headless" // LabelSecurityContextFsGroup defines the pod FsGroup LabelSecurityContextFsGroup = "kompose.security-context.fsgroup" + + // LabelContainerVolumeSubpath defines the volume mount subpath inside container + LabelContainerVolumeSubpath = "kompose.volume.subpath" ) // load environment variables from compose file diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index 11201184..d3b5cf9d 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -887,6 +887,7 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( var PVCs []*api.PersistentVolumeClaim var cms []*api.ConfigMap var volumeName string + var subpathName string // Set a var based on if the user wants to use empty volumes // as opposed to persistent volumes and volume claims @@ -897,6 +898,10 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( useEmptyVolumes = true } + if subpath, ok := service.Labels["kompose.volume.subpath"]; ok { + subpathName = subpath + } + // Override volume type if specified in service labels. if vt, ok := service.Labels["kompose.volume.type"]; ok { if _, okk := ValidVolumeSet[vt]; !okk { @@ -993,6 +998,9 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) ( PVCs = append(PVCs, createdPVC) } } + if subpathName != "" { + volMount.SubPath = subpathName + } volumeMounts = append(volumeMounts, volMount) // create a new volume object using the volsource and add to list diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go index f9ccd45c..c4deb3fb 100644 --- a/pkg/transformer/kubernetes/kubernetes_test.go +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -104,6 +104,13 @@ func newServiceConfigWithExternalTrafficPolicy() kobject.ServiceConfig { } } +func newServiceConfigWithServiceVolumeMount(volumeMountSubPathValue string) kobject.ServiceConfig { + return kobject.ServiceConfig{ + Name: "app", + VolumeMountSubPath: volumeMountSubPathValue, + } +} + func equalStringSlice(s1, s2 []string) bool { if len(s1) != len(s2) { return false @@ -1023,3 +1030,24 @@ func TestServiceExternalTrafficPolicy(t *testing.T) { } } } + +func TestVolumeMountSubPath(t *testing.T) { + groupName := "pod_group" + expectedSubPathValue := "test-subpath" + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": newServiceConfigWithServiceVolumeMount(expectedSubPathValue)}, + } + k := Kubernetes{} + objs, err := k.Transform(komposeObject, kobject.ConvertOptions{ServiceGroupMode: groupName}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + for _, obj := range objs { + if deployment, ok := obj.(*appsv1.Deployment); ok { + volMountSubPath := deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath + if volMountSubPath != expectedSubPathValue { + t.Errorf("Expected VolumeMount Subpath %v, got %v", expectedSubPathValue, volMountSubPath) + } + } + } +} diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 037126aa..876392c6 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -247,6 +247,7 @@ k8s_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-k8s.yaml" os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/fsgroup/docker-compose.yaml convert --stdout --with-kompose-annotation=false" os_output="$KOMPOSE_ROOT/script/test/fixtures/fsgroup/output-os.yaml" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" +convert::expect_success_and_warning "$os_cmd" "$os_output" # Test support for compose.yaml file k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-file-support/docker-compose.yaml convert --stdout --with-kompose-annotation=false" @@ -257,3 +258,12 @@ convert::expect_success "$k8s_cmd" "$k8s_output" k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/compose-env-interpolation/docker-compose.yaml convert --stdout --with-kompose-annotation=false" k8s_output="$KOMPOSE_ROOT/script/test/fixtures/compose-env-interpolation/output-k8s.yaml" convert::expect_success "$k8s_cmd" "$k8s_output" +convert::expect_success "$os_cmd" "$os_output" + +# Test support for subpath volume +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/vols-subpath/docker-compose.yaml convert --stdout --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/vols-subpath/output-k8s.yaml" +os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/vols-subpath/docker-compose.yaml convert --stdout --with-kompose-annotation=false" +os_output="$KOMPOSE_ROOT/script/test/fixtures/vols-subpath/output-os.yaml" +convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" +convert::expect_success "$os_cmd" "$os_output" diff --git a/script/test/fixtures/vols-subpath/docker-compose.yaml b/script/test/fixtures/vols-subpath/docker-compose.yaml new file mode 100644 index 00000000..1184e367 --- /dev/null +++ b/script/test/fixtures/vols-subpath/docker-compose.yaml @@ -0,0 +1,15 @@ +version: '3.8' +volumes: + postgres-data: + +services: + postgres: + labels: + kompose.volume.subpath: test + image: postgres + environment: + POSTGRES_USER: dumb_postgres_user + POSTGRES_PASSWORD: postgres_password + POSTGRES_DB: dumb_db + volumes: + - postgres-data:/var/lib/postgresql/data diff --git a/script/test/fixtures/vols-subpath/output-k8s.yaml b/script/test/fixtures/vols-subpath/output-k8s.yaml new file mode 100644 index 00000000..13d7daf4 --- /dev/null +++ b/script/test/fixtures/vols-subpath/output-k8s.yaml @@ -0,0 +1,80 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + kompose.volume.subpath: test + creationTimestamp: null + labels: + io.kompose.service: postgres + name: postgres +spec: + replicas: 1 + selector: + matchLabels: + io.kompose.service: postgres + strategy: + type: Recreate + template: + metadata: + annotations: + kompose.volume.subpath: test + creationTimestamp: null + labels: + io.kompose.network/vols-subpath-default: "true" + io.kompose.service: postgres + spec: + containers: + - env: + - name: POSTGRES_DB + value: dumb_db + - name: POSTGRES_PASSWORD + value: postgres_password + - name: POSTGRES_USER + value: dumb_postgres_user + image: postgres + name: postgres + resources: {} + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + subPath: test + restartPolicy: Always + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-data +status: {} + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: postgres-data + name: postgres-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} + +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + creationTimestamp: null + name: vols-subpath-default +spec: + ingress: + - from: + - podSelector: + matchLabels: + io.kompose.network/vols-subpath-default: "true" + podSelector: + matchLabels: + io.kompose.network/vols-subpath-default: "true" + diff --git a/script/test/fixtures/vols-subpath/output-os.yaml b/script/test/fixtures/vols-subpath/output-os.yaml new file mode 100644 index 00000000..bd2295e3 --- /dev/null +++ b/script/test/fixtures/vols-subpath/output-os.yaml @@ -0,0 +1,103 @@ +--- +apiVersion: apps.openshift.io/v1 +kind: DeploymentConfig +metadata: + annotations: + kompose.volume.subpath: test + creationTimestamp: null + labels: + io.kompose.service: postgres + name: postgres +spec: + replicas: 1 + selector: + io.kompose.service: postgres + strategy: + resources: {} + type: Recreate + template: + metadata: + creationTimestamp: null + labels: + io.kompose.network/vols-subpath-default: "true" + io.kompose.service: postgres + spec: + containers: + - env: + - name: POSTGRES_DB + value: dumb_db + - name: POSTGRES_PASSWORD + value: postgres_password + - name: POSTGRES_USER + value: dumb_postgres_user + image: ' ' + name: postgres + resources: {} + volumeMounts: + - mountPath: /var/lib/postgresql/data + name: postgres-data + subPath: test + restartPolicy: Always + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-data + test: false + triggers: + - type: ConfigChange + - imageChangeParams: + automatic: true + containerNames: + - postgres + from: + kind: ImageStreamTag + name: postgres:latest + type: ImageChange +status: + availableReplicas: 0 + latestVersion: 0 + observedGeneration: 0 + replicas: 0 + unavailableReplicas: 0 + updatedReplicas: 0 + +--- +apiVersion: image.openshift.io/v1 +kind: ImageStream +metadata: + creationTimestamp: null + labels: + io.kompose.service: postgres + name: postgres +spec: + lookupPolicy: + local: false + tags: + - annotations: null + from: + kind: DockerImage + name: postgres + generation: null + importPolicy: {} + name: latest + referencePolicy: + type: "" +status: + dockerImageRepository: "" + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + creationTimestamp: null + labels: + io.kompose.service: postgres-data + name: postgres-data +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 100Mi +status: {} +