Repository: keikoproj/instance-manager
Pull request #168 (2020-09-05 00:20:40)

support base64 payload in userData
This adds support for base64 data in userData.[].Data

TBD
- [x] Tests
- [x] Docs

codecov[bot]: # [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=h1) Report
> Merging [#168](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=desc) into [master](https://codecov.io/gh/keikoproj/instance-manager/commit/5082305e138511359b2a86acd12a6f9ccb57008c?el=desc) will **increase** coverage by `0.29%`.
> The diff coverage is `94.94%`.

[![Impacted file tree graph](https://codecov.io/gh/keikoproj/instance-manager/pull/168/graphs/tree.svg?width=650&height=150&src=pr&token=IJbjmSBliL)](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #168      +/-   ##
==========================================
+ Coverage   89.41%   89.71%   +0.29%     
==========================================
  Files          13       15       +2     
  Lines        1654     1653       -1     
==========================================
+ Hits         1479     1483       +4     
+ Misses        113      110       -3     
+ Partials       62       60       -2     
```


| [Impacted Files](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [controllers/provisioners/config.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2NvbmZpZy5nbw==) | `73.98% <ø> (-0.21%)` | :arrow_down: |
| [controllers/provisioners/eks/state.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9zdGF0ZS5nbw==) | `92.85% <ø> (ø)` | |
| [controllers/provisioners/eksfargate/eksfargate.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrc2ZhcmdhdGUvZWtzZmFyZ2F0ZS5nbw==) | `93.91% <ø> (-0.13%)` | :arrow_down: |
| [controllers/provisioners/eksmanaged/eksmanaged.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrc21hbmFnZWQvZWtzbWFuYWdlZC5nbw==) | `84.41% <ø> (+2.65%)` | :arrow_up: |
| [controllers/provisioners/eks/helpers.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9oZWxwZXJzLmdv) | `88.46% <69.23%> (-1.64%)` | :arrow_down: |
| [controllers/provisioners/eks/cloud.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9jbG91ZC5nbw==) | `89.28% <87.50%> (+3.39%)` | :arrow_up: |
| [...ntrollers/provisioners/eks/scaling/launchconfig.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9zY2FsaW5nL2xhdW5jaGNvbmZpZy5nbw==) | `95.30% <95.30%> (ø)` | |
| [controllers/provisioners/eks/create.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9jcmVhdGUuZ28=) | `95.50% <100.00%> (+2.73%)` | :arrow_up: |
| [controllers/provisioners/eks/delete.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9kZWxldGUuZ28=) | `96.36% <100.00%> (+0.90%)` | :arrow_up: |
| [controllers/provisioners/eks/update.go](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy91cGRhdGUuZ28=) | `93.49% <100.00%> (-3.11%)` | :arrow_down: |
| ... and [6 more](https://codecov.io/gh/keikoproj/instance-manager/pull/168/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=footer). Last update [af5eed1...3b54700](https://codecov.io/gh/keikoproj/instance-manager/pull/168?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/controllers/common/utils.go b/controllers/common/utils.go
index bc14795..55e2bc9 100644
--- a/controllers/common/utils.go
+++ b/controllers/common/utils.go
@@ -17,10 +17,12 @@ package common
 
 import (
 	"crypto/md5"
+	"encoding/base64"
 	"encoding/hex"
 	"fmt"
 	"io/ioutil"
 	"reflect"
+	"regexp"
 	"sort"
 	"strings"
 	"time"
@@ -28,6 +30,29 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
+const (
+	Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
+)
+
+var (
+	rxBase64 = regexp.MustCompile(Base64)
+)
+
+func IsBase64(str string) bool {
+	return rxBase64.MatchString(str)
+}
+
+func GetDecodedString(str string) (string, error) {
+	if IsBase64(str) {
+		d, err := base64.StdEncoding.DecodeString(str)
+		if err != nil {
+			return "", err
+		}
+		return string(d), nil
+	}
+	return str, nil
+}
+
 // ContainsString returns true if a given slice 'slice' contains string 's', otherwise return false
 func ContainsString(slice []string, s string) bool {
 	for _, item := range slice {
diff --git a/controllers/provisioners/eks/helpers.go b/controllers/provisioners/eks/helpers.go
index 77ec81b..af97c61 100644
--- a/controllers/provisioners/eks/helpers.go
+++ b/controllers/provisioners/eks/helpers.go
@@ -105,9 +105,17 @@ func (ctx *EksInstanceGroupContext) GetUserDataStages() ([]string, []string) {
 	for _, stage := range userData {
 		switch {
 		case strings.EqualFold(stage.Stage, v1alpha1.PreBootstrapStage):
-			preScript = append(preScript, stage.Data)
+			data, err := common.GetDecodedString(stage.Data)
+			if err != nil {
+				ctx.Log.Error(err, "failed to decode base64 stage data", "stage", stage.Stage, "data", stage.Data)
+			}
+			preScript = append(preScript, data)
 		case strings.EqualFold(stage.Stage, v1alpha1.PostBootstrapStage):
-			postScript = append(postScript, stage.Data)
+			data, err := common.GetDecodedString(stage.Data)
+			if err != nil {
+				ctx.Log.Error(err, "failed to decode base64 stage data", "stage", stage.Stage, "data", stage.Data)
+			}
+			postScript = append(postScript, 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 7fd5303..d1e2d10 100644
--- a/docs/EKS.md
+++ b/docs/EKS.md
@@ -99,7 +99,7 @@ spec:
       userData:
       - name: <string> : name of the stage
         stage: <string> : represents the stage of the script, allowed values are PreBootstrap, PostBootstrap (required)
-        data: <string> : represents the script payload to inject (required)
+        data: <string> : represents the script payload to inject in plain text or base64 (required)
 ```
 
 ### NodeVolume


Test patch:
diff --git a/controllers/provisioners/eks/helpers_test.go b/controllers/provisioners/eks/helpers_test.go
index 02455e8..66d1667 100644
--- a/controllers/provisioners/eks/helpers_test.go
+++ b/controllers/provisioners/eks/helpers_test.go
@@ -298,13 +298,16 @@ func TestGetUserDataStages(t *testing.T) {
 	ctx := MockContext(ig, k, w)
 
 	tests := []struct {
-		preBootstrapScript  []string
-		postBootstrapScript []string
+		preBootstrapScript    []string
+		postBootstrapScript   []string
+		expectedPreBootstrap  []string
+		expectedPostBootstrap []string
 	}{
 		{},
-		{preBootstrapScript: []string{""}, postBootstrapScript: []string{""}},
-		{preBootstrapScript: []string{"prebootstrap"}, postBootstrapScript: []string{"postbootstrap"}},
-		{preBootstrapScript: []string{"prebootstrap1", "prebootstrap2"}, postBootstrapScript: []string{"postbootstrap1", "postbootstrap2"}},
+		{preBootstrapScript: []string{""}, postBootstrapScript: []string{""}, expectedPreBootstrap: []string{""}, expectedPostBootstrap: []string{""}},
+		{preBootstrapScript: []string{"dGVzdA=="}, postBootstrapScript: []string{"dGVzdDE="}, expectedPreBootstrap: []string{"test"}, expectedPostBootstrap: []string{"test1"}},
+		{preBootstrapScript: []string{"prebootstrap1"}, postBootstrapScript: []string{"postbootstrap"}, expectedPreBootstrap: []string{"prebootstrap1"}, expectedPostBootstrap: []string{"postbootstrap"}},
+		{preBootstrapScript: []string{"prebootstrap1", "prebootstrap2"}, postBootstrapScript: []string{"postbootstrap1", "postbootstrap2"}, expectedPreBootstrap: []string{"prebootstrap1", "prebootstrap2"}, expectedPostBootstrap: []string{"postbootstrap1", "postbootstrap2"}},
 	}
 
 	for i, tc := range tests {
@@ -332,7 +335,7 @@ func TestGetUserDataStages(t *testing.T) {
 			Data:  "test",
 		})
 		preScript, postScript := ctx.GetUserDataStages()
-		g.Expect(preScript).To(gomega.ConsistOf(tc.preBootstrapScript))
-		g.Expect(postScript).To(gomega.ConsistOf(tc.postBootstrapScript))
+		g.Expect(preScript).To(gomega.ConsistOf(tc.expectedPreBootstrap))
+		g.Expect(postScript).To(gomega.ConsistOf(tc.expectedPostBootstrap))
 	}
 }