Repository: kubernetes/kompose
Pull request #1401 (2021-07-24 01:52:33)

Add ServiceAccountName
As mentioned in #1088, currently it's not possible to set ```serviceAccountName```. So I fixed it.

## How it works
The label `kompose.service.accountname` defines the service account name
to provide the credential info of the pod.

For example, in your docker-compose.yml, add ```kompose.service.accountname``` in labels to specify the service account name.
```
version: '3.4'
services:
  app:
    image: python
    labels:
      kompose.service.accountname: "myservice"
```
Then, you'll get a yaml for deployment as follows
```
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: ./kompose convert
    kompose.service.accountname: myservice
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: app
  name: app
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: app
  strategy: {}
  template:
    metadata:
      annotations:
        kompose.cmd: ./kompose convert
        kompose.service.accountname: myservice
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: app
    spec:
      containers:
        - image: python
          name: app
          resources: {}
      restartPolicy: Always
      serviceAccountName: myservice
status: {}
```


Patch:
diff --git a/docs/user-guide.md b/docs/user-guide.md
index 62818ea1..b5f18cdf 100755
--- a/docs/user-guide.md
+++ b/docs/user-guide.md
@@ -244,6 +244,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:
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 10912ffd..ff95ed54 100644
--- a/pkg/transformer/kubernetes/kubernetes.go
+++ b/pkg/transformer/kubernetes/kubernetes.go
@@ -1261,6 +1261,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 7f077556..6434ed74 100644
--- a/pkg/transformer/kubernetes/kubernetes_test.go
+++ b/pkg/transformer/kubernetes/kubernetes_test.go
@@ -639,3 +639,60 @@ 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)
+				}
+			}
+		}
+	}
+}