Repository: appvia/kev
Pull request #586 (2021-06-22 11:24:51)

Generate ServiceAccount manifest for eligible objects if service account name specified
Resolves: #583 

When workload's k8s configuration specifies a service account name other than `default` it'll result in ServiceAccount manifest being generated when `render` command is executed. 

It's a NOOP for `default` service account name.


Patch:
diff --git a/pkg/kev/converter/kubernetes/transform.go b/pkg/kev/converter/kubernetes/transform.go
index 250d43d..37b5694 100644
--- a/pkg/kev/converter/kubernetes/transform.go
+++ b/pkg/kev/converter/kubernetes/transform.go
@@ -696,7 +696,7 @@ func (k *Kubernetes) initIngress(projectService ProjectService, port int32) *net
 	return ingress
 }
 
-// initHpa initialised horizontal pod autoscaler for a project service
+// initHpa initialises horizontal pod autoscaler for a project service
 func (k *Kubernetes) initHpa(projectService ProjectService, target runtime.Object) *autoscalingv2beta2.HorizontalPodAutoscaler {
 	t := reflect.ValueOf(target).Elem()
 	typeMeta := t.FieldByName("TypeMeta").Interface().(meta.TypeMeta)
@@ -789,6 +789,30 @@ func (k *Kubernetes) initHpa(projectService ProjectService, target runtime.Objec
 	}
 }
 
+// initServiceAccount initialises Service Account for a project service
+// It only creates the ServiceAccount spec for accounts with name other than `default`
+func (k *Kubernetes) initServiceAccount(projectService ProjectService) *v1.ServiceAccount {
+	automountSAToken := false
+	saname := projectService.serviceAccountName()
+
+	if saname != "default" && len(strings.TrimSpace(saname)) > 0 {
+		return &v1.ServiceAccount{
+			TypeMeta: meta.TypeMeta{
+				Kind:       "ServiceAccount",
+				APIVersion: "v1",
+			},
+			ObjectMeta: meta.ObjectMeta{
+				Name:        saname,
+				Labels:      configLabels(projectService.Name),
+				Annotations: configAnnotations(projectService.Labels),
+			},
+			AutomountServiceAccountToken: &automountSAToken,
+		}
+	}
+
+	return nil
+}
+
 // createSecrets create secrets
 // @orig: https://github.com/kubernetes/kompose/blob/master/pkg/transformer/kubernetes/kubernetes.go#L502
 func (k *Kubernetes) createSecrets() ([]*v1.Secret, error) {
@@ -1449,23 +1473,30 @@ func (k *Kubernetes) createKubernetesObjects(projectService ProjectService) []ru
 	}
 
 	// @step create object based on inferred / manually configured workload controller type
+	var o runtime.Object
+
 	switch {
 	case config.WorkloadTypesEqual(workloadType, config.DeploymentWorkload):
-		o := k.initDeployment(projectService)
+		o = k.initDeployment(projectService)
 		objects = append(objects, o)
-		hpa := k.initHpa(projectService, o)
-		if hpa != nil {
-			objects = append(objects, hpa)
-		}
 	case config.WorkloadTypesEqual(workloadType, config.StatefulSetWorkload):
-		o := k.initStatefulSet(projectService)
+		o = k.initStatefulSet(projectService)
 		objects = append(objects, o)
+	case config.WorkloadTypesEqual(workloadType, config.DaemonSetWorkload):
+		objects = append(objects, k.initDaemonSet(projectService))
+	}
+
+	// @step create a horizontal pod autoscaler for eligible objects
+	if o != nil {
 		hpa := k.initHpa(projectService, o)
 		if hpa != nil {
 			objects = append(objects, hpa)
 		}
-	case config.WorkloadTypesEqual(workloadType, config.DaemonSetWorkload):
-		objects = append(objects, k.initDaemonSet(projectService))
+	}
+
+	// @step create a Service Account if speficied
+	if sa := k.initServiceAccount(projectService); sa != nil {
+		objects = append(objects, sa)
 	}
 
 	return objects


Test patch:
diff --git a/pkg/kev/converter/kubernetes/transform_test.go b/pkg/kev/converter/kubernetes/transform_test.go
index 1ae13b1..03d76a4 100644
--- a/pkg/kev/converter/kubernetes/transform_test.go
+++ b/pkg/kev/converter/kubernetes/transform_test.go
@@ -1218,6 +1218,58 @@ var _ = Describe("Transform", func() {
 
 	})
 
+	Describe("initSa", func() {
+		When("service account name is specified as empty string in the workload configuration", func() {
+			BeforeEach(func() {
+				projectService.SvcK8sConfig.Workload.ServiceAccountName = ""
+			})
+
+			It("doesn't initialize ServiceAccount for that project service", func() {
+				sa := k.initServiceAccount(projectService)
+				Expect(sa).To(BeNil())
+			})
+		})
+
+		When("service account name is defined as `default`", func() {
+			BeforeEach(func() {
+				projectService.SvcK8sConfig.Workload.ServiceAccountName = "default"
+			})
+
+			It("doesn't initialize ServiceAccount for that project service", func() {
+				sa := k.initServiceAccount(projectService)
+				Expect(sa).To(BeNil())
+			})
+		})
+
+		When("service account name is specified with name different than `default`", func() {
+			BeforeEach(func() {
+				projectService.SvcK8sConfig.Workload.ServiceAccountName = "mysvcacc"
+			})
+
+			It("initializes ServiceAccount for the project service", func() {
+				sa := k.initServiceAccount(projectService)
+				Expect(sa).ToNot(BeNil())
+
+				automountSAToken := false
+
+				expected := &v1.ServiceAccount{
+					TypeMeta: meta.TypeMeta{
+						Kind:       "ServiceAccount",
+						APIVersion: "v1",
+					},
+					ObjectMeta: meta.ObjectMeta{
+						Name:        "mysvcacc",
+						Labels:      configLabels(projectService.Name),
+						Annotations: configAnnotations(projectService.Labels),
+					},
+					AutomountServiceAccountToken: &automountSAToken,
+				}
+
+				Expect(sa).To(Equal(expected))
+			})
+		})
+	})
+
 	Describe("createSecrets", func() {
 		secretName := "my-secret"
 		var secretConfig composego.SecretConfig