Repository: keikoproj/instance-manager
Pull request #188 (2020-11-09 20:35:52)

feat: Add BottleRocket os family
This PR leverages the os-family annotation introduced in #186 to support [BottleRocket](https://aws.amazon.com/bottlerocket/) AMIs on EKS self-managed node groups. 

Signed-off-by: Jonah Back <jonah@jonahback.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/188?src=pr&el=h1) Report
> Merging [#188](https://codecov.io/gh/keikoproj/instance-manager/pull/188?src=pr&el=desc) (0ae816b) into [master](https://codecov.io/gh/keikoproj/instance-manager/commit/8d7d1abf139da759d7d6d6c9debd43ddc22bab6c?el=desc) (8d7d1ab) will **decrease** coverage by `0.01%`.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff             @@
##           master     #188      +/-   ##
==========================================
- Coverage   89.26%   89.25%   -0.02%     
==========================================
  Files          15       15              
  Lines        1649     1684      +35     
==========================================
+ Hits         1472     1503      +31     
- Misses        109      111       +2     
- Partials       68       70       +2     
```


| [Impacted Files](https://codecov.io/gh/keikoproj/instance-manager/pull/188?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [controllers/provisioners/eks/eks.go](https://codecov.io/gh/keikoproj/instance-manager/pull/188/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9la3MuZ28=) | `86.48% <100.00%> (-5.41%)` | :arrow_down: |
| [controllers/provisioners/eks/helpers.go](https://codecov.io/gh/keikoproj/instance-manager/pull/188/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9oZWxwZXJzLmdv) | `89.74% <100.00%> (+0.76%)` | :arrow_up: |
| [controllers/provisioners/eks/cloud.go](https://codecov.io/gh/keikoproj/instance-manager/pull/188/diff?src=pr&el=tree#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9jbG91ZC5nbw==) | `84.29% <0.00%> (-1.66%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/188?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/188?src=pr&el=footer). Last update [8d7d1ab...0ae816b](https://codecov.io/gh/keikoproj/instance-manager/pull/188?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

backjo: @eytan-avisror testing with an IG from master, running instance-manager on the latest commit here did not change the status of the IG, which remained in Ready

Patch:
diff --git a/controllers/provisioners/eks/eks.go b/controllers/provisioners/eks/eks.go
index df96adb..e25aab2 100644
--- a/controllers/provisioners/eks/eks.go
+++ b/controllers/provisioners/eks/eks.go
@@ -17,6 +17,7 @@ package eks
 
 import (
 	"fmt"
+	corev1 "k8s.io/api/core/v1"
 	"sync"
 
 	"github.com/go-logr/logr"
@@ -34,13 +35,18 @@ const (
 	OsFamilyAnnotation                  = "instancemgr.keikoproj.io/os-family"
 	ClusterAutoscalerEnabledAnnotation  = "instancemgr.keikoproj.io/cluster-autoscaler-enabled"
 
-	OsFamilyWindows                     = "windows"
+	OsFamilyWindows      = "windows"
+	OsFamilyBottleRocket = "bottlerocket"
+	OsFamilyAmazonLinux2 = "amazonlinux2"
 )
 
 var (
-	RoleNewLabelFmt     = "node.kubernetes.io/role=%s"
-	RoleOldLabelFmt     = "node-role.kubernetes.io/%s=\"\""
-	InstanceMgrLabelFmt = "instancemgr.keikoproj.io/%s=%s"
+	RoleNewLabel              = "node.kubernetes.io/role"
+	RoleNewLabelFmt           = "node.kubernetes.io/role=%s"
+	RoleOldLabel              = "node-role.kubernetes.io/%s"
+	RoleOldLabelFmt           = "node-role.kubernetes.io/%s=\"\""
+	InstanceMgrLifecycleLabel = "instancemgr.keikoproj.io/lifecycle"
+	InstanceMgrLabelFmt       = "instancemgr.keikoproj.io/%s=%s"
 
 	DefaultManagedPolicies = []string{"AmazonEKSWorkerNodePolicy", "AmazonEKS_CNI_Policy", "AmazonEC2ContainerRegistryReadOnly"}
 )
@@ -97,12 +103,16 @@ type MountOpts struct {
 }
 
 type EKSUserData struct {
-	ClusterName   string
+	ApiEndpoint      string
+	ClusterCA        string
+	ClusterName      string
+	NodeLabels       map[string]string
+	NodeTaints       []corev1.Taint
 	KubeletExtraArgs string
-	Arguments     string
-	PreBootstrap  []string
-	PostBootstrap []string
-	MountOptions  []MountOpts
+	Arguments        string
+	PreBootstrap     []string
+	PostBootstrap    []string
+	MountOptions     []MountOpts
 }
 
 func (ctx *EksInstanceGroupContext) GetInstanceGroup() *v1alpha1.InstanceGroup {
@@ -114,14 +124,13 @@ func (ctx *EksInstanceGroupContext) GetInstanceGroup() *v1alpha1.InstanceGroup {
 
 func (ctx *EksInstanceGroupContext) GetOsFamily() string {
 	var (
-		instanceGroup  = ctx.GetInstanceGroup()
-		annotations = instanceGroup.GetAnnotations()
+		instanceGroup = ctx.GetInstanceGroup()
+		annotations   = instanceGroup.GetAnnotations()
 	)
 	if _, exists := annotations[OsFamilyAnnotation]; exists {
 		return annotations[OsFamilyAnnotation]
 	}
-	return "default"  // return "" is will also be fine here
-
+	return OsFamilyAmazonLinux2
 }
 
 func (ctx *EksInstanceGroupContext) GetUpgradeStrategy() *v1alpha1.AwsUpgradeStrategy {
diff --git a/controllers/provisioners/eks/helpers.go b/controllers/provisioners/eks/helpers.go
index ab323f9..3cb1862 100644
--- a/controllers/provisioners/eks/helpers.go
+++ b/controllers/provisioners/eks/helpers.go
@@ -97,18 +97,43 @@ func (ctx *EksInstanceGroupContext) ResolveSecurityGroups() []string {
 }
 
 func (ctx *EksInstanceGroupContext) GetBasicUserData(clusterName, args string, kubeletExtraArgs string, payload UserDataPayload, mounts []MountOpts) string {
-      osFamily := ctx.GetOsFamily()
+	var (
+		instanceGroup = ctx.GetInstanceGroup()
+		configuration = instanceGroup.GetEKSConfiguration()
+		cluster       = ctx.GetDiscoveredState().Cluster
+		apiEndpoint   = cluster.Endpoint
+		clusterCa     = cluster.CertificateAuthority.Data
+		osFamily      = ctx.GetOsFamily()
+		nodeLabels    = ctx.GetComputedLabels()
+		nodeTaints    = configuration.GetTaints()
+	)
+
 	var UserDataTemplate string
-	if strings.EqualFold(osFamily, OsFamilyWindows) {
+	switch strings.ToLower(osFamily) {
+	case OsFamilyWindows:
 		UserDataTemplate = `
 <powershell>
   [string]$EKSBinDir = "$env:ProgramFiles\Amazon\EKS"
   [string]$EKSBootstrapScriptName = 'Start-EKSBootstrap.ps1'
   [string]$EKSBootstrapScriptFile = "$EKSBinDir\$EKSBootstrapScriptName"
   & $EKSBootstrapScriptFile -EKSClusterName {{ .ClusterName }} -KubeletExtraArgs '{{ .KubeletExtraArgs }}' 3>&1 4>&1 5>&1 6>&1
-</powershell>
-		`
-	} else {
+</powershell>`
+	case OsFamilyBottleRocket:
+		UserDataTemplate = `
+[settings.kubernetes]
+api-server   = "{{ .ApiEndpoint }}"
+cluster-certificate = "{{ .ClusterCA }}"
+cluster-name = "{{ .ClusterName }}"
+[settings.kubernetes.node-labels]
+{{- range $key, $value := .NodeLabels }}
+"{{ $key }}" = "{{ $value }}"
+{{- end}}
+[settings.kubernetes.node-taints]
+{{- range .NodeTaints}}
+"{{ .Key }}" = "{{ .Value }}:{{ .Effect }}"
+{{- end}}
+`
+	case OsFamilyAmazonLinux2:
 		UserDataTemplate = `#!/bin/bash
 {{range $pre := .PreBootstrap}}{{$pre}}{{end}}
 {{- range .MountOptions}}
@@ -125,13 +150,18 @@ set -o xtrace
 set +o xtrace
 {{range $post := .PostBootstrap}}{{$post}}{{end}}`
 	}
+
 	data := EKSUserData{
-		ClusterName:   clusterName,
+		ApiEndpoint:      *apiEndpoint,
+		ClusterCA:        *clusterCa,
+		ClusterName:      clusterName,
+		NodeLabels:       nodeLabels,
+		NodeTaints:       nodeTaints,
 		KubeletExtraArgs: kubeletExtraArgs,
-		Arguments:     args,
-		PreBootstrap:  payload.PreBootstrap,
-		PostBootstrap: payload.PostBootstrap,
-		MountOptions:  mounts,
+		Arguments:        args,
+		PreBootstrap:     payload.PreBootstrap,
+		PostBootstrap:    payload.PostBootstrap,
+		MountOptions:     mounts,
 	}
 	out := &bytes.Buffer{}
 	tmpl := template.New("userData").Funcs(template.FuncMap{
@@ -219,8 +249,9 @@ func (ctx *EksInstanceGroupContext) GetAddedTags(asgName string) []*autoscaling.
 		configuration = instanceGroup.GetEKSConfiguration()
 		clusterName   = configuration.GetClusterName()
 		annotations   = instanceGroup.GetAnnotations()
-		labels 	      = configuration.GetLabels()
+		labels        = ctx.GetComputedLabels()
 		taints        = configuration.GetTaints()
+		osFamily      = ctx.GetOsFamily()
 	)
 
 	tags = append(tags, ctx.AwsWorker.NewTag("Name", asgName, asgName))
@@ -233,6 +264,13 @@ func (ctx *EksInstanceGroupContext) GetAddedTags(asgName string) []*autoscaling.
 		tags = append(tags, ctx.AwsWorker.NewTag(fmt.Sprintf("k8s.io/cluster-autoscaler/%v", clusterName), "owned", asgName))
 		tags = append(tags, ctx.AwsWorker.NewTag("k8s.io/cluster-autoscaler/enabled", "true", asgName))
 
+		switch strings.ToLower(osFamily) {
+		case OsFamilyWindows:
+			tags = append(tags, ctx.AwsWorker.NewTag("k8s.io/cluster-autoscaler/node-template/label/kubernetes.io/os", "windows", asgName))
+		default:
+			tags = append(tags, ctx.AwsWorker.NewTag("k8s.io/cluster-autoscaler/node-template/label/kubernetes.io/os", "linux", asgName))
+		}
+
 		for label, labelValue := range labels {
 			tags = append(tags, ctx.AwsWorker.NewTag(fmt.Sprintf("k8s.io/cluster-autoscaler/node-template/label/%v", label), labelValue, asgName))
 		}
@@ -328,10 +366,10 @@ func (ctx *EksInstanceGroupContext) GetTaintList() []string {
 	return taintList
 }
 
-func (ctx *EksInstanceGroupContext) GetLabelList() []string {
+func (ctx *EksInstanceGroupContext) GetComputedLabels() map[string]string {
 	var (
-		labelList     []string
 		isOverride    bool
+		labelMap      = make(map[string]string)
 		instanceGroup = ctx.GetInstanceGroup()
 		annotations   = instanceGroup.GetAnnotations()
 		configuration = instanceGroup.GetEKSConfiguration()
@@ -341,7 +379,7 @@ func (ctx *EksInstanceGroupContext) GetLabelList() []string {
 	// get custom labels
 	if len(customLabels) > 0 {
 		for k, v := range customLabels {
-			labelList = append(labelList, fmt.Sprintf("%v=%v", k, v))
+			labelMap[k] = v
 		}
 	}
 
@@ -350,34 +388,53 @@ func (ctx *EksInstanceGroupContext) GetLabelList() []string {
 		isOverride = true
 		overrideLabels := strings.Split(val, ",")
 		for _, label := range overrideLabels {
-			labelList = append(labelList, label)
+			keyVal := strings.Split(label, "=")
+			if len(keyVal) == 2 {
+				labelMap[keyVal[0]] = keyVal[1]
+			} else {
+				labelMap[keyVal[0]] = ""
+			}
 		}
 	}
 
 	if !isOverride {
 		// add default labels
-		labelList = append(labelList, fmt.Sprintf(RoleNewLabelFmt, instanceGroup.GetName()))
+		labelMap[RoleNewLabel] = instanceGroup.GetName()
 
 		// add the old style role label if the cluster's k8s version is < 1.16
 		clusterVersion := ctx.DiscoveredState.GetClusterVersion()
 		ver, err := semver.NewVersion(clusterVersion)
 		if err != nil {
 			ctx.Log.Error(err, "Failed parsing the cluster's kubernetes version", "instancegroup", instanceGroup.GetName())
-			labelList = append(labelList, fmt.Sprintf(RoleOldLabelFmt, instanceGroup.GetName()))
+			labelMap[fmt.Sprintf(RoleOldLabel, instanceGroup.GetName())] = ""
 		} else {
 			c, _ := semver.NewConstraint("< 1.16-0")
 			if c.Check(ver) {
-				labelList = append(labelList, fmt.Sprintf(RoleOldLabelFmt, instanceGroup.GetName()))
+				labelMap[fmt.Sprintf(RoleOldLabel, instanceGroup.GetName())] = ""
 			}
 		}
 	}
 
 	if configuration.GetSpotPrice() == "" {
-		labelList = append(labelList, fmt.Sprintf(InstanceMgrLabelFmt, "lifecycle", v1alpha1.LifecycleStateNormal))
+		labelMap[InstanceMgrLifecycleLabel] = v1alpha1.LifecycleStateNormal
 	} else {
-		labelList = append(labelList, fmt.Sprintf(InstanceMgrLabelFmt, "lifecycle", v1alpha1.LifecycleStateSpot))
+		labelMap[InstanceMgrLifecycleLabel] = v1alpha1.LifecycleStateSpot
 	}
 
+	return labelMap
+}
+
+func (ctx *EksInstanceGroupContext) GetLabelList() []string {
+	var (
+		labelList []string
+	)
+
+	for key, value := range ctx.GetComputedLabels() {
+		if value == "" {
+			value = "\"\""
+		}
+		labelList = append(labelList, fmt.Sprintf("%v=%v", key, value))
+	}
 	sort.Strings(labelList)
 	return labelList
 }
@@ -733,25 +790,25 @@ func (ctx *EksInstanceGroupContext) UpdateLifecycleHooks(asgName string) error {
 	if hooks, ok := ctx.GetAddedHooks(); ok {
 		for _, hook := range hooks {
 			input := &autoscaling.PutLifecycleHookInput{
-				AutoScalingGroupName:  aws.String(asgName),
-				LifecycleHookName:     aws.String(hook.Name),
-				DefaultResult:         aws.String(hook.DefaultResult),
-				HeartbeatTimeout:      aws.Int64(hook.HeartbeatTimeout),
-				LifecycleTransition:   aws.String(hook.Lifecycle),
+				AutoScalingGroupName: aws.String(asgName),
+				LifecycleHookName:    aws.String(hook.Name),
+				DefaultResult:        aws.String(hook.DefaultResult),
+				HeartbeatTimeout:     aws.Int64(hook.HeartbeatTimeout),
+				LifecycleTransition:  aws.String(hook.Lifecycle),
 			}
-			
+
 			if !common.StringEmpty(hook.Metadata) {
 				input.NotificationMetadata = aws.String(hook.Metadata)
 			}
-			
+
 			if !common.StringEmpty(hook.RoleArn) {
 				input.RoleARN = aws.String(hook.RoleArn)
 			}
-			
+
 			if !common.StringEmpty(hook.NotificationArn) {
 				input.NotificationTargetARN = aws.String(hook.NotificationArn)
 			}
-            
+
 			if err := ctx.AwsWorker.CreateLifecycleHook(input); err != nil {
 				return errors.Wrapf(err, "failed to add lifecycle hook %v", hook)
 			}


Test patch:
diff --git a/controllers/provisioners/eks/create_test.go b/controllers/provisioners/eks/create_test.go
index 1aa4fd3..3d8be6e 100644
--- a/controllers/provisioners/eks/create_test.go
+++ b/controllers/provisioners/eks/create_test.go
@@ -25,7 +25,6 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/service/autoscaling"
-	"github.com/aws/aws-sdk-go/service/eks"
 	"github.com/aws/aws-sdk-go/service/iam"
 	"github.com/keikoproj/instance-manager/api/v1alpha1"
 	"github.com/onsi/gomega"
@@ -46,7 +45,7 @@ func TestCreateManagedRolePositive(t *testing.T) {
 	w := MockAwsWorker(asgMock, iamMock, eksMock, ec2Mock)
 	ctx := MockContext(ig, k, w)
 	state := ctx.GetDiscoveredState()
-	state.SetCluster(&eks.Cluster{Version: aws.String("1.15")})
+	state.SetCluster(MockEksCluster("1.15"))
 	state.Publisher.Client = k.Kubernetes
 	state.ScalingConfiguration = &scaling.LaunchConfiguration{
 		AwsWorker: w,
@@ -136,6 +135,7 @@ func TestCreateScalingGroupPositive(t *testing.T) {
 		Publisher: kubeprovider.EventPublisher{
 			Client: k.Kubernetes,
 		},
+		Cluster:              MockEksCluster(""),
 		ScalingConfiguration: lc,
 	})
 
@@ -175,6 +175,7 @@ func TestCreateNoOp(t *testing.T) {
 	}
 
 	ctx.SetDiscoveredState(&DiscoveredState{
+		Cluster: MockEksCluster(""),
 		Publisher: kubeprovider.EventPublisher{
 			Client: k.Kubernetes,
 		},
diff --git a/controllers/provisioners/eks/eks_test.go b/controllers/provisioners/eks/eks_test.go
index 05bc014..f0b05f3 100644
--- a/controllers/provisioners/eks/eks_test.go
+++ b/controllers/provisioners/eks/eks_test.go
@@ -64,9 +64,7 @@ func NewIamMocker() *MockIamClient {
 
 func NewEksMocker() *MockEksClient {
 	mock := &MockEksClient{
-		EksCluster: &eks.Cluster{
-			ResourcesVpcConfig: &eks.VpcConfigResponse{},
-		},
+		EksCluster: MockEksCluster("1.18"),
 	}
 	return mock
 }
@@ -84,6 +82,17 @@ func MockAwsWorker(asgClient *MockAutoScalingClient, iamClient *MockIamClient, e
 	}
 }
 
+func MockEksCluster(version string) *eks.Cluster {
+	return &eks.Cluster{
+		CertificateAuthority: &eks.Certificate{
+			Data: aws.String(""),
+		},
+		Endpoint:           aws.String("foo.amazonaws.com"),
+		ResourcesVpcConfig: &eks.VpcConfigResponse{},
+		Version:            &version,
+	}
+}
+
 func MockKubernetesClientSet() kubeprovider.KubernetesClientSet {
 	return kubeprovider.KubernetesClientSet{
 		Kubernetes:  fake.NewSimpleClientset(),
@@ -98,7 +107,14 @@ func MockContext(instanceGroup *v1alpha1.InstanceGroup, kube kubeprovider.Kubern
 		InstanceGroup: instanceGroup,
 		Log:           ctrl.Log.WithName("unit-test").WithName("InstanceGroup"),
 	}
-	return New(input)
+	context := New(input)
+
+	context.DiscoveredState = &DiscoveredState{
+		Publisher: kubeprovider.EventPublisher{},
+		Cluster:   MockEksCluster("1.18"),
+		VPCId:     "",
+	}
+	return context
 }
 
 func MockInstanceGroup() *v1alpha1.InstanceGroup {
@@ -138,7 +154,38 @@ func MockWindowsInstanceGroup() *v1alpha1.InstanceGroup {
 			Namespace: "instance-manager",
 			Annotations: map[string]string{
 				ClusterAutoscalerEnabledAnnotation: "true",
-				OsFamilyAnnotation: OsFamilyWindows,
+				OsFamilyAnnotation:                 OsFamilyWindows,
+			},
+		},
+		Spec: v1alpha1.InstanceGroupSpec{
+			Provisioner: ProvisionerName,
+			EKSSpec: &v1alpha1.EKSSpec{
+				MaxSize: 3,
+				MinSize: 1,
+				EKSConfiguration: &v1alpha1.EKSConfiguration{
+					EksClusterName: "my-cluster",
+					SuspendedProcesses: []string{
+						"AZRebalance",
+					},
+				},
+			},
+			AwsUpgradeStrategy: v1alpha1.AwsUpgradeStrategy{
+				CRDType:           &v1alpha1.CRDUpdateStrategy{},
+				RollingUpdateType: &v1alpha1.RollingUpdateStrategy{},
+			},
+		},
+		Status: v1alpha1.InstanceGroupStatus{},
+	}
+}
+
+func MockBottleRocketInstanceGroup() *v1alpha1.InstanceGroup {
+	return &v1alpha1.InstanceGroup{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "instance-group-1",
+			Namespace: "instance-manager",
+			Annotations: map[string]string{
+				ClusterAutoscalerEnabledAnnotation: "true",
+				OsFamilyAnnotation:                 OsFamilyBottleRocket,
 			},
 		},
 		Spec: v1alpha1.InstanceGroupSpec{
diff --git a/controllers/provisioners/eks/helpers_test.go b/controllers/provisioners/eks/helpers_test.go
index b5632af..c644788 100644
--- a/controllers/provisioners/eks/helpers_test.go
+++ b/controllers/provisioners/eks/helpers_test.go
@@ -26,7 +26,6 @@ import (
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/service/autoscaling"
 	"github.com/aws/aws-sdk-go/service/ec2"
-	"github.com/aws/aws-sdk-go/service/eks"
 	"github.com/keikoproj/instance-manager/api/v1alpha1"
 	awsprovider "github.com/keikoproj/instance-manager/controllers/providers/aws"
 	kubeprovider "github.com/keikoproj/instance-manager/controllers/providers/kubernetes"
@@ -55,9 +54,9 @@ func TestAutoscalerTags(t *testing.T) {
 
 	ig.Spec.EKSSpec.EKSConfiguration.Taints = []corev1.Taint{}
 	ig.Spec.EKSSpec.EKSConfiguration.Taints = append(ig.Spec.EKSSpec.EKSConfiguration.Taints, corev1.Taint{
-		Key:       "red",
-		Value:     "green",
-		Effect:    "NoSchedule",
+		Key:    "red",
+		Value:  "green",
+		Effect: "NoSchedule",
 	})
 	ctx := MockContext(ig, k, w)
 
@@ -65,7 +64,7 @@ func TestAutoscalerTags(t *testing.T) {
 	expectedTags := make(map[string]string)
 
 	expectedTags["k8s.io/cluster-autoscaler/enabled"] = "true"
-	expectedTags["k8s.io/cluster-autoscaler/" + ig.Spec.EKSSpec.EKSConfiguration.EksClusterName] = "owned"
+	expectedTags["k8s.io/cluster-autoscaler/"+ig.Spec.EKSSpec.EKSConfiguration.EksClusterName] = "owned"
 	expectedTags["k8s.io/cluster-autoscaler/node-template/label/foo"] = "bar"
 	expectedTags["k8s.io/cluster-autoscaler/node-template/taint/red"] = "green:NoSchedule"
 
@@ -73,13 +72,12 @@ func TestAutoscalerTags(t *testing.T) {
 	for _, tag := range tagSlice {
 		tags[*tag.Key] = *tag.Value
 	}
-	for expectedKey, expectedValue  := range expectedTags {
+	for expectedKey, expectedValue := range expectedTags {
 		if tags[expectedKey] != expectedValue {
-			t.Fatalf("Expected %v=%v, Got %v",expectedKey,expectedValue,tags[expectedKey])
+			t.Fatalf("Expected %v=%v, Got %v", expectedKey, expectedValue, tags[expectedKey])
 		}
 	}
 
-
 }
 
 func TestResolveSecurityGroups(t *testing.T) {
@@ -320,9 +318,7 @@ func TestGetLabelList(t *testing.T) {
 			Publisher: kubeprovider.EventPublisher{
 				Client: k.Kubernetes,
 			},
-			Cluster: &eks.Cluster{
-				Version: aws.String(tc.clusterVersion),
-			},
+			Cluster: MockEksCluster(tc.clusterVersion),
 		})
 		if tc.spotPrice == "" {
 			tc.expectedLabels = append(tc.expectedLabels, defaultLifecycleLable)
@@ -505,43 +501,47 @@ func TestGetUserDataStages(t *testing.T) {
 
 func TestBootstrapDataForOSFamily(t *testing.T) {
 	var (
-		k             = MockKubernetesClientSet()
-		linuxIg       = MockInstanceGroup()
-		windowsIg            = MockWindowsInstanceGroup()
-		asgMock       = NewAutoScalingMocker()
-		iamMock       = NewIamMocker()
-		eksMock       = NewEksMocker()
-		ec2Mock       = NewEc2Mocker()
+		k              = MockKubernetesClientSet()
+		bottleRocketIg = MockBottleRocketInstanceGroup()
+		linuxIg        = MockInstanceGroup()
+		windowsIg      = MockWindowsInstanceGroup()
+		asgMock        = NewAutoScalingMocker()
+		iamMock        = NewIamMocker()
+		eksMock        = NewEksMocker()
+		ec2Mock        = NewEc2Mocker()
 	)
 
 	w := MockAwsWorker(asgMock, iamMock, eksMock, ec2Mock)
 
 	tests := []struct {
-		ig        *v1alpha1.InstanceGroup
+		ig                       *v1alpha1.InstanceGroup
 		expectedScriptSubstrings string
 	}{
 		{
-			ig: linuxIg,
+			ig:                       linuxIg,
 			expectedScriptSubstrings: "/etc/eks/bootstrap.sh",
 		},
 		{
-			ig: windowsIg,
+			ig:                       windowsIg,
 			expectedScriptSubstrings: "<powershell>",
 		},
+		{
+			ig:                       bottleRocketIg,
+			expectedScriptSubstrings: "[settings.kubernetes]",
+		},
 	}
 
 	for i, tc := range tests {
 		t.Logf("Test #%v - %+v", i, tc)
 		ctx := MockContext(tc.ig, k, w)
-		basicUserData := ctx.GetBasicUserData("", "","", UserDataPayload{}, []MountOpts{} )
+		basicUserData := ctx.GetBasicUserData("", "", "", UserDataPayload{}, []MountOpts{})
 		basicUserDataDecoded, _ := base64.StdEncoding.DecodeString(basicUserData)
 		basicUserDataString := string(basicUserDataDecoded)
 		if !strings.Contains(basicUserDataString, tc.expectedScriptSubstrings) {
-			t.Fatalf("Cound not find expected string %v script in %v",tc.expectedScriptSubstrings, basicUserDataString)
+			t.Fatalf("Cound not find expected string %v script in %v", tc.expectedScriptSubstrings, basicUserDataString)
 		}
 	}
 
-
 }
 
 func TestUpdateLifecycleHooks(t *testing.T) {
diff --git a/controllers/provisioners/eks/update_test.go b/controllers/provisioners/eks/update_test.go
index 6b469e9..49ac064 100644
--- a/controllers/provisioners/eks/update_test.go
+++ b/controllers/provisioners/eks/update_test.go
@@ -108,9 +108,7 @@ func TestUpdateWithDriftRotationPositive(t *testing.T) {
 			Arn: aws.String("some-instance-arn"),
 		},
 		ClusterNodes: nodes,
-		Cluster: &eks.Cluster{
-			Version: aws.String("1.15"),
-		},
+		Cluster:      MockEksCluster("1.15"),
 	})
 
 	err = ctx.Update()
@@ -137,9 +135,7 @@ func TestUpdateWithRotationPositive(t *testing.T) {
 		Publisher: kubeprovider.EventPublisher{
 			Client: k.Kubernetes,
 		},
-		Cluster: &eks.Cluster{
-			Version: aws.String("1.15"),
-		},
+		Cluster: MockEksCluster("1.15"),
 	})
 
 	input := &autoscaling.CreateLaunchConfigurationInput{
@@ -190,9 +186,7 @@ func TestUpdateWithRotationPositive(t *testing.T) {
 			TargetResource: mockLaunchConfig,
 		},
 		ClusterNodes: nodes,
-		Cluster: &eks.Cluster{
-			Version: aws.String("1.15"),
-		},
+		Cluster:      MockEksCluster("1.15"),
 	})
 
 	err = ctx.Update()
@@ -300,9 +294,7 @@ func TestLaunchConfigurationDrifted(t *testing.T) {
 				AwsWorker:      w,
 				TargetResource: tc.input,
 			},
-			Cluster: &eks.Cluster{
-				Version: aws.String("1.15"),
-			},
+			Cluster: MockEksCluster("1.15"),
 		})
 		got := ctx.DiscoveredState.ScalingConfiguration.Drifted(existingConfig)
 		g.Expect(got).To(gomega.Equal(tc.expected))
@@ -342,9 +334,7 @@ func TestUpdateScalingGroupNegative(t *testing.T) {
 		ScalingConfiguration: &scaling.LaunchConfiguration{
 			AwsWorker: w,
 		},
-		Cluster: &eks.Cluster{
-			Version: aws.String("1.15"),
-		},
+		Cluster: MockEksCluster("1.15"),
 	})
 
 	asgMock.UpdateAutoScalingGroupErr = errors.New("some-update-error")
@@ -426,6 +416,7 @@ func TestScalingGroupUpdatePredicate(t *testing.T) {
 	for i, tc := range tests {
 		t.Logf("Test #%v", i)
 		ctx.SetDiscoveredState(&DiscoveredState{
+			Cluster: MockEksCluster(""),
 			Publisher: kubeprovider.EventPublisher{
 				Client: k.Kubernetes,
 			},