Repository: keikoproj/instance-manager (go)

Issue:
Support multiple userData stages by name
We should change the API while it's unreleased from
```yaml
userData:
  - stage: PreBootstrap
    script: | <content>
```

to
```yaml
  - name: my-stage 
    stage: PreBootstrap
    script: | <content>
```
This will allow multiple scripts to be executed and better ordering

Pull request description:
Add "Name" Field to UserDataStage
Fixes #158 

This adds the "Name" field to the UserData stage struct.

- [x] Functional test

Patch:
diff --git a/api/v1alpha1/instancegroup_types.go b/api/v1alpha1/instancegroup_types.go
index fecf664..9c07cb0 100644
--- a/api/v1alpha1/instancegroup_types.go
+++ b/api/v1alpha1/instancegroup_types.go
@@ -186,6 +186,7 @@ type EKSConfiguration struct {
 }
 
 type UserDataStage struct {
+	Name  string `json:"name,omitempty"`
 	Stage string `json:"stage,omitempty"`
 	Data  string `json:"data,omitempty"`
 }
diff --git a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
index 0e6fa04..4fa6750 100644
--- a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
+++ b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
@@ -154,6 +154,8 @@ spec:
                         properties:
                           data:
                             type: string
+                          name:
+                            type: string
                           stage:
                             type: string
                         type: object
diff --git a/controllers/providers/aws/aws.go b/controllers/providers/aws/aws.go
index 457c4fd..19ea461 100644
--- a/controllers/providers/aws/aws.go
+++ b/controllers/providers/aws/aws.go
@@ -105,16 +105,16 @@ const (
 type EKSUserData struct {
 	ClusterName   string
 	Arguments     string
-	PreBootstrap  string
-	PostBootstrap string
+	PreBootstrap  []string
+	PostBootstrap []string
 }
 
 var UserDataPayload = `#!/bin/bash
-{{ .PreBootstrap }}
+{{range $pre := .PreBootstrap}}{{$pre}}{{end}}
 set -o xtrace
 /etc/eks/bootstrap.sh {{ .ClusterName }} {{ .Arguments }}
 set +o xtrace
-{{ .PostBootstrap }}`
+{{range $post := .PostBootstrap}}{{$post}}{{end}}`
 
 func RenderUserData(data EKSUserData) []byte {
 	out := &bytes.Buffer{}
@@ -254,7 +254,7 @@ func (w *AwsWorker) SetResumeProcesses(name string, processesToResume []string)
 	return nil
 }
 
-func (w *AwsWorker) GetBasicUserData(clusterName, args, preScript, postScript string) string {
+func (w *AwsWorker) GetBasicUserData(clusterName, args string, preScript, postScript []string) string {
 	userData := RenderUserData(EKSUserData{
 		ClusterName:   clusterName,
 		Arguments:     args,
diff --git a/controllers/provisioners/eks/helpers.go b/controllers/provisioners/eks/helpers.go
index ba657c4..de751f8 100644
--- a/controllers/provisioners/eks/helpers.go
+++ b/controllers/provisioners/eks/helpers.go
@@ -66,20 +66,20 @@ func (ctx *EksInstanceGroupContext) GetLaunchConfigurationInput(name string) *au
 	return input
 }
 
-func (ctx *EksInstanceGroupContext) GetUserDataStages() (string, string) {
+func (ctx *EksInstanceGroupContext) GetUserDataStages() ([]string, []string) {
 	var (
 		instanceGroup = ctx.GetInstanceGroup()
 		configuration = instanceGroup.GetEKSConfiguration()
 		userData      = configuration.GetUserData()
 	)
 
-	var preScript, postScript string
+	var preScript, postScript []string
 	for _, stage := range userData {
 		switch {
 		case strings.EqualFold(stage.Stage, v1alpha1.PreBootstrapStage):
-			preScript = stage.Data
+			preScript = append(preScript, stage.Data)
 		case strings.EqualFold(stage.Stage, v1alpha1.PostBootstrapStage):
-			postScript = stage.Data
+			postScript = append(postScript, stage.Data)
 		default:
 			ctx.Log.Info("invalid userdata stage will not be rendered", "stage", stage.Stage, "data", stage.Data)
 		}
diff --git a/docs/EKS.md b/docs/EKS.md
index 276e07b..970ba8c 100644
--- a/docs/EKS.md
+++ b/docs/EKS.md
@@ -97,7 +97,8 @@ spec:
   eks:
     configuration:
       userData:
-      - stage: <string> : represents the stage of the script, allowed values are PreBootstrap, PostBootstrap
+      - name: <string> : name of the stage
+        stage: <string> : represents the stage of the script, allowed values are PreBootstrap, PostBootstrap
         data: <string> : represents the script payload to inject
 ```
 


Test patch:
diff --git a/controllers/provisioners/eks/helpers_test.go b/controllers/provisioners/eks/helpers_test.go
index 76e768f..c96eda4 100644
--- a/controllers/provisioners/eks/helpers_test.go
+++ b/controllers/provisioners/eks/helpers_test.go
@@ -16,6 +16,7 @@ limitations under the License.
 package eks
 
 import (
+	"fmt"
 	"sort"
 	"testing"
 
@@ -209,31 +210,42 @@ func TestGetUserDataStages(t *testing.T) {
 	ctx := MockContext(ig, k, w)
 
 	tests := []struct {
-		preBootstrapScript  string
-		postBootstrapScript string
+		preBootstrapScript  []string
+		postBootstrapScript []string
 	}{
 		{},
-		{preBootstrapScript: "", postBootstrapScript: ""},
-		{preBootstrapScript: "prebootstrap", postBootstrapScript: "postbootstrap"},
+		{preBootstrapScript: []string{""}, postBootstrapScript: []string{""}},
+		{preBootstrapScript: []string{"prebootstrap"}, postBootstrapScript: []string{"postbootstrap"}},
+		{preBootstrapScript: []string{"prebootstrap1", "prebootstrap2"}, postBootstrapScript: []string{"postbootstrap1", "postbootstrap2"}},
 	}
 
 	for i, tc := range tests {
 		t.Logf("Test #%v - %+v", i, tc)
+		configuration.UserData = []v1alpha1.UserDataStage{}
+		for _, data := range tc.preBootstrapScript {
+			configuration.UserData = append(configuration.UserData, v1alpha1.UserDataStage{
+				Name:  fmt.Sprintf("stage-%v", i),
+				Stage: v1alpha1.PreBootstrapStage,
+				Data:  data,
+			})
+		}
+
+		for _, data := range tc.postBootstrapScript {
+			configuration.UserData = append(configuration.UserData, v1alpha1.UserDataStage{
+				Name:  fmt.Sprintf("stage-%v", i),
+				Stage: v1alpha1.PostBootstrapStage,
+				Data:  data,
+			})
+		}
+
 		configuration.UserData = append(configuration.UserData, v1alpha1.UserDataStage{
-			Stage: v1alpha1.PreBootstrapStage,
-			Data:  tc.preBootstrapScript,
-		})
-		configuration.UserData = append(configuration.UserData, v1alpha1.UserDataStage{
-			Stage: v1alpha1.PostBootstrapStage,
-			Data:  tc.postBootstrapScript,
-		})
-		configuration.UserData = append(configuration.UserData, v1alpha1.UserDataStage{
+			Name:  fmt.Sprintf("stage-%v", i),
 			Stage: "invalid-stage",
 			Data:  "test",
 		})
 		preScript, postScript := ctx.GetUserDataStages()
-		g.Expect(preScript).To(gomega.Equal(tc.preBootstrapScript))
-		g.Expect(postScript).To(gomega.Equal(tc.postBootstrapScript))
+		g.Expect(preScript).To(gomega.ConsistOf(tc.preBootstrapScript))
+		g.Expect(postScript).To(gomega.ConsistOf(tc.postBootstrapScript))
 	}
 }