Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New resource: aws_ec2_instance_metadata_defaults #36589

Merged
3 changes: 3 additions & 0 deletions .changelog/36589.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_ec2_instance_metadata_defaults
```
210 changes: 210 additions & 0 deletions internal/service/ec2/ec2_instance_metadata_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ec2

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

const (
httpPutResponseHopLimitNoPreference = -1
)

// @FrameworkResource(name="Instance Metadata Defaults")
func newInstanceMetadataDefaultsResource(_ context.Context) (resource.ResourceWithConfigure, error) {
r := &instanceMetadataDefaultsResource{}

return r, nil
}

type instanceMetadataDefaultsResource struct {
framework.ResourceWithConfigure
framework.WithImportByID
}

func (*instanceMetadataDefaultsResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) {
response.TypeName = "aws_ec2_instance_metadata_defaults"
}

func (r *instanceMetadataDefaultsResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) {
httpEndpointType := fwtypes.StringEnumType[awstypes.DefaultInstanceMetadataEndpointState]()
httpTokensType := fwtypes.StringEnumType[awstypes.MetadataDefaultHttpTokensState]()
instanceMetadataTagsType := fwtypes.StringEnumType[awstypes.DefaultInstanceMetadataTagsState]()

response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"http_endpoint": schema.StringAttribute{
CustomType: httpEndpointType,
Optional: true,
Computed: true,
Default: httpEndpointType.AttributeDefault(awstypes.DefaultInstanceMetadataEndpointStateNoPreference),
},
"http_put_response_hop_limit": schema.Int64Attribute{
Optional: true,
Computed: true,
Default: int64default.StaticInt64(httpPutResponseHopLimitNoPreference),
Validators: []validator.Int64{
int64validator.Between(-1, 64),
},
},
"http_tokens": schema.StringAttribute{
CustomType: httpTokensType,
Optional: true,
Computed: true,
Default: httpTokensType.AttributeDefault(awstypes.MetadataDefaultHttpTokensStateNoPreference),
},
names.AttrID: framework.IDAttribute(),
"instance_metadata_tags": schema.StringAttribute{
CustomType: instanceMetadataTagsType,
Optional: true,
Computed: true,
Default: instanceMetadataTagsType.AttributeDefault(awstypes.DefaultInstanceMetadataTagsStateNoPreference),
},
},
}
}

func (r *instanceMetadataDefaultsResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) {
var data instanceMetadataDefaultsModel
response.Diagnostics.Append(request.Plan.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().EC2Client(ctx)

input := &ec2.ModifyInstanceMetadataDefaultsInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...)
if response.Diagnostics.HasError() {
return
}

_, err := conn.ModifyInstanceMetadataDefaults(ctx, input)

if err != nil {
response.Diagnostics.AddError("creating EC2 Instance Metadata Defaults", err.Error())

return
}

response.Diagnostics.Append(response.State.Set(ctx, data)...)
}

func (r *instanceMetadataDefaultsResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) {
var data instanceMetadataDefaultsModel
response.Diagnostics.Append(request.State.Get(ctx, &data)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().EC2Client(ctx)

output, err := findInstanceMetadataDefaults(ctx, conn)

if tfresource.NotFound(err) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetInstanceMetadataDefaults always return something (even if no defaults were explicitely set through ModifyInstanceMetadataDefaults), so I think this check is not necessary FWIW

response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err))
response.State.RemoveResource(ctx)

return
}

if err != nil {
response.Diagnostics.AddError("reading EC2 Instance Metadata Defaults", err.Error())

return
}

// Set attributes for import.
response.Diagnostics.Append(fwflex.Flatten(ctx, output, &data)...)
if response.Diagnostics.HasError() {
return
}

response.Diagnostics.Append(response.State.Set(ctx, &data)...)
}

// Update is very similar to Create as AWS has a single API call ModifyInstanceMetadataDefaults
func (r *instanceMetadataDefaultsResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) {
var new instanceMetadataDefaultsModel
response.Diagnostics.Append(request.Plan.Get(ctx, &new)...)
if response.Diagnostics.HasError() {
return
}

conn := r.Meta().EC2Client(ctx)

input := &ec2.ModifyInstanceMetadataDefaultsInput{}
response.Diagnostics.Append(fwflex.Expand(ctx, new, input)...)
if response.Diagnostics.HasError() {
return
}

_, err := conn.ModifyInstanceMetadataDefaults(ctx, input)

if err != nil {
response.Diagnostics.AddError("updating EC2 Instance Metadata Defaults", err.Error())

return
}

response.Diagnostics.Append(response.State.Set(ctx, &new)...)
}

func (r *instanceMetadataDefaultsResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) {
conn := r.Meta().EC2Client(ctx)

input := &ec2.ModifyInstanceMetadataDefaultsInput{
HttpEndpoint: awstypes.DefaultInstanceMetadataEndpointStateNoPreference,
HttpPutResponseHopLimit: aws.Int32(httpPutResponseHopLimitNoPreference),
HttpTokens: awstypes.MetadataDefaultHttpTokensStateNoPreference,
InstanceMetadataTags: awstypes.DefaultInstanceMetadataTagsStateNoPreference,
}

_, err := conn.ModifyInstanceMetadataDefaults(ctx, input)

if err != nil {
response.Diagnostics.AddError("deleting EC2 Instance Metadata Defaults", err.Error())

return
}
}

func findInstanceMetadataDefaults(ctx context.Context, conn *ec2.Client) (*awstypes.InstanceMetadataDefaultsResponse, error) {
input := &ec2.GetInstanceMetadataDefaultsInput{}

output, err := conn.GetInstanceMetadataDefaults(ctx, &ec2.GetInstanceMetadataDefaultsInput{})

if err != nil {
return nil, err
}

if output == nil || output.AccountLevel == nil {
return nil, tfresource.NewEmptyResultError(input)
}

return output.AccountLevel, nil
}

type instanceMetadataDefaultsModel struct {
HttpEndpoint types.String `tfsdk:"http_endpoint"`
HttpPutResponseHopLimit types.Int64 `tfsdk:"http_put_response_hop_limit"`
HttpTokens types.String `tfsdk:"http_tokens"`
InstanceMetadataTags types.String `tfsdk:"instance_metadata_tags"`
}
154 changes: 154 additions & 0 deletions internal/service/ec2/ec2_instance_metadata_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ec2_test

import (
"context"
"fmt"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
awstypes "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tfec2 "github.com/hashicorp/terraform-provider-aws/internal/service/ec2"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
"github.com/hashicorp/terraform-provider-aws/names"
)

func TestAccEC2InstanceMetadataDefaults_serial(t *testing.T) {
t.Parallel()

testCases := map[string]func(t *testing.T){
"basic": testAccInstanceMetadataDefaults_basic,
"update": testAccInstanceMetadataDefaults_update,
}

acctest.RunSerialTests1Level(t, testCases, 0)
}

func testAccInstanceMetadataDefaults_basic(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_ec2_instance_metadata_defaults.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckInstanceMetadataDefaultsDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccInstanceMetadataDefaultsConfig_basic(),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckInstanceMetadataDefaultsExists(ctx, resourceName, &awstypes.InstanceMetadataDefaultsResponse{
HttpTokens: awstypes.HttpTokensState(awstypes.MetadataDefaultHttpTokensStateRequired),
HttpPutResponseHopLimit: aws.Int32(1),
HttpEndpoint: awstypes.InstanceMetadataEndpointStateEnabled,
InstanceMetadataTags: awstypes.InstanceMetadataTagsStateDisabled,
}),
resource.TestCheckResourceAttr(resourceName, "http_tokens", "required"),
resource.TestCheckResourceAttr(resourceName, "instance_metadata_tags", "disabled"),
resource.TestCheckResourceAttr(resourceName, "http_endpoint", "enabled"),
resource.TestCheckResourceAttr(resourceName, "http_put_response_hop_limit", "1"),
),
},
},
})
}

func testAccInstanceMetadataDefaults_update(t *testing.T) {
ctx := acctest.Context(t)
resourceName := "aws_ec2_instance_metadata_defaults.test"

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.EC2ServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckInstanceMetadataDefaultsDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccInstanceMetadataDefaultsConfig_partial(),
Check: resource.ComposeTestCheckFunc(
testAccCheckInstanceMetadataDefaultsExists(ctx, resourceName, &awstypes.InstanceMetadataDefaultsResponse{
HttpTokens: awstypes.HttpTokensState(awstypes.MetadataDefaultHttpTokensStateRequired),
HttpPutResponseHopLimit: aws.Int32(2),
HttpEndpoint: "",
InstanceMetadataTags: "",
}),
resource.TestCheckResourceAttr(resourceName, "http_tokens", "required"),
resource.TestCheckResourceAttr(resourceName, "http_put_response_hop_limit", "2"),
),
},
},
})
}

func testAccCheckInstanceMetadataDefaultsDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx)

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ec2_instance_metadata_defaults" {
continue
}

_, err := tfec2.FindInstanceMetadataDefaults(ctx, conn)

if tfresource.NotFound(err) {
continue
}

if err != nil {
return err
}

return fmt.Errorf("EC2 Instance Metadata Defaults %s still exists", rs.Primary.ID)
}

return nil
}
}

func testAccCheckInstanceMetadataDefaultsExists(ctx context.Context, n string, v *awstypes.InstanceMetadataDefaultsResponse) resource.TestCheckFunc {
return func(s *terraform.State) error {
_, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Client(ctx)

output, err := tfec2.FindInstanceMetadataDefaults(ctx, conn)

if err != nil {
return err
}

*v = *output

return nil
}
}

func testAccInstanceMetadataDefaultsConfig_basic() string {
return `
resource "aws_ec2_instance_metadata_defaults" "test" {
http_tokens = "required" # non-default
instance_metadata_tags = "disabled"
http_endpoint = "enabled"
http_put_response_hop_limit = 1
}
`
}

func testAccInstanceMetadataDefaultsConfig_partial() string {
return `
resource "aws_ec2_instance_metadata_defaults" "test" {
http_tokens = "required" # non-default
http_put_response_hop_limit = 2 # non-default
}
`
}
2 changes: 2 additions & 0 deletions internal/service/ec2/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
ResourceDefaultRouteTable = resourceDefaultRouteTable
ResourceEBSFastSnapshotRestore = newResourceEBSFastSnapshotRestore
ResourceInstanceConnectEndpoint = newResourceInstanceConnectEndpoint
ResourceInstanceMetadataDefaults = newInstanceMetadataDefaultsResource
ResourceNetworkACL = resourceNetworkACL
ResourceNetworkACLRule = resourceNetworkACLRule
ResourceRoute = resourceRoute
Expand All @@ -26,6 +27,7 @@ var (

CustomFiltersSchema = customFiltersSchema
FindEBSFastSnapshotRestoreByID = findEBSFastSnapshotRestoreByID
FindInstanceMetadataDefaults = findInstanceMetadataDefaults
FindNetworkACLByIDV2 = findNetworkACLByIDV2
NewAttributeFilterList = newAttributeFilterList
NewCustomFilterList = newCustomFilterList
Expand Down
4 changes: 4 additions & 0 deletions internal/service/ec2/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading