diff --git a/.changelog/28144.txt b/.changelog/28144.txt new file mode 100644 index 000000000000..4e6680e1dde4 --- /dev/null +++ b/.changelog/28144.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_resourceexplorer2_index +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 78ea57a6013e..b1fd22c31957 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/hashicorp/go-version v1.6.0 github.com/hashicorp/hcl/v2 v2.15.0 github.com/hashicorp/terraform-plugin-framework v0.17.0 + github.com/hashicorp/terraform-plugin-framework-timeouts v0.2.0 github.com/hashicorp/terraform-plugin-framework-validators v0.7.0 github.com/hashicorp/terraform-plugin-go v0.14.2 github.com/hashicorp/terraform-plugin-log v0.7.0 diff --git a/go.sum b/go.sum index b07ccbf3adf5..55475886d810 100644 --- a/go.sum +++ b/go.sum @@ -208,6 +208,8 @@ github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-plugin-framework v0.17.0 h1:0KUOY/oe1GPLFqaXnKDnd1rhCrnUtt8pV9wGEwNUFlU= github.com/hashicorp/terraform-plugin-framework v0.17.0/go.mod h1:FV97t2BZOARkL7NNlsc/N25c84MyeSSz72uPp7Vq1lg= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.2.0 h1:qo+9WNxAg9UlD0NHoOhi6GJOjQKN6b+JLZJOZ5Nqr8g= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.2.0/go.mod h1:jIV0yknjnzb3Al+DRMtr25BPlQq8LLoy+36bYQduIg8= github.com/hashicorp/terraform-plugin-framework-validators v0.7.0 h1:tIYOMNmEMQIc6mwun8nX3e5U3TkgZg1TpXRlBEBQHwY= github.com/hashicorp/terraform-plugin-framework-validators v0.7.0/go.mod h1:e1RKREyEVdd3FK8Jfgz8L/ThQgcJKLb4ZJxNzsuIH0A= github.com/hashicorp/terraform-plugin-go v0.14.2 h1:rhsVEOGCnY04msNymSvbUsXfRLKh9znXZmHlf5e8mhE= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7b366cbd725a..db9da560ce3b 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -153,6 +153,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/redshift" "github.com/hashicorp/terraform-provider-aws/internal/service/redshiftdata" "github.com/hashicorp/terraform-provider-aws/internal/service/redshiftserverless" + "github.com/hashicorp/terraform-provider-aws/internal/service/resourceexplorer2" "github.com/hashicorp/terraform-provider-aws/internal/service/resourcegroups" "github.com/hashicorp/terraform-provider-aws/internal/service/resourcegroupstaggingapi" "github.com/hashicorp/terraform-provider-aws/internal/service/rolesanywhere" @@ -2256,6 +2257,7 @@ func New(_ context.Context) (*schema.Provider, error) { globalaccelerator.ServicePackageData, medialive.ServicePackageData, meta.ServicePackageData, + resourceexplorer2.ServicePackageData, simpledb.ServicePackageData, sts.ServicePackageData, }, diff --git a/internal/service/ec2/vpc_security_group_ingress_rule.go b/internal/service/ec2/vpc_security_group_ingress_rule.go index 3f562d7d55ff..24df48170c5f 100644 --- a/internal/service/ec2/vpc_security_group_ingress_rule.go +++ b/internal/service/ec2/vpc_security_group_ingress_rule.go @@ -137,12 +137,7 @@ func (r *resourceSecurityGroupRule) Schema(ctx context.Context, req resource.Sch int64validator.Between(-1, 65535), }, }, - "id": schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, + "id": framework.IDAttribute(), "ip_protocol": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ diff --git a/internal/service/resourceexplorer2/exports_test.go b/internal/service/resourceexplorer2/exports_test.go new file mode 100644 index 000000000000..344e59d90e94 --- /dev/null +++ b/internal/service/resourceexplorer2/exports_test.go @@ -0,0 +1,7 @@ +package resourceexplorer2 + +// Exports for use in tests only. +var ( + FindIndex = findIndex + ResourceIndex = newResourceIndex +) diff --git a/internal/service/resourceexplorer2/generate.go b/internal/service/resourceexplorer2/generate.go new file mode 100644 index 000000000000..3be59faea2d4 --- /dev/null +++ b/internal/service/resourceexplorer2/generate.go @@ -0,0 +1,5 @@ +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -TagInIDElem=ResourceArn -ListTags -ListTagsInIDElem=ResourceArn -ServiceTagsMap -UpdateTags -UntagInTagsElem=TagKeys -KVTValues -SkipTypesImp +//go:generate go run ../../generate/servicepackagedata/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package resourceexplorer2 diff --git a/internal/service/resourceexplorer2/index.go b/internal/service/resourceexplorer2/index.go new file mode 100644 index 000000000000..e7e3b9e3963f --- /dev/null +++ b/internal/service/resourceexplorer2/index.go @@ -0,0 +1,388 @@ +package resourceexplorer2 + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/resourceexplorer2" + awstypes "github.com/aws/aws-sdk-go-v2/service/resourceexplorer2/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/timeouts" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + sdkresource "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" +) + +func init() { + registerFrameworkResourceFactory(newResourceIndex) +} + +func newResourceIndex(context.Context) (resource.ResourceWithConfigure, error) { + return &resourceIndex{ + defaultCreateTimeout: 2 * time.Hour, + defaultUpdateTimeout: 2 * time.Hour, + defaultDeleteTimeout: 10 * time.Minute, + }, nil +} + +type resourceIndex struct { + framework.ResourceWithConfigure + + defaultCreateTimeout time.Duration + defaultUpdateTimeout time.Duration + defaultDeleteTimeout time.Duration +} + +func (r *resourceIndex) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_resourceexplorer2_index" +} + +func (r *resourceIndex) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "id": framework.IDAttribute(), + "tags": tftags.TagsAttribute(), + "tags_all": tftags.TagsAttributeComputedOnly(), + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + enum.FrameworkValidate[awstypes.IndexType](), + }, + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *resourceIndex) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data resourceIndexData + + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ResourceExplorer2Client + createTimeout := timeouts.Create(ctx, data.Timeouts, r.defaultCreateTimeout) + defaultTagsConfig := r.Meta().DefaultTagsConfig + ignoreTagsConfig := r.Meta().IgnoreTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(data.Tags)) + + input := &resourceexplorer2.CreateIndexInput{ + ClientToken: aws.String(sdkresource.UniqueId()), + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) + } + + output, err := conn.CreateIndex(ctx, input) + + if err != nil { + response.Diagnostics.AddError("creating Resource Explorer Index", err.Error()) + + return + } + + arn := aws.ToString(output.Arn) + data.ID = types.StringValue(arn) + + if _, err := waitIndexCreated(ctx, conn, createTimeout); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Resource Explorer Index (%s) create", data.ID.ValueString()), err.Error()) + + return + } + + if data.Type.ValueString() == string(awstypes.IndexTypeAggregator) { + input := &resourceexplorer2.UpdateIndexTypeInput{ + Arn: flex.StringFromFramework(ctx, data.ID), + Type: awstypes.IndexTypeAggregator, + } + + _, err := conn.UpdateIndexType(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Resource Explorer Index (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitIndexUpdated(ctx, conn, createTimeout); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Resource Explorer Index (%s) update", data.ID.ValueString()), err.Error()) + + return + } + } + + // Set values for unknowns. + data.ARN = types.StringValue(arn) + data.TagsAll = flex.FlattenFrameworkStringValueMap(ctx, tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *resourceIndex) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data resourceIndexData + + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ResourceExplorer2Client + defaultTagsConfig := r.Meta().DefaultTagsConfig + ignoreTagsConfig := r.Meta().IgnoreTagsConfig + + output, err := findIndex(ctx, conn) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading Resource Explorer Index (%s)", data.ID.ValueString()), err.Error()) + + return + } + + data.ARN = flex.StringToFramework(ctx, output.Arn) + data.Type = types.StringValue(string(output.Type)) + + tags := KeyValueTags(output.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + // AWS APIs often return empty lists of tags when none have been configured. + if tags := tags.RemoveDefaultConfig(defaultTagsConfig).Map(); len(tags) == 0 { + data.Tags = tftags.Null + } else { + data.Tags = flex.FlattenFrameworkStringValueMap(ctx, tags) + } + data.TagsAll = flex.FlattenFrameworkStringValueMap(ctx, tags.Map()) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *resourceIndex) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new resourceIndexData + + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ResourceExplorer2Client + updateTimeout := timeouts.Update(ctx, new.Timeouts, r.defaultUpdateTimeout) + + if !new.Type.Equal(old.Type) { + input := &resourceexplorer2.UpdateIndexTypeInput{ + Arn: flex.StringFromFramework(ctx, new.ID), + Type: awstypes.IndexType(new.Type.ValueString()), + } + + _, err := conn.UpdateIndexType(ctx, input) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Resource Explorer Index (%s)", new.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitIndexUpdated(ctx, conn, updateTimeout); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Resource Explorer Index (%s) update", new.ID.ValueString()), err.Error()) + + return + } + } + + if !new.TagsAll.Equal(old.TagsAll) { + if err := UpdateTags(ctx, conn, new.ID.ValueString(), old.TagsAll, new.TagsAll); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating Resource Explorer Index (%s) tags", new.ID.ValueString()), err.Error()) + + return + } + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *resourceIndex) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data resourceIndexData + + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ResourceExplorer2Client + deleteTimeout := timeouts.Delete(ctx, data.Timeouts, r.defaultDeleteTimeout) + + tflog.Debug(ctx, "deleting Resource Explorer Index", map[string]interface{}{ + "id": data.ID.ValueString(), + }) + _, err := conn.DeleteIndex(ctx, &resourceexplorer2.DeleteIndexInput{ + Arn: flex.StringFromFramework(ctx, data.ID), + }) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting Resource Explorer Index (%s)", data.ID.ValueString()), err.Error()) + + return + } + + if _, err := waitIndexDeleted(ctx, conn, deleteTimeout); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Resource Explorer Index (%s) delete", data.ID.ValueString()), err.Error()) + + return + } +} + +func (r *resourceIndex) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), request, response) +} + +func (r *resourceIndex) ModifyPlan(ctx context.Context, request resource.ModifyPlanRequest, response *resource.ModifyPlanResponse) { + r.SetTagsAll(ctx, request, response) +} + +type resourceIndexData struct { + ARN types.String `tfsdk:"arn"` + ID types.String `tfsdk:"id"` + Tags types.Map `tfsdk:"tags"` + TagsAll types.Map `tfsdk:"tags_all"` + Timeouts types.Object `tfsdk:"timeouts"` + Type types.String `tfsdk:"type"` +} + +func findIndex(ctx context.Context, conn *resourceexplorer2.Client) (*resourceexplorer2.GetIndexOutput, error) { + input := &resourceexplorer2.GetIndexInput{} + + output, err := conn.GetIndex(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &sdkresource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if state := output.State; state == awstypes.IndexStateDeleted { + return nil, &sdkresource.NotFoundError{ + Message: string(state), + LastRequest: input, + } + } + + return output, nil +} + +func statusIndex(ctx context.Context, conn *resourceexplorer2.Client) sdkresource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findIndex(ctx, conn) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.State), nil + } +} + +func waitIndexCreated(ctx context.Context, conn *resourceexplorer2.Client, timeout time.Duration) (*resourceexplorer2.GetIndexOutput, error) { + stateConf := &sdkresource.StateChangeConf{ + Pending: enum.Slice(awstypes.IndexStateCreating), + Target: enum.Slice(awstypes.IndexStateActive), + Refresh: statusIndex(ctx, conn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*resourceexplorer2.GetIndexOutput); ok { + return output, err + } + + return nil, err +} + +func waitIndexUpdated(ctx context.Context, conn *resourceexplorer2.Client, timeout time.Duration) (*resourceexplorer2.GetIndexOutput, error) { //nolint:unparam + stateConf := &sdkresource.StateChangeConf{ + Pending: enum.Slice(awstypes.IndexStateUpdating), + Target: enum.Slice(awstypes.IndexStateActive), + Refresh: statusIndex(ctx, conn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*resourceexplorer2.GetIndexOutput); ok { + return output, err + } + + return nil, err +} + +func waitIndexDeleted(ctx context.Context, conn *resourceexplorer2.Client, timeout time.Duration) (*resourceexplorer2.GetIndexOutput, error) { + stateConf := &sdkresource.StateChangeConf{ + Pending: enum.Slice(awstypes.IndexStateDeleting), + Target: []string{}, + Refresh: statusIndex(ctx, conn), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*resourceexplorer2.GetIndexOutput); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/resourceexplorer2/index_test.go b/internal/service/resourceexplorer2/index_test.go new file mode 100644 index 000000000000..70bbcb34689b --- /dev/null +++ b/internal/service/resourceexplorer2/index_test.go @@ -0,0 +1,215 @@ +package resourceexplorer2_test + +import ( + "context" + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfresourceexplorer2 "github.com/hashicorp/terraform-provider-aws/internal/service/resourceexplorer2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccIndex_basic(t *testing.T) { + resourceName := "aws_resourceexplorer2_index.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(names.ResourceExplorer2EndpointID, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ResourceExplorer2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIndexConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "resource-explorer-2", regexp.MustCompile(`index/+.`)), + resource.TestCheckResourceAttr(resourceName, "type", "LOCAL"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccIndex_disappears(t *testing.T) { + resourceName := "aws_resourceexplorer2_index.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(names.ResourceExplorer2EndpointID, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ResourceExplorer2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIndexConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + acctest.CheckFrameworkResourceDisappears(acctest.Provider, tfresourceexplorer2.ResourceIndex, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccIndex_tags(t *testing.T) { + resourceName := "aws_resourceexplorer2_index.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(names.ResourceExplorer2EndpointID, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ResourceExplorer2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIndexConfig_tags1("key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIndexConfig_tags2("key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccIndexConfig_tags1("key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccIndex_type(t *testing.T) { + resourceName := "aws_resourceexplorer2_index.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(names.ResourceExplorer2EndpointID, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ResourceExplorer2EndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckIndexDestroy, + Steps: []resource.TestStep{ + { + Config: testAccIndexConfig_type("AGGREGATOR"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "type", "AGGREGATOR"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccIndexConfig_type("LOCAL"), + Check: resource.ComposeTestCheckFunc( + testAccCheckIndexExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "type", "LOCAL"), + ), + }, + }, + }) +} + +func testAccCheckIndexDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ResourceExplorer2Client + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_resourceexplorer2_index" { + continue + } + + _, err := tfresourceexplorer2.FindIndex(context.Background(), conn) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("Resource Explorer Index %s still exists", rs.Primary.ID) + } + + return nil +} + +func testAccCheckIndexExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Resource Explorer Index ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ResourceExplorer2Client + + _, err := tfresourceexplorer2.FindIndex(context.Background(), conn) + + return err + } +} + +var testAccIndexConfig_basic = testAccIndexConfig_type("LOCAL") + +func testAccIndexConfig_tags1(tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_resourceexplorer2_index" "test" { + type = "LOCAL" + + tags = { + %[1]q = %[2]q + } +} +`, tagKey1, tagValue1) +} + +func testAccIndexConfig_tags2(tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_resourceexplorer2_index" "test" { + type = "LOCAL" + + tags = { + %[1]q = %[2]q + %[3]q = %[4]q + } +} +`, tagKey1, tagValue1, tagKey2, tagValue2) +} + +func testAccIndexConfig_type(typ string) string { + return fmt.Sprintf(` +resource "aws_resourceexplorer2_index" "test" { + type = %[1]q +} +`, typ) +} diff --git a/internal/service/resourceexplorer2/resourceexplorer2_test.go b/internal/service/resourceexplorer2/resourceexplorer2_test.go new file mode 100644 index 000000000000..fbd08cc17ee3 --- /dev/null +++ b/internal/service/resourceexplorer2/resourceexplorer2_test.go @@ -0,0 +1,28 @@ +package resourceexplorer2_test + +import ( + "testing" +) + +func TestAccResourceExplorer2_serial(t *testing.T) { + testCases := map[string]map[string]func(t *testing.T){ + "Index": { + "basic": testAccIndex_basic, + "disappears": testAccIndex_disappears, + "tags": testAccIndex_tags, + "type": testAccIndex_type, + }, + } + + for group, m := range testCases { + m := m + t.Run(group, func(t *testing.T) { + for name, tc := range m { + tc := tc + t.Run(name, func(t *testing.T) { + tc(t) + }) + } + }) + } +} diff --git a/internal/service/resourceexplorer2/service_package_data_gen.go b/internal/service/resourceexplorer2/service_package_data_gen.go new file mode 100644 index 000000000000..49c7ea3dc657 --- /dev/null +++ b/internal/service/resourceexplorer2/service_package_data_gen.go @@ -0,0 +1,44 @@ +// Code generated by internal/generate/servicepackagedata/main.go; DO NOT EDIT. + +package resourceexplorer2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-provider-aws/internal/experimental/intf" +) + +var spd = &servicePackageData{} + +func registerFrameworkDataSourceFactory(factory func(context.Context) (datasource.DataSourceWithConfigure, error)) { + spd.frameworkDataSourceFactories = append(spd.frameworkDataSourceFactories, factory) +} + +func registerFrameworkResourceFactory(factory func(context.Context) (resource.ResourceWithConfigure, error)) { + spd.frameworkResourceFactories = append(spd.frameworkResourceFactories, factory) +} + +type servicePackageData struct { + frameworkDataSourceFactories []func(context.Context) (datasource.DataSourceWithConfigure, error) + frameworkResourceFactories []func(context.Context) (resource.ResourceWithConfigure, error) +} + +func (d *servicePackageData) Configure(ctx context.Context, meta any) error { + return nil +} + +func (d *servicePackageData) FrameworkDataSources(ctx context.Context) []func(context.Context) (datasource.DataSourceWithConfigure, error) { + return d.frameworkDataSourceFactories +} + +func (d *servicePackageData) FrameworkResources(ctx context.Context) []func(context.Context) (resource.ResourceWithConfigure, error) { + return d.frameworkResourceFactories +} + +func (d *servicePackageData) ServicePackageName() string { + return "resourceexplorer2" +} + +var ServicePackageData intf.ServicePackageData = spd diff --git a/internal/service/resourceexplorer2/tags_gen.go b/internal/service/resourceexplorer2/tags_gen.go new file mode 100644 index 000000000000..3d095a61837b --- /dev/null +++ b/internal/service/resourceexplorer2/tags_gen.go @@ -0,0 +1,76 @@ +// Code generated by internal/generate/tags/main.go; DO NOT EDIT. +package resourceexplorer2 + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/resourceexplorer2" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" +) + +// ListTags lists resourceexplorer2 service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func ListTags(ctx context.Context, conn *resourceexplorer2.Client, identifier string) (tftags.KeyValueTags, error) { + input := &resourceexplorer2.ListTagsForResourceInput{ + ResourceArn: aws.String(identifier), + } + + output, err := conn.ListTagsForResource(ctx, input) + + if err != nil { + return tftags.New(nil), err + } + + return KeyValueTags(output.Tags), nil +} + +// map[string]string handling + +// Tags returns resourceexplorer2 service tags. +func Tags(tags tftags.KeyValueTags) map[string]string { + return tags.Map() +} + +// KeyValueTags creates KeyValueTags from resourceexplorer2 service tags. +func KeyValueTags(tags map[string]string) tftags.KeyValueTags { + return tftags.New(tags) +} + +// UpdateTags updates resourceexplorer2 service tags. +// The identifier is typically the Amazon Resource Name (ARN), although +// it may also be a different identifier depending on the service. +func UpdateTags(ctx context.Context, conn *resourceexplorer2.Client, identifier string, oldTagsMap interface{}, newTagsMap interface{}) error { + oldTags := tftags.New(oldTagsMap) + newTags := tftags.New(newTagsMap) + + if removedTags := oldTags.Removed(newTags); len(removedTags) > 0 { + input := &resourceexplorer2.UntagResourceInput{ + ResourceArn: aws.String(identifier), + TagKeys: removedTags.IgnoreAWS().Keys(), + } + + _, err := conn.UntagResource(ctx, input) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &resourceexplorer2.TagResourceInput{ + ResourceArn: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResource(ctx, input) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} diff --git a/internal/service/simpledb/domain_test.go b/internal/service/simpledb/domain_test.go index e4647235c3d3..d1c4238ebff8 100644 --- a/internal/service/simpledb/domain_test.go +++ b/internal/service/simpledb/domain_test.go @@ -55,7 +55,6 @@ func TestAccSimpleDBDomain_disappears(t *testing.T) { Config: testAccDomainConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckDomainExists(resourceName), - resource.TestCheckResourceAttr(resourceName, "name", rName), acctest.CheckFrameworkResourceDisappears(acctest.Provider, tfsimpledb.ResourceDomain, resourceName), ), ExpectNonEmptyPlan: true, diff --git a/names/names.go b/names/names.go index 99b511565f13..30f584019e0a 100644 --- a/names/names.go +++ b/names/names.go @@ -23,20 +23,21 @@ import ( // This "should" be defined by the AWS Go SDK v2, but currently isn't. const ( - CloudWatchLogsEndpointID = "logs" - ComprehendEndpointID = "comprehend" - ComputeOptimizerEndpointID = "computeoptimizer" - IdentityStoreEndpointID = "identitystore" - Inspector2EndpointID = "inspector2" - IVSChatEndpointID = "ivschat" - KendraEndpointID = "kendra" - MediaLiveEndpointID = "medialive" - RolesAnywhereEndpointID = "rolesanywhere" - Route53DomainsEndpointID = "route53domains" - SchedulerEndpointID = "scheduler" - SESV2EndpointID = "sesv2" - SSMEndpointID = "ssm" - TranscribeEndpointID = "transcribe" + CloudWatchLogsEndpointID = "logs" + ComprehendEndpointID = "comprehend" + ComputeOptimizerEndpointID = "computeoptimizer" + IdentityStoreEndpointID = "identitystore" + Inspector2EndpointID = "inspector2" + IVSChatEndpointID = "ivschat" + KendraEndpointID = "kendra" + MediaLiveEndpointID = "medialive" + ResourceExplorer2EndpointID = "resource-explorer-2" + RolesAnywhereEndpointID = "rolesanywhere" + Route53DomainsEndpointID = "route53domains" + SchedulerEndpointID = "scheduler" + SESV2EndpointID = "sesv2" + SSMEndpointID = "ssm" + TranscribeEndpointID = "transcribe" ) // Type ServiceDatum corresponds closely to columns in `names_data.csv` and are diff --git a/website/docs/r/resourceexplorer2_index.html.markdown b/website/docs/r/resourceexplorer2_index.html.markdown new file mode 100644 index 000000000000..8c22e91aecf6 --- /dev/null +++ b/website/docs/r/resourceexplorer2_index.html.markdown @@ -0,0 +1,49 @@ +--- +subcategory: "Resource Explorer" +layout: "aws" +page_title: "AWS: aws_resourceexplorer2_index" +description: |- + Provides a resource to manage a Resource Explorer index in the current AWS Region. +--- + +# Resource: aws_resourceexplorer2_index + +Provides a resource to manage a Resource Explorer index in the current AWS Region. + +## Example Usage + +```terraform +resource "aws_resourceexplorer2_index" "example" { + type = "LOCAL" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `type` - (Required) The type of the index. Valid values: `AGGREGATOR`, `LOCAL`. To understand the difference between `LOCAL` and `AGGREGATOR`, see the [_AWS Resource Explorer User Guide_](https://docs.aws.amazon.com/resource-explorer/latest/userguide/manage-aggregator-region.html). +* `tags` - (Optional) Key-value map of resource tags. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +- `create` - (Default `2h`) +- `update` - (Default `2h`) +- `delete` - (Default `10m`) + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `arn` - Amazon Resource Name (ARN) of the Resource Explorer index. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Import + +Resource Explorer indexes can be imported using the `arn`, e.g. + +``` +$ terraform import aws_resourceexplorer2_index.example arn:aws:resource-explorer-2:us-east-1:123456789012:index/6047ac4e-207e-4487-9bcf-cb53bb0ff5cc +```