Repository: kubernetes/kompose
Pull request #1414 (2021-08-12 08:19:01)

fix kompose.image-pull-secret invalid when use compose configs
kompose version: 1.23.0 (bc7d9f4f)
compose version: 3.8

I found kompose.image-pull-secret will be invaild 
when compose service has configs



Patch:
diff --git a/docs/user-guide.md b/docs/user-guide.md
index e5b06ce3..3ff992b2 100755
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -163,6 +163,7 @@ The currently supported options are:
 | kompose.service.nodeport.port | port value (string) | 
 | kompose.service.expose.tls-secret | secret name |
 | kompose.volume.size | kubernetes supported volume size |
+| kompose.volume.storage-class-name | kubernetes supported volume storageClassName |
 | kompose.controller.type | deployment / daemonset / replicationcontroller |
 | kompose.image-pull-policy | kubernetes pods imagePullPolicy |
 | kompose.image-pull-secret | kubernetes secret name for imagePullSecrets |
@@ -244,6 +245,19 @@ services:
      - "6379"
 ```
 
+- `kompose.serviceaccount-name` defines the service account name to provide the credential info of the pod.
+
+For example:
+
+```yaml
+version: '3.4'
+services:
+  app:
+    image: python
+    labels:
+      kompose.serviceaccount-name: "my-service"
+```
+
 - `kompose.image-pull-secret` defines a kubernetes secret name for imagePullSecrets podspec field.
 This secret will be used for pulling private images.
 For example:
@@ -273,6 +287,21 @@ services:
       - db-data:/var/lib/postgresql/data
 ```
 
+- `kompose.volume.storage-class-name` defines the requests storage's class name in the PersistentVolumeClaim.
+
+For example:
+
+```yaml
+version: '3'
+services:
+  db:
+    image: postgres:10.1
+    labels:
+      kompose.volume.storage-class-name: custom-storage-class-name
+    volumes:
+      - db-data:/var/lib/postgresql/data
+```
+
 - `kompose.controller.type` defines which controller type should convert for this service
 
 For example:
diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go
index 516b5be7..7a800ab0 100644
--- a/pkg/loader/compose/utils.go
+++ b/pkg/loader/compose/utils.go
@@ -38,8 +38,10 @@ const (
 	LabelNodePortPort = "kompose.service.nodeport.port"
 	// LabelServiceExpose defines if the service needs to be made accessible from outside the cluster or not
 	LabelServiceExpose = "kompose.service.expose"
-	// LabelServiceExposeTLSSecret  provides the name of the TLS secret to use with the Kubernetes ingress controller
+	// LabelServiceExposeTLSSecret provides the name of the TLS secret to use with the Kubernetes ingress controller
 	LabelServiceExposeTLSSecret = "kompose.service.expose.tls-secret"
+	// LabelServiceAccountName defines the service account name to provide the credential info of the pod.
+	LabelServiceAccountName = "kompose.serviceaccount-name"
 	// LabelControllerType defines the type of controller to be created
 	LabelControllerType = "kompose.controller.type"
 	// LabelImagePullSecret defines a secret name for kubernetes ImagePullSecrets
diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go
index 3798b028..9c08c64f 100644
--- a/pkg/transformer/kubernetes/k8sutils.go
+++ b/pkg/transformer/kubernetes/k8sutils.go
@@ -670,6 +670,10 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
 			template.Spec.Subdomain = service.DomainName
 		}
 
+		if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
+			template.Spec.ServiceAccountName = serviceAccountName
+		}
+
 		return nil
 	}
 
diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go
index bb386fdf..917e0115 100644
--- a/pkg/transformer/kubernetes/kubernetes.go
+++ b/pkg/transformer/kubernetes/kubernetes.go
@@ -185,6 +185,14 @@ func (k *Kubernetes) InitPodSpecWithConfigMap(name string, image string, service
 		},
 		Volumes: volumes,
 	}
+
+	if service.ImagePullSecret != "" {
+		pod.ImagePullSecrets = []api.LocalObjectReference{
+			{
+				Name: service.ImagePullSecret,
+			},
+		}
+	}
 	return pod
 }
 
@@ -498,7 +506,7 @@ func (k *Kubernetes) CreateSecrets(komposeObject kobject.KomposeObject) ([]*api.
 }
 
 // CreatePVC initializes PersistentVolumeClaim
-func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string) (*api.PersistentVolumeClaim, error) {
+func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorValue string, storageClassName string) (*api.PersistentVolumeClaim, error) {
 	volSize, err := resource.ParseQuantity(size)
 	if err != nil {
 		return nil, errors.Wrap(err, "resource.ParseQuantity failed, Error parsing size")
@@ -533,6 +541,11 @@ func (k *Kubernetes) CreatePVC(name string, mode string, size string, selectorVa
 	} else {
 		pvc.Spec.AccessModes = []api.PersistentVolumeAccessMode{api.ReadWriteOnce}
 	}
+
+	if len(storageClassName) > 0 {
+		pvc.Spec.StorageClassName = &storageClassName
+	}
+
 	return pvc, nil
 }
 
@@ -844,6 +857,7 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
 		} else {
 			volsource = k.ConfigPVCVolumeSource(volumeName, readonly)
 			if volume.VFrom == "" {
+				var storageClassName string
 				defaultSize := PVCRequestSize
 				if k.Opt.PVCRequestSize != "" {
 					defaultSize = k.Opt.PVCRequestSize
@@ -854,11 +868,13 @@ func (k *Kubernetes) ConfigVolumes(name string, service kobject.ServiceConfig) (
 					for key, value := range service.Labels {
 						if key == "kompose.volume.size" {
 							defaultSize = value
+						} else if key == "kompose.volume.storage-class-name" {
+							storageClassName = value
 						}
 					}
 				}
 
-				createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue)
+				createdPVC, err := k.CreatePVC(volumeName, volume.Mode, defaultSize, volume.SelectorValue, storageClassName)
 
 				if err != nil {
 					return nil, nil, nil, nil, errors.Wrap(err, "k.CreatePVC failed")
@@ -1261,6 +1277,10 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject.
 					TerminationGracePeriodSeconds(name, service),
 				)
 
+				if serviceAccountName, ok := service.Labels[compose.LabelServiceAccountName]; ok {
+					podSpec.Append(ServiceAccountName(serviceAccountName))
+				}
+
 				err = k.UpdateKubernetesObjectsMultipleContainers(name, service, opt, &objects, podSpec)
 				if err != nil {
 					return nil, errors.Wrap(err, "Error transforming Kubernetes objects")
diff --git a/pkg/transformer/kubernetes/podspec.go b/pkg/transformer/kubernetes/podspec.go
index 51238ed9..74def137 100644
--- a/pkg/transformer/kubernetes/podspec.go
+++ b/pkg/transformer/kubernetes/podspec.go
@@ -340,6 +340,12 @@ func ReadinessProbe(service kobject.ServiceConfig) PodSpecOption {
 	}
 }
 
+func ServiceAccountName(serviceAccountName string) PodSpecOption {
+	return func(podSpec *PodSpec) {
+		podSpec.ServiceAccountName = serviceAccountName
+	}
+}
+
 func (podSpec *PodSpec) Append(ops ...PodSpecOption) *PodSpec {
 	for _, option := range ops {
 		option(podSpec)


Test patch:
diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go
index 205dcfe2..134251ab 100644
--- a/pkg/transformer/kubernetes/k8sutils_test.go
+++ b/pkg/transformer/kubernetes/k8sutils_test.go
@@ -24,6 +24,7 @@ import (
 	"testing"
 
 	"github.com/kubernetes/kompose/pkg/kobject"
+	"github.com/kubernetes/kompose/pkg/loader/compose"
 	"github.com/kubernetes/kompose/pkg/testutils"
 	"github.com/pkg/errors"
 	appsv1 "k8s.io/api/apps/v1"
@@ -487,3 +488,31 @@ func TestDurationStrToSecondsInt(t *testing.T) {
 		}
 	}
 }
+
+func TestServiceWithServiceAccount(t *testing.T) {
+	assertServiceAccountName := "my-service"
+
+	service := kobject.ServiceConfig{
+		ContainerName: "name",
+		Image:         "image",
+		Port:          []kobject.Ports{{HostPort: 55555}},
+		Labels:        map[string]string{compose.LabelServiceAccountName: assertServiceAccountName},
+	}
+
+	komposeObject := kobject.KomposeObject{
+		ServiceConfigs: map[string]kobject.ServiceConfig{"app": service},
+	}
+	k := Kubernetes{}
+
+	objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true})
+	if err != nil {
+		t.Error(errors.Wrap(err, "k.Transform failed"))
+	}
+	for _, obj := range objects {
+		if deployment, ok := obj.(*appsv1.Deployment); ok {
+			if deployment.Spec.Template.Spec.ServiceAccountName != assertServiceAccountName {
+				t.Errorf("Expected %v returned, got %v", assertServiceAccountName, deployment.Spec.Template.Spec.ServiceAccountName)
+			}
+		}
+	}
+}
diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go
index a2c97561..8209192b 100644
--- a/pkg/transformer/kubernetes/kubernetes_test.go
+++ b/pkg/transformer/kubernetes/kubernetes_test.go
@@ -23,6 +23,8 @@ import (
 	"strings"
 	"testing"
 
+	dockerCliTypes "github.com/docker/cli/cli/compose/types"
+
 	"github.com/kubernetes/kompose/pkg/kobject"
 	"github.com/kubernetes/kompose/pkg/loader/compose"
 	"github.com/kubernetes/kompose/pkg/transformer"
@@ -38,30 +40,33 @@ import (
 
 func newServiceConfig() kobject.ServiceConfig {
 	return kobject.ServiceConfig{
-		Name:          "app",
-		ContainerName: "name",
-		Image:         "image",
-		Environment:   []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}},
-		Port:          []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456}, kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolUDP}},
-		Command:       []string{"cmd"},
-		WorkingDir:    "dir",
-		Args:          []string{"arg1", "arg2"},
-		VolList:       []string{"/tmp/volume"},
-		Network:       []string{"network1", "network2"}, // supported
-		Labels:        nil,
-		Annotations:   map[string]string{"abc": "def"},
-		CPUQuota:      1, // not supported
-		CapAdd:        []string{"cap_add"},
-		CapDrop:       []string{"cap_drop"},
-		Expose:        []string{"expose"}, // not supported
-		Privileged:    true,
-		Restart:       "always",
-		Stdin:         true,
-		Tty:           true,
-		TmpFs:         []string{"/tmp"},
-		Replicas:      2,
-		Volumes:       []kobject.Volumes{{SvcName: "app", MountPath: "/tmp/volume", PVCName: "app-claim0"}},
-		GroupAdd:      []int64{1003, 1005},
+		Name:            "app",
+		ContainerName:   "name",
+		Image:           "image",
+		Environment:     []kobject.EnvVar{kobject.EnvVar{Name: "env", Value: "value"}},
+		Port:            []kobject.Ports{kobject.Ports{HostPort: 123, ContainerPort: 456}, kobject.Ports{HostPort: 123, ContainerPort: 456, Protocol: api.ProtocolUDP}},
+		Command:         []string{"cmd"},
+		WorkingDir:      "dir",
+		Args:            []string{"arg1", "arg2"},
+		VolList:         []string{"/tmp/volume"},
+		Network:         []string{"network1", "network2"}, // supported
+		Labels:          nil,
+		Annotations:     map[string]string{"abc": "def"},
+		CPUQuota:        1, // not supported
+		CapAdd:          []string{"cap_add"},
+		CapDrop:         []string{"cap_drop"},
+		Expose:          []string{"expose"}, // not supported
+		Privileged:      true,
+		Restart:         "always",
+		ImagePullSecret: "regcred",
+		Stdin:           true,
+		Tty:             true,
+		TmpFs:           []string{"/tmp"},
+		Replicas:        2,
+		Volumes:         []kobject.Volumes{{SvcName: "app", MountPath: "/tmp/volume", PVCName: "app-claim0"}},
+		GroupAdd:        []int64{1003, 1005},
+		Configs:         []dockerCliTypes.ServiceConfigObjConfig{{Source: "config", Target: "/etc/world"}},
+		ConfigsMetaData: map[string]dockerCliTypes.ConfigObjConfig{"config": dockerCliTypes.ConfigObjConfig{Name: "myconfig", File: "kubernetes_test.go"}},
 	}
 }
 
@@ -163,7 +168,7 @@ func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec
 	if !equalStringSlice(config.Args, container.Args) {
 		return fmt.Errorf("Found different container args: %#v vs. %#v", config.Args, container.Args)
 	}
-	if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || template.Spec.Volumes[0].VolumeSource.PersistentVolumeClaim == nil {
+	if len(template.Spec.Volumes) == 0 || len(template.Spec.Volumes[0].Name) == 0 || template.Spec.Volumes[0].VolumeSource.PersistentVolumeClaim == nil && template.Spec.Volumes[0].ConfigMap == nil {
 		return fmt.Errorf("Found incorrect volumes: %v vs. %#v", config.Volumes, template.Spec.Volumes)
 	}
 	// We only set controller labels here and k8s server will take care of other defaults, such as selectors
@@ -183,6 +188,9 @@ func checkPodTemplate(config kobject.ServiceConfig, template api.PodTemplateSpec
 	if config.Tty != template.Spec.Containers[0].TTY {
 		return fmt.Errorf("Found different values for TTY: %#v vs. %#v", config.Tty, template.Spec.Containers[0].TTY)
 	}
+	if config.ImagePullSecret != template.Spec.ImagePullSecrets[0].Name {
+		return fmt.Errorf("Found different values for ImagePullSecrets: %#v vs. %#v", config.ImagePullSecret, template.Spec.ImagePullSecrets[0].Name)
+	}
 	return nil
 }
 
@@ -281,12 +289,12 @@ func TestKomposeConvert(t *testing.T) {
 		expectedNumObjs int
 	}{
 		// objects generated are deployment, service nework policies (2) and pvc
-		"Convert to Deployments (D)":                  {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas, IsReplicaSetFlag: true}, 5},
-		"Convert to Deployments (D) with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true}, 5},
-		"Convert to DaemonSets (DS)":                  {newKomposeObject(), kobject.ConvertOptions{CreateDS: true}, 5},
+		"Convert to Deployments (D)":                  {newKomposeObject(), kobject.ConvertOptions{CreateD: true, Replicas: replicas, IsReplicaSetFlag: true}, 6},
+		"Convert to Deployments (D) with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true}, 6},
+		"Convert to DaemonSets (DS)":                  {newKomposeObject(), kobject.ConvertOptions{CreateDS: true}, 6},
 		// objects generated are deployment, daemonset, ReplicationController, service and pvc
-		"Convert to D, DS, and RC":                  {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true, Replicas: replicas, IsReplicaSetFlag: true}, 6},
-		"Convert to D, DS, and RC with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true}, 6},
+		"Convert to D, DS, and RC":                  {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true, Replicas: replicas, IsReplicaSetFlag: true}, 7},
+		"Convert to D, DS, and RC with v3 replicas": {newKomposeObject(), kobject.ConvertOptions{CreateD: true, CreateDS: true, CreateRC: true}, 7},
 		// TODO: add more tests
 	}
 
@@ -578,21 +586,21 @@ func TestMultipleContainersInPod(t *testing.T) {
 					"app1": createConfig("app1", &containerName),
 					"app2": createConfig("app2", &containerName),
 				},
-			}, kobject.ConvertOptions{MultipleContainerMode: true}, 2, []string{"app1", "app2"}},
+			}, kobject.ConvertOptions{MultipleContainerMode: true}, 3, []string{"app1", "app2"}},
 		"Converted multiple containers to Deployments (D)": {
 			kobject.KomposeObject{
 				ServiceConfigs: map[string]kobject.ServiceConfig{
 					"app1": createConfig("app1", &containerName),
 					"app2": createConfig("app2", &containerName),
 				},
-			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"app1", "app2"}},
+			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 4, []string{"app1", "app2"}},
 		"Converted multiple containers (ContainerName are nil) to Deployments (D)": {
 			kobject.KomposeObject{
 				ServiceConfigs: map[string]kobject.ServiceConfig{
 					"app1": createConfig("app1", nil),
 					"app2": createConfig("app2", nil),
 				},
-			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 3, []string{"name", "name"}},
+			}, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true}, 4, []string{"name", "name"}},
 		// TODO: add more tests
 	}
 
@@ -640,3 +648,72 @@ func TestMultipleContainersInPod(t *testing.T) {
 		}
 	}
 }
+
+func TestServiceAccountNameOnMultipleContainers(t *testing.T) {
+	groupName := "pod_group"
+	serviceAccountName := "my-service"
+
+	createConfigs := func(labels map[string]string) map[string]kobject.ServiceConfig {
+		createConfig := func(name string) kobject.ServiceConfig {
+			config := newServiceConfig()
+			config.Labels = map[string]string{compose.LabelServiceGroup: groupName}
+			for k, v := range labels {
+				config.Labels[k] = v
+			}
+			config.Name = name
+			config.ContainerName = ""
+			config.Volumes = []kobject.Volumes{
+				{
+					VolumeName: "mountVolume",
+					MountPath:  "/data",
+				},
+			}
+			return config
+		}
+		return map[string]kobject.ServiceConfig{"app1": createConfig("app1"), "app2": createConfig("app2")}
+	}
+
+	testCases := map[string]struct {
+		komposeObject      kobject.KomposeObject
+		expectedLabelNames []string
+	}{
+		"Converted multiple containers with ServiceAccountName": {
+			kobject.KomposeObject{
+				ServiceConfigs: createConfigs(map[string]string{compose.LabelServiceAccountName: serviceAccountName}),
+			}, []string{serviceAccountName}},
+	}
+
+	for name, test := range testCases {
+		t.Log("Test case:", name)
+		k := Kubernetes{}
+		// Run Transform
+		objs, err := k.Transform(test.komposeObject, kobject.ConvertOptions{MultipleContainerMode: true, CreateD: true})
+		if err != nil {
+			t.Error(errors.Wrap(err, "k.Transform failed"))
+		}
+
+		// Check results
+		for _, obj := range objs {
+			if deployment, ok := obj.(*appsv1.Deployment); ok {
+				if deployment.Name != groupName {
+					t.Errorf("Expected %v returned, got %v", groupName, deployment.Name)
+				}
+				if deployment.Spec.Template.Spec.ServiceAccountName != serviceAccountName {
+					t.Errorf("Expected %v returned, got %v", serviceAccountName, deployment.Spec.Template.Spec.ServiceAccountName)
+				}
+			}
+		}
+	}
+}
+
+func TestCreatePVC(t *testing.T) {
+	storageClassName := "custom-storage-class-name"
+	k := Kubernetes{}
+	result, err := k.CreatePVC("", "", PVCRequestSize, "", storageClassName)
+	if err != nil {
+		t.Error(errors.Wrap(err, "k.CreatePVC failed"))
+	}
+	if *result.Spec.StorageClassName != storageClassName {
+		t.Errorf("Expected %s returned, got %s", storageClassName, *result.Spec.StorageClassName)
+	}
+}
diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh
index 912a9814..0943a1bf 100755
--- a/script/test/cmd/tests_new.sh
+++ b/script/test/cmd/tests_new.sh
@@ -102,3 +102,7 @@ convert::check_artifacts_generated "kompose --build local -f $relative_path conv
 # Should warn when push image disabled
 cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/buildconfig/docker-compose-build-no-image.yml -o $TEMP_DIR/output_file convert --build=local --push-image-registry=whatever"
 convert::expect_warning "$cmd" "Push image registry 'whatever' is specified but push image is disabled, skipping pushing to repository"
+
+#TEST the kompose.volume.storage-class-name label
+convert::check_artifacts_generated "kompose -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/docker-compose.yml convert -o $TEMP_DIR/output-k8s.json -j" "$TEMP_DIR/output-k8s.json"
+convert::check_artifacts_generated "kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/storage-class-name/docker-compose.yml convert -o $TEMP_DIR/output-os.json -j" "$TEMP_DIR/output-os.json"
diff --git a/script/test/fixtures/storage-class-name/docker-compose.yml b/script/test/fixtures/storage-class-name/docker-compose.yml
new file mode 100644
index 00000000..acb3afdc
--- /dev/null
+++ b/script/test/fixtures/storage-class-name/docker-compose.yml
@@ -0,0 +1,10 @@
+version: "3"
+services:
+  nginx:
+    image: nginx
+    ports:
+      - "80:80"
+    labels:
+      kompose.volume.storage-class-name: custom-storage-class-name
+    volumes:
+      - /etc/nginx/nginx.conf
diff --git a/script/test/fixtures/storage-class-name/output-k8s.json b/script/test/fixtures/storage-class-name/output-k8s.json
new file mode 100644
index 00000000..1a5c01a3
--- /dev/null
+++ b/script/test/fixtures/storage-class-name/output-k8s.json
@@ -0,0 +1,131 @@
+{
+  "kind": "List",
+  "apiVersion": "v1",
+  "metadata": {},
+  "items": [
+    {
+      "kind": "Service",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx"
+        },
+        "annotations": {
+          "kompose.cmd": "%CMD%",
+          "kompose.version": "%VERSION%",
+          "kompose.volume.storage-class-name": "custom-storage-class-name"
+        }
+      },
+      "spec": {
+        "ports": [
+          {
+            "name": "80",
+            "port": 80,
+            "targetPort": 80
+          }
+        ],
+        "selector": {
+          "io.kompose.service": "nginx"
+        }
+      },
+      "status": {
+        "loadBalancer": {}
+      }
+    },
+    {
+      "kind": "Deployment",
+      "apiVersion": "apps/v1",
+      "metadata": {
+        "name": "nginx",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx"
+        },
+        "annotations": {
+          "kompose.cmd": "%CMD%",
+          "kompose.version": "%VERSION%",
+          "kompose.volume.storage-class-name": "custom-storage-class-name"
+        }
+      },
+      "spec": {
+        "replicas": 1,
+        "selector": {
+          "matchLabels": {
+            "io.kompose.service": "nginx"
+          }
+        },
+        "template": {
+          "metadata": {
+            "creationTimestamp": null,
+            "labels": {
+              "io.kompose.service": "nginx"
+            },
+            "annotations": {
+              "kompose.cmd": "%CMD%",
+              "kompose.version": "%VERSION%",
+              "kompose.volume.storage-class-name": "custom-storage-class-name"
+            }
+          },
+          "spec": {
+            "volumes": [
+              {
+                "name": "nginx-claim0",
+                "persistentVolumeClaim": {
+                  "claimName": "nginx-claim0"
+                }
+              }
+            ],
+            "containers": [
+              {
+                "name": "nginx",
+                "image": "nginx",
+                "ports": [
+                  {
+                    "containerPort": 80
+                  }
+                ],
+                "resources": {},
+                "volumeMounts": [
+                  {
+                    "name": "nginx-claim0",
+                    "mountPath": "/etc/nginx/nginx.conf"
+                  }
+                ]
+              }
+            ],
+            "restartPolicy": "Always"
+          }
+        },
+        "strategy": {
+          "type": "Recreate"
+        }
+      },
+      "status": {}
+    },
+    {
+      "kind": "PersistentVolumeClaim",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx-claim0",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx-claim0"
+        }
+      },
+      "spec": {
+        "accessModes": [
+          "ReadWriteOnce"
+        ],
+        "resources": {
+          "requests": {
+            "storage": "100Mi"
+          }
+        },
+        "storageClassName": "custom-storage-class-name"
+      },
+      "status": {}
+    }
+  ]
+}
diff --git a/script/test/fixtures/storage-class-name/output-os.json b/script/test/fixtures/storage-class-name/output-os.json
new file mode 100644
index 00000000..74c61c1a
--- /dev/null
+++ b/script/test/fixtures/storage-class-name/output-os.json
@@ -0,0 +1,181 @@
+{
+  "kind": "List",
+  "apiVersion": "v1",
+  "metadata": {},
+  "items": [
+    {
+      "kind": "Service",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx"
+        },
+        "annotations": {
+          "kompose.volume.storage-class-name": "custom-storage-class-name"
+        }
+      },
+      "spec": {
+        "ports": [
+          {
+            "name": "80",
+            "port": 80,
+            "targetPort": 80
+          }
+        ],
+        "selector": {
+          "io.kompose.service": "nginx"
+        }
+      },
+      "status": {
+        "loadBalancer": {}
+      }
+    },
+    {
+      "kind": "DeploymentConfig",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx"
+        },
+        "annotations": {
+          "kompose.volume.storage-class-name": "custom-storage-class-name"
+        }
+      },
+      "spec": {
+        "strategy": {
+          "type": "Recreate",
+          "resources": {}
+        },
+        "triggers": [
+          {
+            "type": "ConfigChange"
+          },
+          {
+            "type": "ImageChange",
+            "imageChangeParams": {
+              "automatic": true,
+              "containerNames": [
+                "nginx"
+              ],
+              "from": {
+                "kind": "ImageStreamTag",
+                "name": "nginx:latest"
+              }
+            }
+          }
+        ],
+        "replicas": 1,
+        "test": false,
+        "selector": {
+          "io.kompose.service": "nginx"
+        },
+        "template": {
+          "metadata": {
+            "creationTimestamp": null,
+            "labels": {
+              "io.kompose.service": "nginx"
+            }
+          },
+          "spec": {
+            "volumes": [
+              {
+                "name": "nginx-claim0",
+                "persistentVolumeClaim": {
+                  "claimName": "nginx-claim0"
+                }
+              }
+            ],
+            "containers": [
+              {
+                "name": "nginx",
+                "image": " ",
+                "ports": [
+                  {
+                    "containerPort": 80
+                  }
+                ],
+                "resources": {},
+                "volumeMounts": [
+                  {
+                    "name": "nginx-claim0",
+                    "mountPath": "/etc/nginx/nginx.conf"
+                  }
+                ]
+              }
+            ],
+            "restartPolicy": "Always"
+          }
+        }
+      },
+      "status": {
+        "latestVersion": 0,
+        "observedGeneration": 0,
+        "replicas": 0,
+        "updatedReplicas": 0,
+        "availableReplicas": 0,
+        "unavailableReplicas": 0
+      }
+    },
+    {
+      "kind": "ImageStream",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx"
+        }
+      },
+      "spec": {
+        "lookupPolicy": {
+          "local": false
+        },
+        "tags": [
+          {
+            "name": "",
+            "annotations": null,
+            "from": {
+              "kind": "DockerImage",
+              "name": "nginx"
+            },
+            "generation": null,
+            "importPolicy": {},
+            "referencePolicy": {
+              "type": ""
+            }
+          }
+        ]
+      },
+      "status": {
+        "dockerImageRepository": ""
+      }
+    },
+    {
+      "kind": "PersistentVolumeClaim",
+      "apiVersion": "v1",
+      "metadata": {
+        "name": "nginx-claim0",
+        "creationTimestamp": null,
+        "labels": {
+          "io.kompose.service": "nginx-claim0"
+        }
+      },
+      "spec": {
+        "accessModes": [
+          "ReadWriteOnce"
+        ],
+        "resources": {
+          "requests": {
+            "storage": "100Mi"
+          }
+        },
+        "storageClassName": "custom-storage-class-name"
+      },
+      "status": {}
+    }
+  ]
+}