Repository: keikoproj/instance-manager
Pull request #305 (2021-05-15 23:18:26)

feat: add support for instance metadata options
Closes #305 

codecov[bot]: # [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj) Report
> Merging [#305](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj) (28060d8) into [master](https://codecov.io/gh/keikoproj/instance-manager/commit/345ef0c4a543280bbc495e112fdf1efa7cde8de4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj) (345ef0c) will **decrease** coverage by `0.68%`.
> The diff coverage is `22.22%`.

> :exclamation: Current head 28060d8 differs from pull request most recent head 98cfb41. Consider uploading reports for the commit 98cfb41 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/keikoproj/instance-manager/pull/305/graphs/tree.svg?width=650&height=150&src=pr&token=IJbjmSBliL&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj)](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj)

```diff
@@            Coverage Diff             @@
##           master     #305      +/-   ##
==========================================
- Coverage   70.06%   69.38%   -0.69%     
==========================================
  Files          19       19              
  Lines        3107     3152      +45     
==========================================
+ Hits         2177     2187      +10     
- Misses        789      822      +33     
- Partials      141      143       +2     
```


| [Impacted Files](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj) | Coverage Δ | |
|---|---|---|
| [api/v1alpha1/instancegroup\_types.go](https://codecov.io/gh/keikoproj/instance-manager/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj#diff-YXBpL3YxYWxwaGExL2luc3RhbmNlZ3JvdXBfdHlwZXMuZ28=) | `15.08% <0.00%> (-0.05%)` | :arrow_down: |
| [api/v1alpha1/zz\_generated.deepcopy.go](https://codecov.io/gh/keikoproj/instance-manager/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj#diff-YXBpL3YxYWxwaGExL3p6X2dlbmVyYXRlZC5kZWVwY29weS5nbw==) | `0.00% <0.00%> (ø)` | |
| [controllers/provisioners/eks/scaling/interface.go](https://codecov.io/gh/keikoproj/instance-manager/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9zY2FsaW5nL2ludGVyZmFjZS5nbw==) | `0.00% <ø> (ø)` | |
| [...ntrollers/provisioners/eks/scaling/launchconfig.go](https://codecov.io/gh/keikoproj/instance-manager/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9zY2FsaW5nL2xhdW5jaGNvbmZpZy5nbw==) | `86.93% <28.57%> (-5.05%)` | :arrow_down: |
| [...rollers/provisioners/eks/scaling/launchtemplate.go](https://codecov.io/gh/keikoproj/instance-manager/pull/305/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj#diff-Y29udHJvbGxlcnMvcHJvdmlzaW9uZXJzL2Vrcy9zY2FsaW5nL2xhdW5jaHRlbXBsYXRlLmdv) | `83.62% <30.00%> (-5.06%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj). Last update [345ef0c...98cfb41](https://codecov.io/gh/keikoproj/instance-manager/pull/305?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=keikoproj).

eytan-avisror: LG, added suggestions to move some code to awsprovider
eytan-avisror: @backjo nevermind, I see actually in the cause of launchtemplate we are placing these constructors within the package.
We can refactor later if it makes sense, for now it's aligned with current patterns.

Patch:
diff --git a/api/v1alpha1/instancegroup_types.go b/api/v1alpha1/instancegroup_types.go
index 0c61611..2dcfb1e 100644
--- a/api/v1alpha1/instancegroup_types.go
+++ b/api/v1alpha1/instancegroup_types.go
@@ -227,6 +227,7 @@ type EKSConfiguration struct {
 	MixedInstancesPolicy        *MixedInstancesPolicySpec `json:"mixedInstancesPolicy,omitempty"`
 	LicenseSpecifications       []string                  `json:"licenseSpecifications,omitempty"`
 	Placement                   *PlacementSpec            `json:"placement,omitempty"`
+	MetadataOptions             *MetadataOptions          `json:"metadataOptions,omitempty"`
 }
 
 const (
@@ -250,6 +251,12 @@ type PlacementSpec struct {
 	Tenancy              string `json:"tenancy,omitempty"`
 }
 
+type MetadataOptions struct {
+	HttpEndpoint    string `json:"httpEndpoint,omitempty"`
+	HttpTokens      string `json:"httpTokens,omitempty"`
+	HttpPutHopLimit int64  `json:"httpPutHopLimit,omitempty"`
+}
+
 type InstanceTypeSpec struct {
 	Type   string `json:"type"`
 	Weight int64  `json:"weight,omitempty"`
@@ -721,6 +728,9 @@ func (c *EKSConfiguration) GetRoleName() string {
 func (c *EKSConfiguration) GetMixedInstancesPolicy() *MixedInstancesPolicySpec {
 	return c.MixedInstancesPolicy
 }
+func (c *EKSConfiguration) GetMetadataOptions() *MetadataOptions {
+	return c.MetadataOptions
+}
 func (c *EKSConfiguration) GetPlacement() *PlacementSpec {
 	return c.Placement
 }
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 5ec42fe..90f591c 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -177,6 +177,11 @@ func (in *EKSConfiguration) DeepCopyInto(out *EKSConfiguration) {
 		*out = new(PlacementSpec)
 		**out = **in
 	}
+	if in.MetadataOptions != nil {
+		in, out := &in.MetadataOptions, &out.MetadataOptions
+		*out = new(MetadataOptions)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EKSConfiguration.
@@ -496,6 +501,21 @@ func (in *LifecycleHookSpec) DeepCopy() *LifecycleHookSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *MetadataOptions) DeepCopyInto(out *MetadataOptions) {
+	*out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetadataOptions.
+func (in *MetadataOptions) DeepCopy() *MetadataOptions {
+	if in == nil {
+		return nil
+	}
+	out := new(MetadataOptions)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *MixedInstancesPolicySpec) DeepCopyInto(out *MixedInstancesPolicySpec) {
 	*out = *in
diff --git a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
index cea433d..3021d8e 100644
--- a/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
+++ b/config/crd/bases/instancemgr.keikoproj.io_instancegroups.yaml
@@ -124,6 +124,16 @@ spec:
                         items:
                           type: string
                         type: array
+                      metadataOptions:
+                        properties:
+                          httpEndpoint:
+                            type: string
+                          httpPutHopLimit:
+                            format: int64
+                            type: integer
+                          httpTokens:
+                            type: string
+                        type: object
                       metricsCollection:
                         items:
                           type: string
diff --git a/controllers/provisioners/eks/create.go b/controllers/provisioners/eks/create.go
index c426209..e839215 100644
--- a/controllers/provisioners/eks/create.go
+++ b/controllers/provisioners/eks/create.go
@@ -44,6 +44,7 @@ func (ctx *EksInstanceGroupContext) Create() error {
 		sgs             = ctx.ResolveSecurityGroups()
 		spotPrice       = configuration.GetSpotPrice()
 		placement       = configuration.GetPlacement()
+		metadataOptions = configuration.GetMetadataOptions()
 	)
 
 	ctx.SetState(v1alpha1.ReconcileModifying)
@@ -73,6 +74,7 @@ func (ctx *EksInstanceGroupContext) Create() error {
 		SpotPrice:             spotPrice,
 		LicenseSpecifications: configuration.LicenseSpecifications,
 		Placement:             placement,
+		MetadataOptions:       metadataOptions,
 	}
 
 	if err := scalingConfig.Create(config); err != nil {
diff --git a/controllers/provisioners/eks/scaling/interface.go b/controllers/provisioners/eks/scaling/interface.go
index 0978382..7beb32c 100644
--- a/controllers/provisioners/eks/scaling/interface.go
+++ b/controllers/provisioners/eks/scaling/interface.go
@@ -61,6 +61,7 @@ type CreateConfigurationInput struct {
 	SpotPrice             string
 	LicenseSpecifications []string
 	Placement             *v1alpha1.PlacementSpec
+	MetadataOptions       *v1alpha1.MetadataOptions
 }
 
 func ConvertToLaunchTemplate(resource interface{}) *ec2.LaunchTemplate {
diff --git a/controllers/provisioners/eks/scaling/launchconfig.go b/controllers/provisioners/eks/scaling/launchconfig.go
index 5989769..186348f 100644
--- a/controllers/provisioners/eks/scaling/launchconfig.go
+++ b/controllers/provisioners/eks/scaling/launchconfig.go
@@ -89,6 +89,7 @@ func (lc *LaunchConfiguration) Create(input *CreateConfigurationInput) error {
 		SecurityGroups:          aws.StringSlice(input.SecurityGroups),
 		UserData:                aws.String(input.UserData),
 		BlockDeviceMappings:     devices,
+		MetadataOptions:         lc.metadataOptions(input.MetadataOptions),
 	}
 
 	if !common.StringEmpty(input.SpotPrice) {
@@ -231,6 +232,16 @@ func (lc *LaunchConfiguration) Drifted(input *CreateConfigurationInput) bool {
 		drift = true
 	}
 
+	metadataOptions := lc.metadataOptions(input.MetadataOptions)
+
+	if !reflect.DeepEqual(metadataOptions, existingConfig.MetadataOptions) {
+		log.Info("detected drift", "reason", "metadata options have changed", "instancegroup", lc.OwnerName,
+			"previousValue", existingConfig.MetadataOptions,
+			"newValue", metadataOptions,
+		)
+		drift = true
+	}
+
 	if !drift {
 		log.Info("drift not detected", "instancegroup", lc.OwnerName)
 	}
@@ -282,6 +293,17 @@ func (lc *LaunchConfiguration) blockDeviceList(volumes []v1alpha1.NodeVolume) []
 	return sortConfigDevices(devices)
 }
 
+func (lc *LaunchConfiguration) metadataOptions(input *v1alpha1.MetadataOptions) *autoscaling.InstanceMetadataOptions {
+	if input == nil {
+		return nil
+	}
+	return &autoscaling.InstanceMetadataOptions{
+		HttpEndpoint:            aws.String(input.HttpEndpoint),
+		HttpPutResponseHopLimit: aws.Int64(input.HttpPutHopLimit),
+		HttpTokens:              aws.String(input.HttpTokens),
+	}
+}
+
 func getPrefixedConfigurations(configs []*autoscaling.LaunchConfiguration, prefix string) []*autoscaling.LaunchConfiguration {
 	prefixed := []*autoscaling.LaunchConfiguration{}
 	for _, lc := range configs {
diff --git a/controllers/provisioners/eks/scaling/launchtemplate.go b/controllers/provisioners/eks/scaling/launchtemplate.go
index 0233c86..6a1d269 100644
--- a/controllers/provisioners/eks/scaling/launchtemplate.go
+++ b/controllers/provisioners/eks/scaling/launchtemplate.go
@@ -102,6 +102,7 @@ func (lt *LaunchTemplate) Create(input *CreateConfigurationInput) error {
 		BlockDeviceMappings:   lt.blockDeviceListRequest(input.Volumes),
 		LicenseSpecifications: lt.LaunchTemplateLicenseConfigurationRequest(input.LicenseSpecifications),
 		Placement:             lt.launchTemplatePlacementRequest(input.Placement),
+		MetadataOptions:       lt.metadataOptionsRequest(input.MetadataOptions),
 	}
 
 	if !lt.Provisioned() {
@@ -270,6 +271,16 @@ func (lt *LaunchTemplate) Drifted(input *CreateConfigurationInput) bool {
 		drift = true
 	}
 
+	metadataOptions := lt.metadataOptions(input.MetadataOptions)
+
+	if !reflect.DeepEqual(metadataOptions, latestVersion.LaunchTemplateData.MetadataOptions) {
+		log.Info("detected drift", "reason", "metadata options have changed", "instancegroup", lt.OwnerName,
+			"previousValue", latestVersion.LaunchTemplateData.MetadataOptions,
+			"newValue", metadataOptions,
+		)
+		drift = true
+	}
+
 	if !drift {
 		log.Info("drift not detected", "instancegroup", lt.OwnerName)
 	}
@@ -345,6 +356,28 @@ func (lt *LaunchTemplate) launchTemplatePlacementRequest(input *v1alpha1.Placeme
 	return lt.LaunchTemplatePlacementRequest(input.AvailabilityZone, input.HostResourceGroupArn, input.Tenancy)
 }
 
+func (lt *LaunchTemplate) metadataOptions(input *v1alpha1.MetadataOptions) *ec2.LaunchTemplateInstanceMetadataOptions {
+	if input == nil {
+		return nil
+	}
+	return &ec2.LaunchTemplateInstanceMetadataOptions{
+		HttpEndpoint:            aws.String(input.HttpEndpoint),
+		HttpPutResponseHopLimit: aws.Int64(input.HttpPutHopLimit),
+		HttpTokens:              aws.String(input.HttpTokens),
+	}
+}
+
+func (lt *LaunchTemplate) metadataOptionsRequest(input *v1alpha1.MetadataOptions) *ec2.LaunchTemplateInstanceMetadataOptionsRequest {
+	if input == nil {
+		return nil
+	}
+	return &ec2.LaunchTemplateInstanceMetadataOptionsRequest{
+		HttpEndpoint:            aws.String(input.HttpEndpoint),
+		HttpPutResponseHopLimit: aws.Int64(input.HttpPutHopLimit),
+		HttpTokens:              aws.String(input.HttpTokens),
+	}
+}
+
 func (lt *LaunchTemplate) launchTemplatePlacement(input *v1alpha1.PlacementSpec) *ec2.LaunchTemplatePlacement {
 	if input == nil {
 		return &ec2.LaunchTemplatePlacement{}
diff --git a/controllers/provisioners/eks/update.go b/controllers/provisioners/eks/update.go
index 3c9dabe..17512bf 100644
--- a/controllers/provisioners/eks/update.go
+++ b/controllers/provisioners/eks/update.go
@@ -52,6 +52,7 @@ func (ctx *EksInstanceGroupContext) Update() error {
 		sgs             = ctx.ResolveSecurityGroups()
 		spotPrice       = configuration.GetSpotPrice()
 		placement       = configuration.GetPlacement()
+		metadataOptions = configuration.GetMetadataOptions()
 	)
 
 	ctx.SetState(v1alpha1.ReconcileModifying)
@@ -75,6 +76,7 @@ func (ctx *EksInstanceGroupContext) Update() error {
 		SpotPrice:             spotPrice,
 		LicenseSpecifications: configuration.LicenseSpecifications,
 		Placement:             placement,
+		MetadataOptions:       metadataOptions,
 	}
 
 	// create new launchconfig if it has drifted


Test patch:
diff --git a/api/v1alpha1/instancegroup_types_test.go b/api/v1alpha1/instancegroup_types_test.go
index c3188d1..3160dce 100644
--- a/api/v1alpha1/instancegroup_types_test.go
+++ b/api/v1alpha1/instancegroup_types_test.go
@@ -231,6 +231,35 @@ func TestInstanceGroupSpecValidate(t *testing.T) {
 			},
 			want: "",
 		},
+		{
+			name: "eks with metadataoptions validates",
+			args: args{
+				instancegroup: MockInstanceGroup("eks", "rollingUpdate", &EKSSpec{
+					MaxSize: 1,
+					MinSize: 1,
+					Type:    "LaunchTemplate",
+					EKSConfiguration: &EKSConfiguration{
+						EksClusterName:     "my-eks-cluster",
+						NodeSecurityGroups: []string{"sg-123456789"},
+						Image:              "ami-12345",
+						InstanceType:       "m5.large",
+						KeyPairName:        "thisShouldBeOptional",
+						Subnets:            []string{"subnet-1111111", "subnet-222222"},
+						Placement: &PlacementSpec{
+							AvailabilityZone:     "us-west-2a",
+							HostResourceGroupArn: "arn:aws:resource-groups:us-west-2:1122334455:group/resourceName",
+							Tenancy:              "host",
+						},
+						MetadataOptions: &MetadataOptions{
+							HttpEndpoint:    "enabled",
+							HttpTokens:      "required",
+							HttpPutHopLimit: 1,
+						},
+					},
+				}, nil, nil),
+			},
+			want: "",
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/controllers/provisioners/eks/scaling/launchconfig_test.go b/controllers/provisioners/eks/scaling/launchconfig_test.go
index 6ba2e81..30690c4 100644
--- a/controllers/provisioners/eks/scaling/launchconfig_test.go
+++ b/controllers/provisioners/eks/scaling/launchconfig_test.go
@@ -475,6 +475,16 @@ func TestLaunchConfigurationDrifted(t *testing.T) {
 			},
 			shouldDrift: true,
 		},
+		{
+			launchConfig: &autoscaling.LaunchConfiguration{
+				LaunchConfigurationName: aws.String("my-launch-config"),
+			},
+			input: &CreateConfigurationInput{
+				SecurityGroups: []string{},
+				MetadataOptions: &v1alpha1.MetadataOptions{HttpEndpoint: "enabled"},
+			},
+			shouldDrift: true,
+		},
 	}
 
 	for i, tc := range tests {
diff --git a/controllers/provisioners/eks/scaling/launchtemplate_test.go b/controllers/provisioners/eks/scaling/launchtemplate_test.go
index 235d2eb..ce0a894 100644
--- a/controllers/provisioners/eks/scaling/launchtemplate_test.go
+++ b/controllers/provisioners/eks/scaling/launchtemplate_test.go
@@ -612,6 +612,16 @@ func TestLaunchTemplateDrifted(t *testing.T) {
 			},
 			shouldDrift: true,
 		},
+		{
+			launchTemplate: MockLaunchTemplate("my-launch-template"),
+			latestVersion:  MockLaunchTemplateVersion(),
+			input: &CreateConfigurationInput{
+				MetadataOptions: &v1alpha1.MetadataOptions{
+					HttpEndpoint: "Enabled",
+				},
+			},
+			shouldDrift: true,
+		},
 	}
 
 	for i, tc := range tests {