From 592770f46f61841838c3be6f82b842a9665a87f8 Mon Sep 17 00:00:00 2001 From: Shawn Chasse Date: Thu, 30 Nov 2023 15:38:05 -0500 Subject: [PATCH 01/25] Add support for Shield ProactiveEngagement --- internal/service/shield/exports_test.go | 1 + .../proactive_engagement_association.go | 559 ++++++++++++++++++ .../proactive_engagement_association_test.go | 274 +++++++++ .../service/shield/service_package_gen.go | 4 + ...ctive_engagement_association.html.markdown | 82 +++ 5 files changed, 920 insertions(+) create mode 100644 internal/service/shield/proactive_engagement_association.go create mode 100644 internal/service/shield/proactive_engagement_association_test.go create mode 100644 website/docs/r/shield_proactive_engagement_association.html.markdown diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index a8535ae07635..b35703275cda 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -8,4 +8,5 @@ var ( ResourceDRTAccessRoleARNAssociation = newResourceDRTAccessRoleARNAssociation ResourceDRTAccessLogBucketAssociation = newResourceDRTAccessLogBucketAssociation ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse + ResourceProactiveEngagementAssociation = newResourceProactiveEngagementAssociation ) diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go new file mode 100644 index 000000000000..c0441a3dd910 --- /dev/null +++ b/internal/service/shield/proactive_engagement_association.go @@ -0,0 +1,559 @@ +package shield + +import ( + "context" + "errors" + "strings" + "time" + + "github.com/aws/aws-sdk-go/service/shield" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "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" +) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @FrameworkResource(name="Proactive Engagement Association") +func newResourceProactiveEngagementAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceProactiveEngagementAssociation{} + + r.SetDefaultCreateTimeout(30 * time.Minute) + r.SetDefaultUpdateTimeout(30 * time.Minute) + r.SetDefaultDeleteTimeout(30 * time.Minute) + + return r, nil +} + +const ( + ResNameProactiveEngagementAssociation = "Proactive Engagement Association" + emergencyContactsEmailKey = "email_address" + emergencyContactsNotesKey = "contact_notes" + emergencyContactsPhoneKey = "phone_number" +) + +type resourceProactiveEngagementAssociation struct { + framework.ResourceWithConfigure + framework.WithTimeouts +} + +func (r *resourceProactiveEngagementAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_shield_proactive_engagement_association" +} + +func (r *resourceProactiveEngagementAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttribute(), + "enabled": schema.BoolAttribute{ + Required: true, + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + "emergency_contacts": schema.ListNestedBlock{ + Validators: []validator.List{ + listvalidator.SizeAtMost(10), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "contact_notes": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "email_address": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "phone_number": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().ShieldConn(ctx) + + var plan resourceProactiveEngagementAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var emergencyContactsList []*shield.EmergencyContact + if plan.Enabled.ValueBool() { + if !plan.EmergencyContacts.IsNull() { + var emergencyContacts []emergencyContactData + resp.Diagnostics.Append(plan.EmergencyContacts.ElementsAs(ctx, &emergencyContacts, false)...) + if resp.Diagnostics.HasError() { + return + } + emergencyContactsList = expandEmergencyContacts(ctx, emergencyContacts) + } else { + err := errors.New("At least one emergency_contacts block is required.") + resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + + describeIn := &shield.DescribeEmergencyContactSettingsInput{} + eContactSettings, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, describeIn) + + if err == nil && len(eContactSettings.EmergencyContactList) == 0 { + r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + } else if err != nil { + var ioe *shield.InvalidOperationException + var nfe *shield.ResourceNotFoundException + if errors.As(err, &ioe) && strings.Contains(ioe.Message(), "Enable/DisableProactiveEngagement") { + r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + } else if errors.As(err, &nfe) { + r.executeCreateNewAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + } else { + resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + } else { + r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + } + + createTimeout := r.CreateTimeout(ctx, plan.Timeouts) + _, err = waitProactiveEngagementAssociationCreated(ctx, conn, plan.ID.ValueString(), createTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + } + + plan.ID = types.StringValue(r.Meta().AccountID) + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociation(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { + + in := &shield.UpdateEmergencyContactSettingsInput{ + EmergencyContactList: emergencyContactsList, + } + _, err := conn.UpdateEmergencyContactSettingsWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + + enableIn := &shield.EnableProactiveEngagementInput{} + _, err = conn.EnableProactiveEngagementWithContext(ctx, enableIn) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceProactiveEngagementAssociation) executeCreateNewAssociation(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { + in := &shield.AssociateProactiveEngagementDetailsInput{ + EmergencyContactList: emergencyContactsList, + } + in.EmergencyContactList = emergencyContactsList + _, err := conn.AssociateProactiveEngagementDetailsWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceProactiveEngagementAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().ShieldConn(ctx) + var state resourceProactiveEngagementAssociationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.DescribeEmergencyContactSettingsInput{} + + out, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, in) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameProactiveEngagementAssociation, state.ID.String(), err), + err.Error(), + ) + return + } + + if out != nil && out.EmergencyContactList != nil { + emergencyContacts, d := flattenEmergencyContacts(ctx, out.EmergencyContactList) + resp.Diagnostics.Append(d...) + state.EmergencyContacts = emergencyContacts + } + state.Enabled = types.BoolValue(true) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceProactiveEngagementAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().ShieldConn(ctx) + + var plan, state resourceProactiveEngagementAssociationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.UpdateEmergencyContactSettingsInput{} + var emergencyContactsList []*shield.EmergencyContact + if !plan.EmergencyContacts.IsNull() { + var emergencyContacts []emergencyContactData + resp.Diagnostics.Append(plan.EmergencyContacts.ElementsAs(ctx, &emergencyContacts, false)...) + if resp.Diagnostics.HasError() { + return + } + emergencyContactsList = expandEmergencyContacts(ctx, emergencyContacts) + } else { + err := errors.New("At least one emergency_contacts block is required.") + resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + in.EmergencyContactList = emergencyContactsList + + _, err := conn.UpdateEmergencyContactSettingsWithContext(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + + if plan.Enabled.ValueBool() && len(emergencyContactsList) > 0 { + _, err = conn.EnableProactiveEngagementWithContext(ctx, &shield.EnableProactiveEngagementInput{}) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + _, err = waitProactiveEngagementAssociationUpdated(ctx, conn, plan.ID.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + } else { + _, err = conn.DisableProactiveEngagementWithContext(ctx, &shield.DisableProactiveEngagementInput{}) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) + _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, plan.ID.ValueString(), updateTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), + err.Error(), + ) + return + } + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceProactiveEngagementAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().ShieldConn(ctx) + + var state resourceProactiveEngagementAssociationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &shield.DisableProactiveEngagementInput{} + + _, err := conn.DisableProactiveEngagementWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameProactiveEngagementAssociation, state.ID.String(), err), + err.Error(), + ) + return + } + + updateIn := &shield.UpdateEmergencyContactSettingsInput{} + updateIn.EmergencyContactList = []*shield.EmergencyContact{} + _, err = conn.UpdateEmergencyContactSettingsWithContext(ctx, updateIn) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameProactiveEngagementAssociation, state.ID.String(), err), + err.Error(), + ) + return + } + + deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) + _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, state.ID.ValueString(), deleteTimeout) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameProactiveEngagementAssociation, state.ID.String(), err), + err.Error(), + ) + return + } +} + +func waitProactiveEngagementAssociationCreated(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{}, + Target: []string{statusNormal}, + Refresh: statusProactiveEngagementAssociation(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 2, + ContinuousTargetOccurence: 2, + } + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { + return out, err + } + return nil, err +} + +func waitProactiveEngagementAssociationUpdated(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusChangePending}, + Target: []string{statusUpdated, statusNormal}, + Refresh: statusProactiveEngagementAssociation(ctx, conn, id), + Timeout: timeout, + NotFoundChecks: 20, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { + return out, err + } + + return nil, err +} + +func waitProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { + stateConf := &retry.StateChangeConf{ + Pending: []string{statusDeleting, statusNormal}, + Target: []string{}, + Refresh: statusProactiveEngagementAssociationDeleted(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { + return out, err + } + return nil, err +} + +func statusProactiveEngagementAssociation(ctx context.Context, conn *shield.Shield, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := describeEmergencyContactSettings(ctx, conn) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + return out, statusNormal, nil + } +} + +func statusProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield, id string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + out, err := describeEmergencyContactSettings(ctx, conn) + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if out == nil || out.EmergencyContactList == nil || len(out.EmergencyContactList) == 0 { + return nil, "", nil + } + return out, statusNormal, nil + } +} + +func describeEmergencyContactSettings(ctx context.Context, conn *shield.Shield) (*shield.DescribeEmergencyContactSettingsOutput, error) { + in := &shield.DescribeEmergencyContactSettingsInput{} + + out, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, in) + if err != nil { + var nfe *shield.ResourceNotFoundException + if errors.As(err, &nfe) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + } + + if out == nil || out.EmergencyContactList == nil || len(out.EmergencyContactList) == 0 { + return nil, tfresource.NewEmptyResultError(in) + } + return out, nil +} + +// func flattenEmergencyContactList(ctx context.Context, apiObjects []*shield.EmergencyContact) (types.List, diag.Diagnostics) { +// var diags diag.Diagnostics +// attributeTypes := fwtypes.AttributeTypesMust[emergencyContactData](ctx) +// elemType := types.ObjectType{AttrTypes: attributeTypes} + +// if len(apiObjects) == 0 { +// return types.ListNull(elemType), diags +// } + +// elems := []attr.Value{} +// for _, apiObject := range apiObjects { +// if apiObject == nil { +// continue +// } + +// obj := map[string]attr.Value{ +// "email_address": flex.StringValueToFramework(ctx, *apiObject.EmailAddress), +// "phone_number": flex.StringValueToFramework(ctx, *apiObject.PhoneNumber), +// "contact_notes": flex.StringValueToFramework(ctx, *apiObject.ContactNotes), +// } +// objVal, d := types.ObjectValue(attributeTypes, obj) +// diags.Append(d...) + +// elems = append(elems, objVal) +// } + +// listVal, d := types.ListValue(elemType, elems) +// diags.Append(d...) + +// return listVal, diags +// } + +func expandEmergencyContacts(ctx context.Context, tfList []emergencyContactData) []*shield.EmergencyContact { + if len(tfList) == 0 { + return nil + } + + apiList := []*shield.EmergencyContact{} + for _, tfObj := range tfList { + apiObject := &shield.EmergencyContact{} + + if !tfObj.EmailAddress.IsNull() { + apiObject.EmailAddress = flex.StringFromFramework(ctx, tfObj.EmailAddress) + } + if !tfObj.ContactNotes.IsNull() { + apiObject.ContactNotes = flex.StringFromFramework(ctx, tfObj.ContactNotes) + } + if !tfObj.PhoneNumber.IsNull() { + apiObject.PhoneNumber = flex.StringFromFramework(ctx, tfObj.PhoneNumber) + } + apiList = append(apiList, apiObject) + } + + return apiList +} + +func flattenEmergencyContacts(ctx context.Context, apiObject []*shield.EmergencyContact) (types.List, diag.Diagnostics) { + var diags diag.Diagnostics + elemType := types.ObjectType{AttrTypes: emergencyContactsAttrTypes} + + elems := []attr.Value{} + for _, t := range apiObject { + obj := map[string]attr.Value{ + "email_address": flex.StringToFramework(ctx, t.EmailAddress), + "contact_notes": flex.StringToFramework(ctx, t.ContactNotes), + "phone_number": flex.StringToFramework(ctx, t.PhoneNumber), + } + objVal, d := types.ObjectValue(emergencyContactsAttrTypes, obj) + diags.Append(d...) + elems = append(elems, objVal) + } + + listVal, d := types.ListValue(elemType, elems) + diags.Append(d...) + + return listVal, diags +} + +var emergencyContactsAttrTypes = map[string]attr.Type{ + "email_address": types.StringType, + "contact_notes": types.StringType, + "phone_number": types.StringType, +} + +type emergencyContactData struct { + EmailAddress types.String `tfsdk:"email_address"` + ContactNotes types.String `tfsdk:"contact_notes"` + PhoneNumber types.String `tfsdk:"phone_number"` +} + +type resourceProactiveEngagementAssociationData struct { + ID types.String `tfsdk:"id"` + Enabled types.Bool `tfsdk:"enabled"` + EmergencyContacts types.List `tfsdk:"emergency_contacts"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} diff --git a/internal/service/shield/proactive_engagement_association_test.go b/internal/service/shield/proactive_engagement_association_test.go new file mode 100644 index 000000000000..e4792a6f26ce --- /dev/null +++ b/internal/service/shield/proactive_engagement_association_test.go @@ -0,0 +1,274 @@ +package shield_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/shield" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "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" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/names" + + tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" +) + +func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_proactive_engagement_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckProactiveEngagement(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProactiveEngagementAssociationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), + ), + }, + }, + }) +} + +func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_proactive_engagement_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckProactiveEngagement(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProactiveEngagementAssociationConfig_disabled(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), + ), + }, + }, + }) +} + +func TestAccShieldProactiveEngagementAssociation_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_shield_proactive_engagement_association.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + testAccPreCheckProactiveEngagement(ctx, t) + }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccProactiveEngagementAssociationConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceProactiveEngagementAssociation, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckProactiveEngagementAssociationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_shield_proactive_engagement_association" { + continue + } + + input := &shield.DescribeEmergencyContactSettingsInput{} + resp, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, input) + + if errs.IsA[*shield.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, errors.New("not destroyed")) + } + if resp != nil { + if resp.EmergencyContactList != nil && len(resp.EmergencyContactList) > 0 { + return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, errors.New("not destroyed")) + } + } + return nil + } + + return nil + } +} + +func testAccCheckProactiveEngagementAssociationExists(ctx context.Context, name string, proactiveengagementassociation *shield.DescribeEmergencyContactSettingsOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + resp, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, &shield.DescribeEmergencyContactSettingsInput{}) + + if err != nil { + return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, err) + } + + *proactiveengagementassociation = *resp + + return nil + } +} + +func testAccPreCheckProactiveEngagement(ctx context.Context, t *testing.T) { + conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldConn(ctx) + + input := &shield.DescribeEmergencyContactSettingsInput{} + _, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, input) + + if acctest.PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccProactiveEngagementAssociationConfig_basic(rName string) string { + return fmt.Sprintf(` + data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn + + depends_on = [aws_iam_role_policy_attachment.test] +} + +resource "aws_shield_proactive_engagement_association" "test" { + enabled = true + emergency_contacts { + contact_notes = "Notes" + email_address = "test@company.com" + phone_number = "+12358132134" + } + emergency_contacts { + contact_notes = "Notes 2" + email_address = "test2@company.com" + phone_number = "+12358132134" + } + depends_on = [aws_shield_drt_access_role_arn_association.test] +} + +`, rName) +} + +func testAccProactiveEngagementAssociationConfig_disabled(rName string) string { + return fmt.Sprintf(` + data "aws_partition" "current" {} + +resource "aws_iam_role" "test" { + name = %[1]q + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "test" { + role = aws_iam_role.test.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test.arn + + depends_on = [aws_iam_role_policy_attachment.test] +} + +resource "aws_shield_proactive_engagement_association" "test" { + enabled = false + emergency_contacts { + contact_notes = "Notes" + email_address = "test@company.com" + phone_number = "+12358132134" + } + emergency_contacts { + contact_notes = "Notes 2" + email_address = "test2@company.com" + phone_number = "+12358132134" + } + depends_on = [aws_shield_drt_access_role_arn_association.test] +} + +`, rName) +} diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index ca80da187eaf..ba0e82c50cfd 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -30,6 +30,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceDRTAccessRoleARNAssociation, Name: "DRT Access Role ARN Association", }, + { + Factory: newResourceProactiveEngagementAssociation, + Name: "Proactive Engagement Association", + }, } } diff --git a/website/docs/r/shield_proactive_engagement_association.html.markdown b/website/docs/r/shield_proactive_engagement_association.html.markdown new file mode 100644 index 000000000000..694f7ea72d4b --- /dev/null +++ b/website/docs/r/shield_proactive_engagement_association.html.markdown @@ -0,0 +1,82 @@ +--- +subcategory: "Shield" +layout: "aws" +page_title: "AWS: aws_shield_proactive_engagement_association" +description: |- + Terraform resource for managing an AWS Shield Proactive Engagement Association. +--- + +# Resource: aws_shield_proactive_engagement_association + +Initializes proactive engagement and sets the list of contacts for the Shield Response Team (SRT) to use. You must provide at least one phone number in the emergency contact list. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_iam_role" "example" { + name = var.aws_shield_drt_access_role_arn + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "example" { + role = aws_iam_role.example.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "example" { + role_arn = aws_iam_role.example.arn +} + +resource "aws_shield_protection_group" "test" { + protection_group_id = %[1]q + aggregation = "MAX" + pattern = "ALL" +} + +resource "aws_shield_proactive_engagement_association" "test" { + enabled = true + emergency_contacts { + contact_notes = "Notes" + email_address = "test@company.com" + phone_number = "+12358132134" + } + emergency_contacts { + contact_notes = "Notes 2" + email_address = "test2@company.com" + phone_number = "+12358132134" + } + depends_on = [aws_shield_drt_access_role_arn_association.test] +} +``` + +## Argument Reference + +The following arguments are required: + +* `enabled` - (Required) Boolean value indicating if Proactive Engagement should be enabled or nota +* `emergency_contacts` - (Required) One or more emergency contacts are required if Proactive Engagement is to be enabled. See [`emergency_contacts`](#emergency_contacts). + + +### emergency_contacts + +* `email_address` - (Required) A valid email address that will be used for this contact. +* `phone_number` - (Optional) A phone number, starting with `+` and up to 15 digits that will be used for this contact. +* `contact_notes` - (Optional) Additional notes regarding the contact. + +## Attribute Reference + +This resource exports no additional attributes. From 692acae291f418cf2c075c0612baf4b6d6e039a4 Mon Sep 17 00:00:00 2001 From: Shawn Chasse Date: Thu, 30 Nov 2023 15:59:51 -0500 Subject: [PATCH 02/25] Update empty plan status and remove commented out function --- internal/service/shield/proactive_engagement_association.go | 1 - internal/service/shield/proactive_engagement_association_test.go | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go index c0441a3dd910..37f694a6b66e 100644 --- a/internal/service/shield/proactive_engagement_association.go +++ b/internal/service/shield/proactive_engagement_association.go @@ -20,7 +20,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/framework" "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" ) diff --git a/internal/service/shield/proactive_engagement_association_test.go b/internal/service/shield/proactive_engagement_association_test.go index e4792a6f26ce..e87b6a1e2694 100644 --- a/internal/service/shield/proactive_engagement_association_test.go +++ b/internal/service/shield/proactive_engagement_association_test.go @@ -70,6 +70,7 @@ func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), ), + ExpectNonEmptyPlan: true, }, }, }) From d1067e5d119f3006b013acffeb09acd9b02be656 Mon Sep 17 00:00:00 2001 From: Shawn Chasse Date: Tue, 5 Dec 2023 14:27:45 -0500 Subject: [PATCH 03/25] Fix linting errors for Pull Request --- .../proactive_engagement_association.go | 1 - .../proactive_engagement_association_test.go | 48 +++++++++++-------- ...ctive_engagement_association.html.markdown | 1 - 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go index 37f694a6b66e..969d0753d6f5 100644 --- a/internal/service/shield/proactive_engagement_association.go +++ b/internal/service/shield/proactive_engagement_association.go @@ -159,7 +159,6 @@ func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req } func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociation(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { - in := &shield.UpdateEmergencyContactSettingsInput{ EmergencyContactList: emergencyContactsList, } diff --git a/internal/service/shield/proactive_engagement_association_test.go b/internal/service/shield/proactive_engagement_association_test.go index e87b6a1e2694..d8bb3f11c61a 100644 --- a/internal/service/shield/proactive_engagement_association_test.go +++ b/internal/service/shield/proactive_engagement_association_test.go @@ -14,9 +14,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" - "github.com/hashicorp/terraform-provider-aws/names" - tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" + "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { @@ -24,6 +23,9 @@ func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { if testing.Short() { t.Skip("skipping long-running test in short mode") } + domain := acctest.RandomDomainName() + address1 := acctest.RandomEmailAddress(domain) + address2 := acctest.RandomEmailAddress(domain) var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -38,7 +40,7 @@ func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_basic(rName), + Config: testAccProactiveEngagementAssociationConfig_basic(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), ), @@ -53,6 +55,10 @@ func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { t.Skip("skipping long-running test in short mode") } + domain := acctest.RandomDomainName() + address1 := acctest.RandomEmailAddress(domain) + address2 := acctest.RandomEmailAddress(domain) + var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_proactive_engagement_association.test" @@ -66,7 +72,7 @@ func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_disabled(rName), + Config: testAccProactiveEngagementAssociationConfig_disabled(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), ), @@ -82,6 +88,10 @@ func TestAccShieldProactiveEngagementAssociation_disappears(t *testing.T) { t.Skip("skipping long-running test in short mode") } + domain := acctest.RandomDomainName() + address1 := acctest.RandomEmailAddress(domain) + address2 := acctest.RandomEmailAddress(domain) + var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_proactive_engagement_association.test" @@ -95,7 +105,7 @@ func TestAccShieldProactiveEngagementAssociation_disappears(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_basic(rName), + Config: testAccProactiveEngagementAssociationConfig_basic(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceProactiveEngagementAssociation, resourceName), @@ -174,9 +184,9 @@ func testAccPreCheckProactiveEngagement(ctx context.Context, t *testing.T) { } } -func testAccProactiveEngagementAssociationConfig_basic(rName string) string { +func testAccProactiveEngagementAssociationConfig_basic(rName string, email1 string, email2 string) string { return fmt.Sprintf(` - data "aws_partition" "current" {} +data "aws_partition" "current" {} resource "aws_iam_role" "test" { name = %[1]q @@ -210,23 +220,23 @@ resource "aws_shield_proactive_engagement_association" "test" { enabled = true emergency_contacts { contact_notes = "Notes" - email_address = "test@company.com" - phone_number = "+12358132134" + email_address = %[2]q + phone_number = "+12358132134" } emergency_contacts { contact_notes = "Notes 2" - email_address = "test2@company.com" - phone_number = "+12358132134" + email_address = %[3]q + phone_number = "+12358132134" } depends_on = [aws_shield_drt_access_role_arn_association.test] } -`, rName) +`, rName, email1, email2) } -func testAccProactiveEngagementAssociationConfig_disabled(rName string) string { +func testAccProactiveEngagementAssociationConfig_disabled(rName string, email1 string, email2 string) string { return fmt.Sprintf(` - data "aws_partition" "current" {} +data "aws_partition" "current" {} resource "aws_iam_role" "test" { name = %[1]q @@ -260,16 +270,16 @@ resource "aws_shield_proactive_engagement_association" "test" { enabled = false emergency_contacts { contact_notes = "Notes" - email_address = "test@company.com" - phone_number = "+12358132134" + email_address = %[2]q + phone_number = "+12358132134" } emergency_contacts { contact_notes = "Notes 2" - email_address = "test2@company.com" - phone_number = "+12358132134" + email_address = %[3]q + phone_number = "+12358132134" } depends_on = [aws_shield_drt_access_role_arn_association.test] } -`, rName) +`, rName, email1, email2) } diff --git a/website/docs/r/shield_proactive_engagement_association.html.markdown b/website/docs/r/shield_proactive_engagement_association.html.markdown index 694f7ea72d4b..5a8e8783ce2e 100644 --- a/website/docs/r/shield_proactive_engagement_association.html.markdown +++ b/website/docs/r/shield_proactive_engagement_association.html.markdown @@ -70,7 +70,6 @@ The following arguments are required: * `enabled` - (Required) Boolean value indicating if Proactive Engagement should be enabled or nota * `emergency_contacts` - (Required) One or more emergency contacts are required if Proactive Engagement is to be enabled. See [`emergency_contacts`](#emergency_contacts). - ### emergency_contacts * `email_address` - (Required) A valid email address that will be used for this contact. From 8d272408d6a60ccef554298ba482e84f6b164928 Mon Sep 17 00:00:00 2001 From: Shawn Chasse Date: Tue, 5 Dec 2023 15:08:13 -0500 Subject: [PATCH 04/25] Fix terrafmt and copyright --- internal/service/shield/proactive_engagement_association.go | 3 +++ .../service/shield/proactive_engagement_association_test.go | 3 +++ .../r/shield_proactive_engagement_association.html.markdown | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go index 969d0753d6f5..c4cd3e295ba1 100644 --- a/internal/service/shield/proactive_engagement_association.go +++ b/internal/service/shield/proactive_engagement_association.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package shield import ( diff --git a/internal/service/shield/proactive_engagement_association_test.go b/internal/service/shield/proactive_engagement_association_test.go index d8bb3f11c61a..246008e3fae8 100644 --- a/internal/service/shield/proactive_engagement_association_test.go +++ b/internal/service/shield/proactive_engagement_association_test.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package shield_test import ( diff --git a/website/docs/r/shield_proactive_engagement_association.html.markdown b/website/docs/r/shield_proactive_engagement_association.html.markdown index 5a8e8783ce2e..86711d2713cc 100644 --- a/website/docs/r/shield_proactive_engagement_association.html.markdown +++ b/website/docs/r/shield_proactive_engagement_association.html.markdown @@ -42,7 +42,7 @@ resource "aws_shield_drt_access_role_arn_association" "example" { } resource "aws_shield_protection_group" "test" { - protection_group_id = %[1]q + protection_group_id = "example" aggregation = "MAX" pattern = "ALL" } @@ -52,12 +52,12 @@ resource "aws_shield_proactive_engagement_association" "test" { emergency_contacts { contact_notes = "Notes" email_address = "test@company.com" - phone_number = "+12358132134" + phone_number = "+12358132134" } emergency_contacts { contact_notes = "Notes 2" email_address = "test2@company.com" - phone_number = "+12358132134" + phone_number = "+12358132134" } depends_on = [aws_shield_drt_access_role_arn_association.test] } From 9253a615c7f35205126c4d2bf16e1cae86b2be70 Mon Sep 17 00:00:00 2001 From: Shawn Chasse Date: Tue, 5 Dec 2023 16:51:39 -0500 Subject: [PATCH 05/25] Fix golangci-lint --- .../proactive_engagement_association.go | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go index c4cd3e295ba1..994155360bb3 100644 --- a/internal/service/shield/proactive_engagement_association.go +++ b/internal/service/shield/proactive_engagement_association.go @@ -127,14 +127,14 @@ func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req eContactSettings, err := conn.DescribeEmergencyContactSettingsWithContext(ctx, describeIn) if err == nil && len(eContactSettings.EmergencyContactList) == 0 { - r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) } else if err != nil { var ioe *shield.InvalidOperationException var nfe *shield.ResourceNotFoundException if errors.As(err, &ioe) && strings.Contains(ioe.Message(), "Enable/DisableProactiveEngagement") { - r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) } else if errors.As(err, &nfe) { - r.executeCreateNewAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + r.executeCreateNewAssociation(ctx, resp, plan, conn, emergencyContactsList) } else { resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), err.Error(), @@ -142,11 +142,11 @@ func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req return } } else { - r.executeUpdateExistingAssociation(ctx, req, resp, plan, conn, emergencyContactsList) + r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) } createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationCreated(ctx, conn, plan.ID.ValueString(), createTimeout) + _, err = waitProactiveEngagementAssociationCreated(ctx, conn, createTimeout) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameProactiveEngagementAssociation, plan.ID.String(), err), @@ -161,7 +161,7 @@ func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } -func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociation(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { +func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociation(ctx context.Context, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { in := &shield.UpdateEmergencyContactSettingsInput{ EmergencyContactList: emergencyContactsList, } @@ -185,7 +185,7 @@ func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociatio } } -func (r *resourceProactiveEngagementAssociation) executeCreateNewAssociation(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { +func (r *resourceProactiveEngagementAssociation) executeCreateNewAssociation(ctx context.Context, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Shield, emergencyContactsList []*shield.EmergencyContact) { in := &shield.AssociateProactiveEngagementDetailsInput{ EmergencyContactList: emergencyContactsList, } @@ -280,7 +280,7 @@ func (r *resourceProactiveEngagementAssociation) Update(ctx context.Context, req return } updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationUpdated(ctx, conn, plan.ID.ValueString(), updateTimeout) + _, err = waitProactiveEngagementAssociationUpdated(ctx, conn, updateTimeout) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), @@ -298,7 +298,7 @@ func (r *resourceProactiveEngagementAssociation) Update(ctx context.Context, req return } updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, plan.ID.ValueString(), updateTimeout) + _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, updateTimeout) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), @@ -351,7 +351,7 @@ func (r *resourceProactiveEngagementAssociation) Delete(ctx context.Context, req } deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) - _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, state.ID.ValueString(), deleteTimeout) + _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, deleteTimeout) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameProactiveEngagementAssociation, state.ID.String(), err), @@ -361,13 +361,13 @@ func (r *resourceProactiveEngagementAssociation) Delete(ctx context.Context, req } } -func waitProactiveEngagementAssociationCreated(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { +func waitProactiveEngagementAssociationCreated(ctx context.Context, conn *shield.Shield, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { stateConf := &retry.StateChangeConf{ Pending: []string{}, Target: []string{statusNormal}, - Refresh: statusProactiveEngagementAssociation(ctx, conn, id), + Refresh: statusProactiveEngagementAssociation(ctx, conn), Timeout: timeout, - NotFoundChecks: 2, + NotFoundChecks: 20, ContinuousTargetOccurence: 2, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -377,11 +377,11 @@ func waitProactiveEngagementAssociationCreated(ctx context.Context, conn *shield return nil, err } -func waitProactiveEngagementAssociationUpdated(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { +func waitProactiveEngagementAssociationUpdated(ctx context.Context, conn *shield.Shield, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { stateConf := &retry.StateChangeConf{ Pending: []string{statusChangePending}, Target: []string{statusUpdated, statusNormal}, - Refresh: statusProactiveEngagementAssociation(ctx, conn, id), + Refresh: statusProactiveEngagementAssociation(ctx, conn), Timeout: timeout, NotFoundChecks: 20, ContinuousTargetOccurence: 2, @@ -391,15 +391,14 @@ func waitProactiveEngagementAssociationUpdated(ctx context.Context, conn *shield if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { return out, err } - return nil, err } -func waitProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield, id string, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { +func waitProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: []string{statusDeleting, statusNormal}, Target: []string{}, - Refresh: statusProactiveEngagementAssociationDeleted(ctx, conn, id), + Refresh: statusProactiveEngagementAssociationDeleted(ctx, conn), Timeout: timeout, } @@ -410,7 +409,7 @@ func waitProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield return nil, err } -func statusProactiveEngagementAssociation(ctx context.Context, conn *shield.Shield, id string) retry.StateRefreshFunc { +func statusProactiveEngagementAssociation(ctx context.Context, conn *shield.Shield) retry.StateRefreshFunc { return func() (interface{}, string, error) { out, err := describeEmergencyContactSettings(ctx, conn) if tfresource.NotFound(err) { @@ -424,7 +423,7 @@ func statusProactiveEngagementAssociation(ctx context.Context, conn *shield.Shie } } -func statusProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield, id string) retry.StateRefreshFunc { +func statusProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Shield) retry.StateRefreshFunc { return func() (interface{}, string, error) { out, err := describeEmergencyContactSettings(ctx, conn) if tfresource.NotFound(err) { From 78346e42b8f8f9aaf2c88f013533b1653a5cb391 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 27 Feb 2024 15:30:21 -0500 Subject: [PATCH 06/25] Add CHANGELOG entry. --- .changelog/34667.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/34667.txt diff --git a/.changelog/34667.txt b/.changelog/34667.txt new file mode 100644 index 000000000000..094238b2ab18 --- /dev/null +++ b/.changelog/34667.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_shield_proactive_engagement_association +``` \ No newline at end of file From 3e14fcd16c189319198e802039db8a5280888ccd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 09:01:59 -0500 Subject: [PATCH 07/25] 'aws_shield_proactive_engagement_association' -> 'aws_shield_proactive_engagement'. --- .changelog/34667.txt | 2 +- .../drt_access_log_bucket_association_test.go | 6 +- .../drt_access_role_arn_association_test.go | 4 +- internal/service/shield/exports_test.go | 4 +- .../service/shield/proactive_engagement.go | 353 +++++++++++ .../proactive_engagement_association.go | 561 ------------------ ...n_test.go => proactive_engagement_test.go} | 103 ++-- .../service/shield/service_package_gen.go | 8 +- internal/service/shield/shield_test.go | 15 +- ...shield_proactive_engagement.html.markdown} | 41 +- 10 files changed, 449 insertions(+), 648 deletions(-) create mode 100644 internal/service/shield/proactive_engagement.go delete mode 100644 internal/service/shield/proactive_engagement_association.go rename internal/service/shield/{proactive_engagement_association_test.go => proactive_engagement_test.go} (64%) rename website/docs/r/{shield_proactive_engagement_association.html.markdown => shield_proactive_engagement.html.markdown} (58%) diff --git a/.changelog/34667.txt b/.changelog/34667.txt index 094238b2ab18..8b86558133e3 100644 --- a/.changelog/34667.txt +++ b/.changelog/34667.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -aws_shield_proactive_engagement_association +aws_shield_proactive_engagement ``` \ No newline at end of file diff --git a/internal/service/shield/drt_access_log_bucket_association_test.go b/internal/service/shield/drt_access_log_bucket_association_test.go index 8e5671a5c927..36c515843d43 100644 --- a/internal/service/shield/drt_access_log_bucket_association_test.go +++ b/internal/service/shield/drt_access_log_bucket_association_test.go @@ -22,7 +22,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func testDRTAccessLogBucketAssociation_basic(t *testing.T) { +func testAccDRTAccessLogBucketAssociation_basic(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -49,7 +49,7 @@ func testDRTAccessLogBucketAssociation_basic(t *testing.T) { }) } -func testDRTAccessLogBucketAssociation_multibucket(t *testing.T) { +func testAccDRTAccessLogBucketAssociation_multibucket(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -83,7 +83,7 @@ func testDRTAccessLogBucketAssociation_multibucket(t *testing.T) { }) } -func testDRTAccessLogBucketAssociation_disappears(t *testing.T) { +func testAccDRTAccessLogBucketAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") diff --git a/internal/service/shield/drt_access_role_arn_association_test.go b/internal/service/shield/drt_access_role_arn_association_test.go index b14483d9d2a9..8bf2d96a4912 100644 --- a/internal/service/shield/drt_access_role_arn_association_test.go +++ b/internal/service/shield/drt_access_role_arn_association_test.go @@ -23,7 +23,7 @@ import ( ) // Acceptance test access AWS and cost money to run. -func testDRTAccessRoleARNAssociation_basic(t *testing.T) { +func testAccDRTAccessRoleARNAssociation_basic(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { @@ -52,7 +52,7 @@ func testDRTAccessRoleARNAssociation_basic(t *testing.T) { }) } -func testDRTAccessRoleARNAssociation_disappears(t *testing.T) { +func testAccDRTAccessRoleARNAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) if testing.Short() { t.Skip("skipping long-running test in short mode") diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index b35703275cda..9adfed69366b 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -8,5 +8,7 @@ var ( ResourceDRTAccessRoleARNAssociation = newResourceDRTAccessRoleARNAssociation ResourceDRTAccessLogBucketAssociation = newResourceDRTAccessLogBucketAssociation ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse - ResourceProactiveEngagementAssociation = newResourceProactiveEngagementAssociation + ResourceProactiveEngagement = newProactiveEngagementResource + + FindEmergencyContactSettings = findEmergencyContactSettings ) diff --git a/internal/service/shield/proactive_engagement.go b/internal/service/shield/proactive_engagement.go new file mode 100644 index 000000000000..b8704cf6a7c4 --- /dev/null +++ b/internal/service/shield/proactive_engagement.go @@ -0,0 +1,353 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "context" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/shield" + awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "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" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Proactive Engagement") +func newProactiveEngagementResource(context.Context) (resource.ResourceWithConfigure, error) { + return &proactiveEngagementResource{}, nil +} + +type proactiveEngagementResource struct { + framework.ResourceWithConfigure + framework.WithImportByID +} + +func (r *proactiveEngagementResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_shield_proactive_engagement" +} + +func (r *proactiveEngagementResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Required: true, + }, + names.AttrID: framework.IDAttribute(), + }, + Blocks: map[string]schema.Block{ + "emergency_contact": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[emergencyContactModel](ctx), + Validators: []validator.List{ + listvalidator.IsRequired(), + listvalidator.SizeAtLeast(1), + listvalidator.SizeAtMost(10), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "contact_notes": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 1024), + }, + }, + "email_address": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 150), + }, + }, + "phone_number": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`^\+[1-9]\d{1,14}$`), ""), + }, + }, + }, + }, + }, + }, + } +} + +func (r *proactiveEngagementResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data proactiveEngagementResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ShieldClient(ctx) + + input := &shield.AssociateProactiveEngagementDetailsInput{} + response.Diagnostics.Append(fwflex.Expand(ctx, data, input)...) + if response.Diagnostics.HasError() { + return + } + + _, err := conn.AssociateProactiveEngagementDetails(ctx, input) + + if err != nil { + response.Diagnostics.AddError("creating Shield Proactive Engagement", err.Error()) + + return + } + + data.ID = types.StringValue(r.Meta().AccountID) + + response.Diagnostics.Append(updateEmergencyContactSettings(ctx, conn, &data)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(putProactiveEngagementStatus(ctx, conn, data.Enabled.ValueBool())...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *proactiveEngagementResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data proactiveEngagementResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ShieldClient(ctx) + + subscription, err := findSubscription(ctx, conn) + + if err == nil && subscription.ProactiveEngagementStatus == "" { + err = tfresource.NewEmptyResultError(nil) + } + + var emergencyContacts []awstypes.EmergencyContact + + if err == nil { + emergencyContacts, err = findEmergencyContactSettings(ctx, conn) + } + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + + return + } + + data.EmergencyContactList = fwtypes.NewListNestedObjectValueOfValueSlice[emergencyContactModel](ctx, tfslices.ApplyToAll(emergencyContacts, func(apiObject awstypes.EmergencyContact) emergencyContactModel { + return emergencyContactModel{ + ContactNotes: fwflex.StringToFramework(ctx, apiObject.ContactNotes), + EmailAddress: fwflex.StringToFramework(ctx, apiObject.EmailAddress), + PhoneNumber: fwflex.StringToFramework(ctx, apiObject.PhoneNumber), + } + })) + data.Enabled = types.BoolValue(subscription.ProactiveEngagementStatus == awstypes.ProactiveEngagementStatusEnabled) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (r *proactiveEngagementResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new proactiveEngagementResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ShieldClient(ctx) + + if !new.EmergencyContactList.Equal(old.EmergencyContactList) { + response.Diagnostics.Append(updateEmergencyContactSettings(ctx, conn, &new)...) + if response.Diagnostics.HasError() { + return + } + } + + if !new.Enabled.Equal(old.Enabled) { + response.Diagnostics.Append(putProactiveEngagementStatus(ctx, conn, new.Enabled.ValueBool())...) + if response.Diagnostics.HasError() { + return + } + } + + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *proactiveEngagementResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data proactiveEngagementResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ShieldClient(ctx) + + inputD := &shield.DisableProactiveEngagementInput{} + + _, err := conn.DisableProactiveEngagement(ctx, inputD) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError("disabling Shield proactive engagement", err.Error()) + + return + } + + inputU := &shield.UpdateEmergencyContactSettingsInput{ + EmergencyContactList: []awstypes.EmergencyContact{}, + } + + _, err = conn.UpdateEmergencyContactSettings(ctx, inputU) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + + if err != nil { + response.Diagnostics.AddError("updating Shield emergency contact settings", err.Error()) + + return + } +} + +func disableProactiveEngagement(ctx context.Context, conn *shield.Client) diag.Diagnostics { + var diags diag.Diagnostics + input := &shield.DisableProactiveEngagementInput{} + + _, err := conn.DisableProactiveEngagement(ctx, input) + + if err != nil { + diags.AddError("disabling Shield proactive engagement", err.Error()) + + return diags + } + + return diags +} + +func enableProactiveEngagement(ctx context.Context, conn *shield.Client) diag.Diagnostics { + var diags diag.Diagnostics + input := &shield.EnableProactiveEngagementInput{} + + _, err := conn.EnableProactiveEngagement(ctx, input) + + if err != nil { + diags.AddError("enabling Shield proactive engagement", err.Error()) + + return diags + } + + return diags +} + +func putProactiveEngagementStatus(ctx context.Context, conn *shield.Client, enabled bool) diag.Diagnostics { + var diags diag.Diagnostics + + if enabled { + diags.Append(enableProactiveEngagement(ctx, conn)...) + } else { + diags.Append(disableProactiveEngagement(ctx, conn)...) + } + + return diags +} + +func updateEmergencyContactSettings(ctx context.Context, conn *shield.Client, data *proactiveEngagementResourceModel) diag.Diagnostics { + var diags diag.Diagnostics + input := &shield.UpdateEmergencyContactSettingsInput{} + + diags.Append(fwflex.Expand(ctx, data, input)...) + if diags.HasError() { + return diags + } + + _, err := conn.UpdateEmergencyContactSettings(ctx, input) + + if err != nil { + diags.AddError("updating Shield emergency contact settings", err.Error()) + + return diags + } + + return diags +} + +func findEmergencyContactSettings(ctx context.Context, conn *shield.Client) ([]awstypes.EmergencyContact, error) { + input := &shield.DescribeEmergencyContactSettingsInput{} + + output, err := conn.DescribeEmergencyContactSettings(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.EmergencyContactList) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.EmergencyContactList, nil +} + +func findSubscription(ctx context.Context, conn *shield.Client) (*awstypes.Subscription, error) { + input := &shield.DescribeSubscriptionInput{} + + output, err := conn.DescribeSubscription(ctx, input) + + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Subscription == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Subscription, nil +} + +type proactiveEngagementResourceModel struct { + EmergencyContactList fwtypes.ListNestedObjectValueOf[emergencyContactModel] `tfsdk:"emergency_contact"` + Enabled types.Bool `tfsdk:"enabled"` + ID types.String `tfsdk:"id"` +} + +type emergencyContactModel struct { + ContactNotes types.String `tfsdk:"contact_notes"` + EmailAddress types.String `tfsdk:"email_address"` + PhoneNumber types.String `tfsdk:"phone_number"` +} diff --git a/internal/service/shield/proactive_engagement_association.go b/internal/service/shield/proactive_engagement_association.go deleted file mode 100644 index 0d4b3660b030..000000000000 --- a/internal/service/shield/proactive_engagement_association.go +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package shield - -import ( - "context" - "errors" - "strings" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" - "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" - "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" -) - -// Function annotations are used for resource registration to the Provider. DO NOT EDIT. -// @FrameworkResource(name="Proactive Engagement Association") -func newResourceProactiveEngagementAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceProactiveEngagementAssociation{} - - r.SetDefaultCreateTimeout(30 * time.Minute) - r.SetDefaultUpdateTimeout(30 * time.Minute) - r.SetDefaultDeleteTimeout(30 * time.Minute) - - return r, nil -} - -const ( - ResNameProactiveEngagementAssociation = "Proactive Engagement Association" - emergencyContactsEmailKey = "email_address" - emergencyContactsNotesKey = "contact_notes" - emergencyContactsPhoneKey = "phone_number" -) - -type resourceProactiveEngagementAssociation struct { - framework.ResourceWithConfigure - framework.WithTimeouts -} - -func (r *resourceProactiveEngagementAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_shield_proactive_engagement_association" -} - -func (r *resourceProactiveEngagementAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "id": framework.IDAttribute(), - "enabled": schema.BoolAttribute{ - Required: true, - }, - }, - Blocks: map[string]schema.Block{ - "timeouts": timeouts.Block(ctx, timeouts.Opts{ - Create: true, - Update: true, - Delete: true, - }), - "emergency_contacts": schema.ListNestedBlock{ - Validators: []validator.List{ - listvalidator.SizeAtMost(10), - }, - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "contact_notes": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "email_address": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - }, - "phone_number": schema.StringAttribute{ - Optional: true, - }, - }, - }, - }, - }, - } -} - -func (r *resourceProactiveEngagementAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().ShieldClient(ctx) - - var plan resourceProactiveEngagementAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { - return - } - - var emergencyContactsList []awstypes.EmergencyContact - if plan.Enabled.ValueBool() { - if !plan.EmergencyContacts.IsNull() { - var emergencyContacts []emergencyContactData - resp.Diagnostics.Append(plan.EmergencyContacts.ElementsAs(ctx, &emergencyContacts, false)...) - if resp.Diagnostics.HasError() { - return - } - emergencyContactsList = expandEmergencyContacts(ctx, emergencyContacts) - } else { - err := errors.New("At least one emergency_contacts block is required.") - resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - - describeIn := &shield.DescribeEmergencyContactSettingsInput{} - eContactSettings, err := conn.DescribeEmergencyContactSettings(ctx, describeIn) - - if err == nil && len(eContactSettings.EmergencyContactList) == 0 { - r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) - } else if err != nil { - var ioe *awstypes.InvalidOperationException - var nfe *awstypes.ResourceNotFoundException - if errors.As(err, &ioe) && strings.Contains(aws.ToString(ioe.Message), "Enable/DisableProactiveEngagement") { - r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) - } else if errors.As(err, &nfe) { - r.executeCreateNewAssociation(ctx, resp, plan, conn, emergencyContactsList) - } else { - resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - } else { - r.executeUpdateExistingAssociation(ctx, resp, plan, conn, emergencyContactsList) - } - - createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationCreated(ctx, conn, createTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - } - - plan.ID = types.StringValue(r.Meta().AccountID) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) -} - -func (r *resourceProactiveEngagementAssociation) executeUpdateExistingAssociation(ctx context.Context, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Client, emergencyContactsList []awstypes.EmergencyContact) { - in := &shield.UpdateEmergencyContactSettingsInput{ - EmergencyContactList: emergencyContactsList, - } - _, err := conn.UpdateEmergencyContactSettings(ctx, in) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - - enableIn := &shield.EnableProactiveEngagementInput{} - _, err = conn.EnableProactiveEngagement(ctx, enableIn) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } -} - -func (r *resourceProactiveEngagementAssociation) executeCreateNewAssociation(ctx context.Context, resp *resource.CreateResponse, plan resourceProactiveEngagementAssociationData, conn *shield.Client, emergencyContactsList []awstypes.EmergencyContact) { - in := &shield.AssociateProactiveEngagementDetailsInput{ - EmergencyContactList: emergencyContactsList, - } - in.EmergencyContactList = emergencyContactsList - _, err := conn.AssociateProactiveEngagementDetails(ctx, in) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } -} - -func (r *resourceProactiveEngagementAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().ShieldClient(ctx) - var state resourceProactiveEngagementAssociationData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - in := &shield.DescribeEmergencyContactSettingsInput{} - - out, err := conn.DescribeEmergencyContactSettings(ctx, in) - - if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) - return - } - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameProactiveEngagementAssociation, state.ID.String(), err), - err.Error(), - ) - return - } - - if out != nil && out.EmergencyContactList != nil { - emergencyContacts, d := flattenEmergencyContacts(ctx, out.EmergencyContactList) - resp.Diagnostics.Append(d...) - state.EmergencyContacts = emergencyContacts - } - state.Enabled = types.BoolValue(true) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (r *resourceProactiveEngagementAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().ShieldClient(ctx) - - var plan, state resourceProactiveEngagementAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - in := &shield.UpdateEmergencyContactSettingsInput{} - var emergencyContactsList []awstypes.EmergencyContact - if !plan.EmergencyContacts.IsNull() { - var emergencyContacts []emergencyContactData - resp.Diagnostics.Append(plan.EmergencyContacts.ElementsAs(ctx, &emergencyContacts, false)...) - if resp.Diagnostics.HasError() { - return - } - emergencyContactsList = expandEmergencyContacts(ctx, emergencyContacts) - } else { - err := errors.New("At least one emergency_contacts block is required.") - resp.Diagnostics.AddError(create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - in.EmergencyContactList = emergencyContactsList - - _, err := conn.UpdateEmergencyContactSettings(ctx, in) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - - if plan.Enabled.ValueBool() && len(emergencyContactsList) > 0 { - _, err = conn.EnableProactiveEngagement(ctx, &shield.EnableProactiveEngagementInput{}) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationUpdated(ctx, conn, updateTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - } else { - _, err = conn.DisableProactiveEngagement(ctx, &shield.DisableProactiveEngagementInput{}) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, updateTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameProactiveEngagementAssociation, plan.ID.String(), err), - err.Error(), - ) - return - } - } - - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -func (r *resourceProactiveEngagementAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceProactiveEngagementAssociationData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - in := &shield.DisableProactiveEngagementInput{} - - _, err := conn.DisableProactiveEngagement(ctx, in) - if err != nil { - var nfe *awstypes.ResourceNotFoundException - if errors.As(err, &nfe) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameProactiveEngagementAssociation, state.ID.String(), err), - err.Error(), - ) - return - } - - updateIn := &shield.UpdateEmergencyContactSettingsInput{} - updateIn.EmergencyContactList = []awstypes.EmergencyContact{} - _, err = conn.UpdateEmergencyContactSettings(ctx, updateIn) - if err != nil { - var nfe *awstypes.ResourceNotFoundException - if errors.As(err, &nfe) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameProactiveEngagementAssociation, state.ID.String(), err), - err.Error(), - ) - return - } - - deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) - _, err = waitProactiveEngagementAssociationDeleted(ctx, conn, deleteTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameProactiveEngagementAssociation, state.ID.String(), err), - err.Error(), - ) - return - } -} - -func waitProactiveEngagementAssociationCreated(ctx context.Context, conn *shield.Client, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{statusNormal}, - Refresh: statusProactiveEngagementAssociation(ctx, conn), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { - return out, err - } - return nil, err -} - -func waitProactiveEngagementAssociationUpdated(ctx context.Context, conn *shield.Client, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated, statusNormal}, - Refresh: statusProactiveEngagementAssociation(ctx, conn), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { - return out, err - } - return nil, err -} - -func waitProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Client, timeout time.Duration) (*shield.DescribeEmergencyContactSettingsOutput, error) { //nolint:unparam - stateConf := &retry.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, - Target: []string{}, - Refresh: statusProactiveEngagementAssociationDeleted(ctx, conn), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeEmergencyContactSettingsOutput); ok { - return out, err - } - return nil, err -} - -func statusProactiveEngagementAssociation(ctx context.Context, conn *shield.Client) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeEmergencyContactSettings(ctx, conn) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - return out, statusNormal, nil - } -} - -func statusProactiveEngagementAssociationDeleted(ctx context.Context, conn *shield.Client) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeEmergencyContactSettings(ctx, conn) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - if out == nil || out.EmergencyContactList == nil || len(out.EmergencyContactList) == 0 { - return nil, "", nil - } - return out, statusNormal, nil - } -} - -func describeEmergencyContactSettings(ctx context.Context, conn *shield.Client) (*shield.DescribeEmergencyContactSettingsOutput, error) { - in := &shield.DescribeEmergencyContactSettingsInput{} - - out, err := conn.DescribeEmergencyContactSettings(ctx, in) - if err != nil { - var nfe *awstypes.ResourceNotFoundException - if errors.As(err, &nfe) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } - } - - if out == nil || out.EmergencyContactList == nil || len(out.EmergencyContactList) == 0 { - return nil, tfresource.NewEmptyResultError(in) - } - return out, nil -} - -// func flattenEmergencyContactList(ctx context.Context, apiObjects []*shield.EmergencyContact) (types.List, diag.Diagnostics) { -// var diags diag.Diagnostics -// attributeTypes := fwtypes.AttributeTypesMust[emergencyContactData](ctx) -// elemType := types.ObjectType{AttrTypes: attributeTypes} - -// if len(apiObjects) == 0 { -// return types.ListNull(elemType), diags -// } - -// elems := []attr.Value{} -// for _, apiObject := range apiObjects { -// if apiObject == nil { -// continue -// } - -// obj := map[string]attr.Value{ -// "email_address": flex.StringValueToFramework(ctx, *apiObject.EmailAddress), -// "phone_number": flex.StringValueToFramework(ctx, *apiObject.PhoneNumber), -// "contact_notes": flex.StringValueToFramework(ctx, *apiObject.ContactNotes), -// } -// objVal, d := types.ObjectValue(attributeTypes, obj) -// diags.Append(d...) - -// elems = append(elems, objVal) -// } - -// listVal, d := types.ListValue(elemType, elems) -// diags.Append(d...) - -// return listVal, diags -// } - -func expandEmergencyContacts(ctx context.Context, tfList []emergencyContactData) []awstypes.EmergencyContact { - if len(tfList) == 0 { - return nil - } - - apiList := []awstypes.EmergencyContact{} - for _, tfObj := range tfList { - apiObject := awstypes.EmergencyContact{} - - if !tfObj.EmailAddress.IsNull() { - apiObject.EmailAddress = flex.StringFromFramework(ctx, tfObj.EmailAddress) - } - if !tfObj.ContactNotes.IsNull() { - apiObject.ContactNotes = flex.StringFromFramework(ctx, tfObj.ContactNotes) - } - if !tfObj.PhoneNumber.IsNull() { - apiObject.PhoneNumber = flex.StringFromFramework(ctx, tfObj.PhoneNumber) - } - apiList = append(apiList, apiObject) - } - - return apiList -} - -func flattenEmergencyContacts(ctx context.Context, apiObject []awstypes.EmergencyContact) (types.List, diag.Diagnostics) { - var diags diag.Diagnostics - elemType := types.ObjectType{AttrTypes: emergencyContactsAttrTypes} - - elems := []attr.Value{} - for _, t := range apiObject { - obj := map[string]attr.Value{ - "email_address": flex.StringToFramework(ctx, t.EmailAddress), - "contact_notes": flex.StringToFramework(ctx, t.ContactNotes), - "phone_number": flex.StringToFramework(ctx, t.PhoneNumber), - } - objVal, d := types.ObjectValue(emergencyContactsAttrTypes, obj) - diags.Append(d...) - elems = append(elems, objVal) - } - - listVal, d := types.ListValue(elemType, elems) - diags.Append(d...) - - return listVal, diags -} - -var emergencyContactsAttrTypes = map[string]attr.Type{ - "email_address": types.StringType, - "contact_notes": types.StringType, - "phone_number": types.StringType, -} - -type emergencyContactData struct { - EmailAddress types.String `tfsdk:"email_address"` - ContactNotes types.String `tfsdk:"contact_notes"` - PhoneNumber types.String `tfsdk:"phone_number"` -} - -type resourceProactiveEngagementAssociationData struct { - ID types.String `tfsdk:"id"` - Enabled types.Bool `tfsdk:"enabled"` - EmergencyContacts types.List `tfsdk:"emergency_contacts"` - Timeouts timeouts.Value `tfsdk:"timeouts"` -} diff --git a/internal/service/shield/proactive_engagement_association_test.go b/internal/service/shield/proactive_engagement_test.go similarity index 64% rename from internal/service/shield/proactive_engagement_association_test.go rename to internal/service/shield/proactive_engagement_test.go index 2f377c322248..92a9951aa38b 100644 --- a/internal/service/shield/proactive_engagement_association_test.go +++ b/internal/service/shield/proactive_engagement_test.go @@ -5,7 +5,6 @@ package shield_test import ( "context" - "errors" "fmt" "testing" @@ -16,24 +15,18 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { +func testAccProactiveEngagement_basic(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } domain := acctest.RandomDomainName() address1 := acctest.RandomEmailAddress(domain) address2 := acctest.RandomEmailAddress(domain) - - var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + var proactiveengagementassociation []types.EmergencyContact rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_shield_proactive_engagement_association.test" + resourceName := "aws_shield_proactive_engagement.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -44,7 +37,7 @@ func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_basic(rName, address1, address2), + Config: testAccProactiveEngagementConfig_basic(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), ), @@ -53,19 +46,14 @@ func TestAccShieldProactiveEngagementAssociation_basic(t *testing.T) { }) } -func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { +func testAccProactiveEngagement_disabled(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - domain := acctest.RandomDomainName() address1 := acctest.RandomEmailAddress(domain) address2 := acctest.RandomEmailAddress(domain) - - var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + var proactiveengagementassociation []types.EmergencyContact rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_shield_proactive_engagement_association.test" + resourceName := "aws_shield_proactive_engagement.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -76,7 +64,7 @@ func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_disabled(rName, address1, address2), + Config: testAccProactiveEngagementConfig_disabled(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), ), @@ -86,19 +74,14 @@ func TestAccShieldProactiveEngagementAssociation_disabled(t *testing.T) { }) } -func TestAccShieldProactiveEngagementAssociation_disappears(t *testing.T) { +func testAccProactiveEngagement_disappears(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - domain := acctest.RandomDomainName() address1 := acctest.RandomEmailAddress(domain) address2 := acctest.RandomEmailAddress(domain) - - var proactiveengagementassociation shield.DescribeEmergencyContactSettingsOutput + var proactiveengagementassociation []types.EmergencyContact rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_shield_proactive_engagement_association.test" + resourceName := "aws_shield_proactive_engagement.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -109,10 +92,10 @@ func TestAccShieldProactiveEngagementAssociation_disappears(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementAssociationConfig_basic(rName, address1, address2), + Config: testAccProactiveEngagementConfig_basic(rName, address1, address2), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceProactiveEngagementAssociation, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceProactiveEngagement, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -125,50 +108,43 @@ func testAccCheckProactiveEngagementAssociationDestroy(ctx context.Context) reso conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_shield_proactive_engagement_association" { + if rs.Type != "aws_shield_proactive_engagement" { continue } - input := &shield.DescribeEmergencyContactSettingsInput{} - resp, err := conn.DescribeEmergencyContactSettings(ctx, input) + _, err := tfshield.FindEmergencyContactSettings(ctx, conn) - if errs.IsA[*types.ResourceNotFoundException](err) { - return nil + if tfresource.NotFound(err) { + continue } + if err != nil { - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, errors.New("not destroyed")) + return err } - if resp != nil { - if resp.EmergencyContactList != nil && len(resp.EmergencyContactList) > 0 { - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, errors.New("not destroyed")) - } - } - return nil + + return fmt.Errorf("Shield Proactive Engagement %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckProactiveEngagementAssociationExists(ctx context.Context, name string, proactiveengagementassociation *shield.DescribeEmergencyContactSettingsOutput) resource.TestCheckFunc { +func testAccCheckProactiveEngagementAssociationExists(ctx context.Context, n string, v *[]types.EmergencyContact) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + _, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - resp, err := conn.DescribeEmergencyContactSettings(ctx, &shield.DescribeEmergencyContactSettingsInput{}) + + output, err := tfshield.FindEmergencyContactSettings(ctx, conn) if err != nil { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameProactiveEngagementAssociation, rs.Primary.ID, err) + return err } - *proactiveengagementassociation = *resp + *v = output return nil } @@ -188,7 +164,7 @@ func testAccPreCheckProactiveEngagement(ctx context.Context, t *testing.T) { } } -func testAccProactiveEngagementAssociationConfig_basic(rName string, email1 string, email2 string) string { +func testAccProactiveEngagementConfig_basic(rName string, email1 string, email2 string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -220,25 +196,27 @@ resource "aws_shield_drt_access_role_arn_association" "test" { depends_on = [aws_iam_role_policy_attachment.test] } -resource "aws_shield_proactive_engagement_association" "test" { +resource "aws_shield_proactive_engagement" "test" { enabled = true - emergency_contacts { + + emergency_contact { contact_notes = "Notes" email_address = %[2]q phone_number = "+12358132134" } - emergency_contacts { + emergency_contact { contact_notes = "Notes 2" email_address = %[3]q phone_number = "+12358132134" } + depends_on = [aws_shield_drt_access_role_arn_association.test] } `, rName, email1, email2) } -func testAccProactiveEngagementAssociationConfig_disabled(rName string, email1 string, email2 string) string { +func testAccProactiveEngagementConfig_disabled(rName string, email1 string, email2 string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -270,18 +248,21 @@ resource "aws_shield_drt_access_role_arn_association" "test" { depends_on = [aws_iam_role_policy_attachment.test] } -resource "aws_shield_proactive_engagement_association" "test" { +resource "aws_shield_proactive_engagement" "test" { enabled = false - emergency_contacts { + + emergency_contact { contact_notes = "Notes" email_address = %[2]q phone_number = "+12358132134" } - emergency_contacts { + + emergency_contact { contact_notes = "Notes 2" email_address = %[3]q phone_number = "+12358132134" } + depends_on = [aws_shield_drt_access_role_arn_association.test] } diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index ba0e82c50cfd..76140307f8e1 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -18,6 +18,10 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ + { + Factory: newProactiveEngagementResource, + Name: "Proactive Engagement", + }, { Factory: newResourceApplicationLayerAutomaticResponse, Name: "Application Layer Automatic Response", @@ -30,10 +34,6 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceDRTAccessRoleARNAssociation, Name: "DRT Access Role ARN Association", }, - { - Factory: newResourceProactiveEngagementAssociation, - Name: "Proactive Engagement Association", - }, } } diff --git a/internal/service/shield/shield_test.go b/internal/service/shield/shield_test.go index cdb64504c322..c365d9acfdce 100644 --- a/internal/service/shield/shield_test.go +++ b/internal/service/shield/shield_test.go @@ -14,13 +14,18 @@ func TestAccShield_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "DRTAccessLogBucketAssociation": { - "basic": testDRTAccessLogBucketAssociation_basic, - "multibucket": testDRTAccessLogBucketAssociation_multibucket, - "disappears": testDRTAccessLogBucketAssociation_disappears, + "basic": testAccDRTAccessLogBucketAssociation_basic, + "multibucket": testAccDRTAccessLogBucketAssociation_multibucket, + "disappears": testAccDRTAccessLogBucketAssociation_disappears, }, "DRTAccessRoleARNAssociation": { - "basic": testDRTAccessRoleARNAssociation_basic, - "disappears": testDRTAccessRoleARNAssociation_disappears, + "basic": testAccDRTAccessRoleARNAssociation_basic, + "disappears": testAccDRTAccessRoleARNAssociation_disappears, + }, + "ProactiveEngagement": { + "basic": testAccProactiveEngagement_basic, + "disabled": testAccProactiveEngagement_disabled, + "disappears": testAccProactiveEngagement_disappears, }, } diff --git a/website/docs/r/shield_proactive_engagement_association.html.markdown b/website/docs/r/shield_proactive_engagement.html.markdown similarity index 58% rename from website/docs/r/shield_proactive_engagement_association.html.markdown rename to website/docs/r/shield_proactive_engagement.html.markdown index 86711d2713cc..a971f88b6a4e 100644 --- a/website/docs/r/shield_proactive_engagement_association.html.markdown +++ b/website/docs/r/shield_proactive_engagement.html.markdown @@ -1,14 +1,15 @@ --- subcategory: "Shield" layout: "aws" -page_title: "AWS: aws_shield_proactive_engagement_association" +page_title: "AWS: aws_shield_proactive_engagement" description: |- - Terraform resource for managing an AWS Shield Proactive Engagement Association. + Terraform resource for managing a AWS Shield Proactive Engagement. --- -# Resource: aws_shield_proactive_engagement_association +# Resource: aws_shield_proactive_engagement -Initializes proactive engagement and sets the list of contacts for the Shield Response Team (SRT) to use. You must provide at least one phone number in the emergency contact list. +Terraform resource for managing a AWS Shield Proactive Engagement. +Proactive engagement authorizes the Shield Response Team (SRT) to use email and phone to notify contacts about escalations to the SRT and to initiate proactive customer support. ## Example Usage @@ -47,18 +48,21 @@ resource "aws_shield_protection_group" "test" { pattern = "ALL" } -resource "aws_shield_proactive_engagement_association" "test" { +resource "aws_shield_proactive_engagement" "test" { enabled = true - emergency_contacts { + + emergency_contact { contact_notes = "Notes" email_address = "test@company.com" phone_number = "+12358132134" } - emergency_contacts { + + emergency_contact { contact_notes = "Notes 2" email_address = "test2@company.com" phone_number = "+12358132134" } + depends_on = [aws_shield_drt_access_role_arn_association.test] } ``` @@ -67,15 +71,32 @@ resource "aws_shield_proactive_engagement_association" "test" { The following arguments are required: -* `enabled` - (Required) Boolean value indicating if Proactive Engagement should be enabled or nota -* `emergency_contacts` - (Required) One or more emergency contacts are required if Proactive Engagement is to be enabled. See [`emergency_contacts`](#emergency_contacts). +* `enabled` - (Required) Boolean value indicating if Proactive Engagement should be enabled or not. +* `emergency_contact` - (Required) One or more emergency contacts. You must provide at least one phone number in the emergency contact list. See [`emergency_contacts`](#emergency_contacts). ### emergency_contacts +* `contact_notes` - (Optional) Additional notes regarding the contact. * `email_address` - (Required) A valid email address that will be used for this contact. * `phone_number` - (Optional) A phone number, starting with `+` and up to 15 digits that will be used for this contact. -* `contact_notes` - (Optional) Additional notes regarding the contact. ## Attribute Reference This resource exports no additional attributes. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Shield proactive engagement using the AWS account ID. For example: + +```terraform +import { + to = aws_shield_proactive_engagement.example + id = "123456789012" +} +``` + +Using `terraform import`, import Shield proactive engagement using the AWS account ID. For example: + +```console +% terraform import aws_shield_proactive_engagement.example 123456789012 +``` From e669191b0063e24fe9956ed7f71f012a787b9a63 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 11:27:24 -0500 Subject: [PATCH 08/25] Move some stuff around. --- internal/framework/types/arn.go | 4 ---- internal/framework/types/consts.go | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 internal/framework/types/consts.go diff --git a/internal/framework/types/arn.go b/internal/framework/types/arn.go index d923c9ae9742..e54688d60f84 100644 --- a/internal/framework/types/arn.go +++ b/internal/framework/types/arn.go @@ -18,10 +18,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" ) -// ProviderErrorDetailPrefix contains instructions for reporting provider errors to provider developers -const ProviderErrorDetailPrefix = "An unexpected error was encountered trying to validate an attribute value. " + - "This is always an error in the provider. Please report the following to the provider developer:\n\n" - type arnType struct { basetypes.StringType } diff --git a/internal/framework/types/consts.go b/internal/framework/types/consts.go new file mode 100644 index 000000000000..52efcfc872c3 --- /dev/null +++ b/internal/framework/types/consts.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package types + +// ProviderErrorDetailPrefix contains instructions for reporting provider errors to provider developers +const ProviderErrorDetailPrefix = "An unexpected error was encountered trying to validate an attribute value. " + + "This is always an error in the provider. Please report the following to the provider developer:\n\n" From ceecc6ee06104c9db3d6ecd2ebfd7930923f2799 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 11:52:12 -0500 Subject: [PATCH 09/25] 'fwtypes.ARNValue' -> 'fwtypes.ARNValueMust'. --- internal/framework/flex/auto_expand_test.go | 4 ++-- internal/framework/flex/auto_flatten_test.go | 4 ++-- internal/framework/flex/string_test.go | 4 ++-- internal/framework/types/arn.go | 4 ++-- internal/framework/types/arn_test.go | 2 +- .../apprunner/default_auto_scaling_configuration_version.go | 2 +- internal/service/bedrock/custom_model.go | 2 +- internal/service/kinesis/resource_policy.go | 2 +- internal/service/redshift/data_share_authorization.go | 2 +- internal/service/redshift/data_share_consumer_association.go | 4 ++-- internal/service/ssoadmin/application_access_scope.go | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/framework/flex/auto_expand_test.go b/internal/framework/flex/auto_expand_test.go index f5b3908d1ea2..b27f6df5be0f 100644 --- a/internal/framework/flex/auto_expand_test.go +++ b/internal/framework/flex/auto_expand_test.go @@ -258,13 +258,13 @@ func TestExpand(t *testing.T) { }, { TestName: "single ARN Source and single string Target", - Source: &TestFlexTF17{Field1: fwtypes.ARNValue(testARN)}, + Source: &TestFlexTF17{Field1: fwtypes.ARNValueMust(testARN)}, Target: &TestFlexAWS01{}, WantTarget: &TestFlexAWS01{Field1: testARN}, }, { TestName: "single ARN Source and single *string Target", - Source: &TestFlexTF17{Field1: fwtypes.ARNValue(testARN)}, + Source: &TestFlexTF17{Field1: fwtypes.ARNValueMust(testARN)}, Target: &TestFlexAWS02{}, WantTarget: &TestFlexAWS02{Field1: aws.String(testARN)}, }, diff --git a/internal/framework/flex/auto_flatten_test.go b/internal/framework/flex/auto_flatten_test.go index 91454c0377bc..e4288695ad8c 100644 --- a/internal/framework/flex/auto_flatten_test.go +++ b/internal/framework/flex/auto_flatten_test.go @@ -374,13 +374,13 @@ func TestFlatten(t *testing.T) { TestName: "single string Source and single ARN Target", Source: &TestFlexAWS01{Field1: testARN}, Target: &TestFlexTF17{}, - WantTarget: &TestFlexTF17{Field1: fwtypes.ARNValue(testARN)}, + WantTarget: &TestFlexTF17{Field1: fwtypes.ARNValueMust(testARN)}, }, { TestName: "single *string Source and single ARN Target", Source: &TestFlexAWS02{Field1: aws.String(testARN)}, Target: &TestFlexTF17{}, - WantTarget: &TestFlexTF17{Field1: fwtypes.ARNValue(testARN)}, + WantTarget: &TestFlexTF17{Field1: fwtypes.ARNValueMust(testARN)}, }, { TestName: "single nil *string Source and single ARN Target", diff --git a/internal/framework/flex/string_test.go b/internal/framework/flex/string_test.go index e9900800ae2f..63ac6c113f52 100644 --- a/internal/framework/flex/string_test.go +++ b/internal/framework/flex/string_test.go @@ -208,7 +208,7 @@ func TestARNStringFromFramework(t *testing.T) { } tests := map[string]testCase{ "valid ARN": { - input: fwtypes.ARNValue("arn:aws:iam::123456789012:user/David"), + input: fwtypes.ARNValueMust("arn:aws:iam::123456789012:user/David"), expected: aws.String("arn:aws:iam::123456789012:user/David"), }, "null ARN": { @@ -245,7 +245,7 @@ func TestStringToFrameworkARN(t *testing.T) { tests := map[string]testCase{ "valid ARN": { input: aws.String("arn:aws:iam::123456789012:user/David"), - expected: fwtypes.ARNValue("arn:aws:iam::123456789012:user/David"), + expected: fwtypes.ARNValueMust("arn:aws:iam::123456789012:user/David"), }, "null ARN": { input: nil, diff --git a/internal/framework/types/arn.go b/internal/framework/types/arn.go index e54688d60f84..cbc1fd9025e2 100644 --- a/internal/framework/types/arn.go +++ b/internal/framework/types/arn.go @@ -61,7 +61,7 @@ func (t arnType) ValueFromString(_ context.Context, in types.String) (basetypes. return ARNUnknown(), diags // Must not return validation errors. } - return ARNValue(valueString), diags + return ARNValueMust(valueString), diags } func (t arnType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { @@ -128,7 +128,7 @@ func ARNUnknown() ARN { return ARN{StringValue: basetypes.NewStringUnknown()} } -func ARNValue(value string) ARN { +func ARNValueMust(value string) ARN { return ARN{ StringValue: basetypes.NewStringValue(value), value: errs.Must(arn.Parse(value)), diff --git a/internal/framework/types/arn_test.go b/internal/framework/types/arn_test.go index bbe2bb4d58fc..80c9eac13c51 100644 --- a/internal/framework/types/arn_test.go +++ b/internal/framework/types/arn_test.go @@ -32,7 +32,7 @@ func TestARNTypeValueFromTerraform(t *testing.T) { }, "valid ARN": { val: tftypes.NewValue(tftypes.String, "arn:aws:rds:us-east-1:123456789012:db:test"), // lintignore:AWSAT003,AWSAT005 - expected: fwtypes.ARNValue("arn:aws:rds:us-east-1:123456789012:db:test"), // lintignore:AWSAT003,AWSAT005 + expected: fwtypes.ARNValueMust("arn:aws:rds:us-east-1:123456789012:db:test"), // lintignore:AWSAT003,AWSAT005 }, "invalid ARN": { val: tftypes.NewValue(tftypes.String, "not ok"), diff --git a/internal/service/apprunner/default_auto_scaling_configuration_version.go b/internal/service/apprunner/default_auto_scaling_configuration_version.go index 7e296b2275e3..d6b4258cc6f0 100644 --- a/internal/service/apprunner/default_auto_scaling_configuration_version.go +++ b/internal/service/apprunner/default_auto_scaling_configuration_version.go @@ -98,7 +98,7 @@ func (r *defaultAutoScalingConfigurationVersionResource) Read(ctx context.Contex return } - data.AutoScalingConfigurationARN = fwtypes.ARNValue(aws.ToString(output.AutoScalingConfigurationArn)) + data.AutoScalingConfigurationARN = fwtypes.ARNValueMust(aws.ToString(output.AutoScalingConfigurationArn)) response.Diagnostics.Append(response.State.Set(ctx, &data)...) } diff --git a/internal/service/bedrock/custom_model.go b/internal/service/bedrock/custom_model.go index 88557aa20fc9..456bce8a23c4 100644 --- a/internal/service/bedrock/custom_model.go +++ b/internal/service/bedrock/custom_model.go @@ -379,7 +379,7 @@ func (r *customModelResource) Read(ctx context.Context, request resource.ReadReq if len(strings.SplitN(old.Resource, ":", 2)) == 1 { // Old ARN doesn't contain the model version and parameter count. new.Resource = strings.SplitN(new.Resource, ":", 2)[0] - data.BaseModelIdentifier = fwtypes.ARNValue(new.String()) + data.BaseModelIdentifier = fwtypes.ARNValueMust(new.String()) } } } diff --git a/internal/service/kinesis/resource_policy.go b/internal/service/kinesis/resource_policy.go index 0e142d69b387..aa990a05c535 100644 --- a/internal/service/kinesis/resource_policy.go +++ b/internal/service/kinesis/resource_policy.go @@ -218,7 +218,7 @@ type resourcePolicyResourceModel struct { } func (data *resourcePolicyResourceModel) InitFromID() error { - data.ResourceARN = fwtypes.ARNValue(data.ID.ValueString()) + data.ResourceARN = fwtypes.ARNValueMust(data.ID.ValueString()) return nil } diff --git a/internal/service/redshift/data_share_authorization.go b/internal/service/redshift/data_share_authorization.go index cf5ffa6664f6..246b730ff4ef 100644 --- a/internal/service/redshift/data_share_authorization.go +++ b/internal/service/redshift/data_share_authorization.go @@ -161,7 +161,7 @@ func (r *resourceDataShareAuthorization) Read(ctx context.Context, req resource. return } // split ID and write constituent parts to state to support import - state.DataShareARN = fwtypes.ARNValue(parts[0]) + state.DataShareARN = fwtypes.ARNValueMust(parts[0]) state.ConsumerIdentifier = types.StringValue(parts[1]) out, err := findDataShareAuthorizationByID(ctx, conn, state.ID.ValueString()) diff --git a/internal/service/redshift/data_share_consumer_association.go b/internal/service/redshift/data_share_consumer_association.go index a5d9dc4db74d..fbe5e8e30b6e 100644 --- a/internal/service/redshift/data_share_consumer_association.go +++ b/internal/service/redshift/data_share_consumer_association.go @@ -191,12 +191,12 @@ func (r *resourceDataShareConsumerAssociation) Read(ctx context.Context, req res return } // split ID and write constituent parts to state to support import - state.DataShareARN = fwtypes.ARNValue(parts[0]) + state.DataShareARN = fwtypes.ARNValueMust(parts[0]) if parts[1] != "" { state.AssociateEntireAccount = types.BoolValue(parts[1] == "true") } if parts[2] != "" { - state.ConsumerARN = fwtypes.ARNValue(parts[2]) + state.ConsumerARN = fwtypes.ARNValueMust(parts[2]) } if parts[3] != "" { state.ConsumerRegion = types.StringValue(parts[3]) diff --git a/internal/service/ssoadmin/application_access_scope.go b/internal/service/ssoadmin/application_access_scope.go index e0b2a3f233c0..57fc09e0d0d6 100644 --- a/internal/service/ssoadmin/application_access_scope.go +++ b/internal/service/ssoadmin/application_access_scope.go @@ -160,7 +160,7 @@ func (r *resourceApplicationAccessScope) Read(ctx context.Context, req resource. return } - state.ApplicationARN = fwtypes.ARNValue(parts[0]) + state.ApplicationARN = fwtypes.ARNValueMust(parts[0]) state.AuthorizedTargets = flex.FlattenFrameworkStringValueList(ctx, out.AuthorizedTargets) state.Scope = flex.StringToFramework(ctx, out.Scope) From 0b4b27c7466f182226e8d4cc192232906456f05b Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 11:53:21 -0500 Subject: [PATCH 10/25] 'fwtypes.DurationValue' -> 'fwtypes.DurationValueMust'. --- internal/framework/types/duration.go | 4 ++-- internal/framework/types/duration_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/framework/types/duration.go b/internal/framework/types/duration.go index cb890a05654d..7bcdf5f9cd4c 100644 --- a/internal/framework/types/duration.go +++ b/internal/framework/types/duration.go @@ -61,7 +61,7 @@ func (t durationType) ValueFromString(_ context.Context, in types.String) (baset return DurationUnknown(), diags // Must not return validation errors } - return DurationValue(valueString), diags + return DurationValueMust(valueString), diags } func (t durationType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { @@ -128,7 +128,7 @@ func DurationUnknown() Duration { return Duration{StringValue: basetypes.NewStringUnknown()} } -func DurationValue(value string) Duration { +func DurationValueMust(value string) Duration { return Duration{ StringValue: basetypes.NewStringValue(value), value: errs.Must(time.ParseDuration(value)), diff --git a/internal/framework/types/duration_test.go b/internal/framework/types/duration_test.go index 93dba8b8529e..00407a748889 100644 --- a/internal/framework/types/duration_test.go +++ b/internal/framework/types/duration_test.go @@ -32,7 +32,7 @@ func TestDurationTypeValueFromTerraform(t *testing.T) { }, "valid duration": { val: tftypes.NewValue(tftypes.String, "2h"), - expected: fwtypes.DurationValue("2h"), + expected: fwtypes.DurationValueMust("2h"), }, "invalid duration": { val: tftypes.NewValue(tftypes.String, "not ok"), @@ -114,7 +114,7 @@ func TestDurationToStringValue(t *testing.T) { expected types.String }{ "value": { - duration: fwtypes.DurationValue("2h"), + duration: fwtypes.DurationValueMust("2h"), expected: types.StringValue("2h"), }, "null": { From e24baa0fa9f09e2d40802d8b984d0267a51f8587 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 12:08:56 -0500 Subject: [PATCH 11/25] 'fwtypes.NewListNestedObjectValueOf...' -> 'fwtypes.NewListNestedObjectValueOf...Must'. --- internal/framework/flex/auto_expand_test.go | 32 ++-- internal/framework/flex/auto_flatten_test.go | 32 ++-- .../framework/types/list_nested_objectof.go | 32 +++- .../types/list_nested_objectof_test.go | 12 +- internal/service/amp/scraper.go | 12 +- internal/service/lexv2models/intent_test.go | 176 +++++++++--------- .../delegation_signer_record.go | 2 +- internal/service/s3/directory_bucket.go | 2 +- .../service/securitylake/aws_log_source.go | 2 +- internal/service/securitylake/data_lake.go | 4 +- .../service/shield/proactive_engagement.go | 2 +- internal/service/ssmcontacts/rotation.go | 10 +- .../ssmcontacts/rotation_data_source.go | 10 +- 13 files changed, 170 insertions(+), 158 deletions(-) diff --git a/internal/framework/flex/auto_expand_test.go b/internal/framework/flex/auto_expand_test.go index b27f6df5be0f..7fee10dac723 100644 --- a/internal/framework/flex/auto_expand_test.go +++ b/internal/framework/flex/auto_expand_test.go @@ -301,7 +301,7 @@ func TestExpandGeneric(t *testing.T) { testCases := autoFlexTestCases{ { TestName: "single list Source and *struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, Target: &TestFlexAWS06{}, WantTarget: &TestFlexAWS06{Field1: &TestFlexAWS01{Field1: "a"}}, }, @@ -313,13 +313,13 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "empty list Source and empty []struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{})}, + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{})}, Target: &TestFlexAWS08{}, WantTarget: &TestFlexAWS08{Field1: []TestFlexAWS01{}}, }, { TestName: "non-empty list Source and non-empty []struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{ + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -331,13 +331,13 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "empty list Source and empty []*struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexTF01{})}, + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{})}, Target: &TestFlexAWS07{}, WantTarget: &TestFlexAWS07{Field1: []*TestFlexAWS01{}}, }, { TestName: "non-empty list Source and non-empty []*struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexTF01{ + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -349,13 +349,13 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "empty list Source and empty []struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{})}, + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{})}, Target: &TestFlexAWS08{}, WantTarget: &TestFlexAWS08{Field1: []TestFlexAWS01{}}, }, { TestName: "non-empty list Source and non-empty []struct Target", - Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{ + Source: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -399,8 +399,8 @@ func TestExpandGeneric(t *testing.T) { TestName: "complex Source and complex Target", Source: &TestFlexTF07{ Field1: types.StringValue("m"), - Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF05{ - Field1: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF01{ + Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF05{ + Field1: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{ Field1: types.StringValue("n"), }), }), @@ -439,7 +439,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "nested string map", Source: &TestFlexTF14{ - FieldOuter: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF11{ + FieldOuter: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF11{ FieldInner: fwtypes.NewMapValueOfMust[basetypes.StringValue](ctx, map[string]attr.Value{ "x": types.StringValue("y"), }), @@ -457,7 +457,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "map block key list", Source: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -517,7 +517,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "map block key ptr source", Source: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -547,7 +547,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "map block key ptr both", Source: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -577,7 +577,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "map block enum key", Source: &TestFlexMapBlockKeyTF04{ - MapBlock: fwtypes.NewListNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF05](ctx, []TestFlexMapBlockKeyTF05{ + MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF05](ctx, []TestFlexMapBlockKeyTF05{ { MapBlockKey: fwtypes.StringEnumValue(TestEnumList), Attr1: types.StringValue("a"), @@ -782,13 +782,13 @@ func TestExpandComplexNestedBlockWithStringEnum(t *testing.T) { testCases := autoFlexTestCases{ { TestName: "single nested valid value", - Source: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &tf01{Field2: fwtypes.StringEnumValue(TestEnumList)})}, + Source: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &tf01{Field2: fwtypes.StringEnumValue(TestEnumList)})}, Target: &aws01{}, WantTarget: &aws01{Field1: 1, Field2: &aws02{Field2: TestEnumList}}, }, { TestName: "single nested empty value", - Source: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &tf01{Field2: fwtypes.StringEnumNull[TestEnum]()})}, + Source: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &tf01{Field2: fwtypes.StringEnumNull[TestEnum]()})}, Target: &aws01{}, WantTarget: &aws01{Field1: 1, Field2: &aws02{Field2: ""}}, }, diff --git a/internal/framework/flex/auto_flatten_test.go b/internal/framework/flex/auto_flatten_test.go index e4288695ad8c..a33fcc54fd5d 100644 --- a/internal/framework/flex/auto_flatten_test.go +++ b/internal/framework/flex/auto_flatten_test.go @@ -269,7 +269,7 @@ func TestFlatten(t *testing.T) { }, Target: &TestFlexTF08{}, WantTarget: &TestFlexTF08{ - Field: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF01{ + Field: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{ Field1: types.StringValue("a"), }), }, @@ -445,7 +445,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "*struct Source and single list Target", Source: &TestFlexAWS06{Field1: &TestFlexAWS01{Field1: "a"}}, Target: &TestFlexTF05{}, - WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, + WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, }, { TestName: "*struct Source and single set Target", @@ -469,7 +469,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "empty []struct and empty list Target", Source: &TestFlexAWS08{Field1: []TestFlexAWS01{}}, Target: &TestFlexTF05{}, - WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{})}, + WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{})}, }, { TestName: "empty []struct and empty struct Target", @@ -484,7 +484,7 @@ func TestFlattenGeneric(t *testing.T) { {Field1: "b"}, }}, Target: &TestFlexTF05{}, - WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{ + WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -517,7 +517,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "empty []*struct and empty list Target", Source: &TestFlexAWS07{Field1: []*TestFlexAWS01{}}, Target: &TestFlexTF05{}, - WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexTF01{})}, + WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{})}, }, { TestName: "empty []*struct and empty set Target", @@ -532,7 +532,7 @@ func TestFlattenGeneric(t *testing.T) { {Field1: "b"}, }}, Target: &TestFlexTF05{}, - WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexTF01{ + WantTarget: &TestFlexTF05{Field1: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -560,8 +560,8 @@ func TestFlattenGeneric(t *testing.T) { Target: &TestFlexTF07{}, WantTarget: &TestFlexTF07{ Field1: types.StringValue("m"), - Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF05{ - Field1: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF01{ + Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF05{ + Field1: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{ Field1: types.StringValue("n"), }), }), @@ -601,7 +601,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexTF14{}, WantTarget: &TestFlexTF14{ - FieldOuter: fwtypes.NewListNestedObjectValueOfPtr(ctx, &TestFlexTF11{ + FieldOuter: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &TestFlexTF11{ FieldInner: fwtypes.NewMapValueOfMust[basetypes.StringValue](ctx, map[string]attr.Value{ "x": types.StringValue("y"), }), @@ -620,7 +620,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexMapBlockKeyTF01{}, WantTarget: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -662,7 +662,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexMapBlockKeyTF01{}, WantTarget: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -683,7 +683,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexMapBlockKeyTF01{}, WantTarget: &TestFlexMapBlockKeyTF01{ - MapBlock: fwtypes.NewListNestedObjectValueOfSlice(ctx, []*TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewListNestedObjectValueOfSliceMust(ctx, []*TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), @@ -704,7 +704,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexMapBlockKeyTF04{}, WantTarget: &TestFlexMapBlockKeyTF04{ - MapBlock: fwtypes.NewListNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF05](ctx, []TestFlexMapBlockKeyTF05{ + MapBlock: fwtypes.NewListNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF05](ctx, []TestFlexMapBlockKeyTF05{ { MapBlockKey: fwtypes.StringEnumValue(TestEnumList), Attr1: types.StringValue("a"), @@ -773,19 +773,19 @@ func TestFlattenComplexNestedBlockWithStringEnum(t *testing.T) { TestName: "single nested valid value", Source: &aws01{Field1: 1, Field2: &aws02{Field2: TestEnumList}}, Target: &tf02{}, - WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &tf01{Field2: fwtypes.StringEnumValue(TestEnumList)})}, + WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &tf01{Field2: fwtypes.StringEnumValue(TestEnumList)})}, }, { TestName: "single nested empty value", Source: &aws01{Field1: 1, Field2: &aws02{Field2: ""}}, Target: &tf02{}, - WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &tf01{Field2: fwtypes.StringEnumNull[TestEnum]()})}, + WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &tf01{Field2: fwtypes.StringEnumNull[TestEnum]()})}, }, { TestName: "single nested zero value", Source: &aws01{Field1: 1, Field2: &aws02{Field2: ""}}, Target: &tf02{}, - WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtr(ctx, &tf01{Field2: zero})}, + WantTarget: &tf02{Field1: types.Int64Value(1), Field2: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &tf01{Field2: zero})}, }, } runAutoFlattenTestCases(ctx, t, testCases) diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index d77cf259e53e..0c9c09d96bca 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -112,7 +112,7 @@ func (t listNestedObjectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr a var diags diag.Diagnostics if v, ok := ptr.(*T); ok { - return NewListNestedObjectValueOfPtr(ctx, v), diags + return NewListNestedObjectValueOfPtrMust(ctx, v), diags } diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) @@ -123,7 +123,7 @@ func (t listNestedObjectTypeOf[T]) ValueFromObjectSlice(ctx context.Context, sli var diags diag.Diagnostics if v, ok := slice.([]*T); ok { - return NewListNestedObjectValueOfSlice(ctx, v), diags + return NewListNestedObjectValueOfSliceMust(ctx, v), diags } diags.Append(diag.NewErrorDiagnostic("Invalid slice value", fmt.Sprintf("incorrect type: want %T, got %T", (*[]T)(nil), slice))) @@ -220,18 +220,30 @@ func NewListNestedObjectValueOfUnknown[T any](ctx context.Context) ListNestedObj return ListNestedObjectValueOf[T]{ListValue: basetypes.NewListUnknown(NewObjectTypeOf[T](ctx))} } -func NewListNestedObjectValueOfPtr[T any](ctx context.Context, t *T) ListNestedObjectValueOf[T] { - return NewListNestedObjectValueOfSlice(ctx, []*T{t}) +func NewListNestedObjectValueOfPtrMust[T any](ctx context.Context, t *T) ListNestedObjectValueOf[T] { + return NewListNestedObjectValueOfSliceMust(ctx, []*T{t}) } -func NewListNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) ListNestedObjectValueOf[T] { - return newListNestedObjectValueOf[T](ctx, ts) +func NewListNestedObjectValueOfSliceMust[T any](ctx context.Context, ts []*T) ListNestedObjectValueOf[T] { + return newListNestedObjectValueOfMust[T](ctx, ts) } -func NewListNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) ListNestedObjectValueOf[T] { - return newListNestedObjectValueOf[T](ctx, ts) +func NewListNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T) ListNestedObjectValueOf[T] { + return newListNestedObjectValueOfMust[T](ctx, ts) } -func newListNestedObjectValueOf[T any](ctx context.Context, elements any) ListNestedObjectValueOf[T] { - return ListNestedObjectValueOf[T]{ListValue: fwdiag.Must(basetypes.NewListValueFrom(ctx, NewObjectTypeOf[T](ctx), elements))} +func newListNestedObjectValueOf[T any](ctx context.Context, elements any) (ListNestedObjectValueOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + v, d := basetypes.NewListValueFrom(ctx, NewObjectTypeOf[T](ctx), elements) + diags.Append(d...) + if diags.HasError() { + return NewListNestedObjectValueOfUnknown[T](ctx), diags + } + + return ListNestedObjectValueOf[T]{ListValue: v}, diags +} + +func newListNestedObjectValueOfMust[T any](ctx context.Context, elements any) ListNestedObjectValueOf[T] { + return fwdiag.Must(newListNestedObjectValueOf[T](ctx, elements)) } diff --git a/internal/framework/types/list_nested_objectof_test.go b/internal/framework/types/list_nested_objectof_test.go index 6b76b9ab5a17..ef9ea4cbd35c 100644 --- a/internal/framework/types/list_nested_objectof_test.go +++ b/internal/framework/types/list_nested_objectof_test.go @@ -90,11 +90,11 @@ func TestListNestedObjectTypeOfValueFromTerraform(t *testing.T) { }, "valid value": { tfVal: objectAListValue, - wantVal: fwtypes.NewListNestedObjectValueOfPtr[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewListNestedObjectValueOfPtrMust[ObjectA](ctx, &objectA), }, "invalid Terraform value": { tfVal: objectBListValue, - wantVal: fwtypes.NewListNestedObjectValueOfPtr[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewListNestedObjectValueOfPtrMust[ObjectA](ctx, &objectA), wantErr: true, }, } @@ -144,14 +144,14 @@ func TestListNestedObjectValueOfEqual(t *testing.T) { other: types.StringValue("test"), }, "equal value": { - other: fwtypes.NewListNestedObjectValueOfPtr(ctx, &objectA), + other: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &objectA), want: true, }, "struct not equal value": { - other: fwtypes.NewListNestedObjectValueOfPtr(ctx, &objectA2), + other: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &objectA2), }, "other struct value": { - other: fwtypes.NewListNestedObjectValueOfPtr(ctx, &objectB), + other: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &objectB), }, "null value": { other: fwtypes.NewListNestedObjectValueOfNull[ObjectA](ctx), @@ -166,7 +166,7 @@ func TestListNestedObjectValueOfEqual(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got := fwtypes.NewListNestedObjectValueOfPtr(ctx, &objectA).Equal(testCase.other) + got := fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &objectA).Equal(testCase.other) if got != testCase.want { t.Errorf("got = %v, want = %v", got, testCase.want) diff --git a/internal/service/amp/scraper.go b/internal/service/amp/scraper.go index 8013c41801aa..ec150135d269 100644 --- a/internal/service/amp/scraper.go +++ b/internal/service/amp/scraper.go @@ -278,9 +278,9 @@ func (r *scraperResource) Create(ctx context.Context, req resource.CreateRequest } // Set values for unknowns after creation is complete. - sourceData.EKS = fwtypes.NewListNestedObjectValueOfPtr(ctx, eksSourceData) + sourceData.EKS = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, eksSourceData) data.RoleARN = flex.StringToFramework(ctx, scraper.RoleArn) - data.Source = fwtypes.NewListNestedObjectValueOfPtr(ctx, sourceData) + data.Source = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, sourceData) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -319,8 +319,8 @@ func (r *scraperResource) Read(ctx context.Context, req resource.ReadRequest, re return } - data.Destination = fwtypes.NewListNestedObjectValueOfPtr(ctx, &scraperDestinationModel{ - AMP: fwtypes.NewListNestedObjectValueOfPtr(ctx, &DestinationData), + data.Destination = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &scraperDestinationModel{ + AMP: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &DestinationData), }) } data.RoleARN = flex.StringToFramework(ctx, scraper.RoleArn) @@ -334,8 +334,8 @@ func (r *scraperResource) Read(ctx context.Context, req resource.ReadRequest, re return } - data.Source = fwtypes.NewListNestedObjectValueOfPtr(ctx, &scraperSourceModel{ - EKS: fwtypes.NewListNestedObjectValueOfPtr(ctx, &eksSourceData), + data.Source = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &scraperSourceModel{ + EKS: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &eksSourceData), }) } diff --git a/internal/service/lexv2models/intent_test.go b/internal/service/lexv2models/intent_test.go index 4d67082231c3..0e9ce216860f 100644 --- a/internal/service/lexv2models/intent_test.go +++ b/internal/service/lexv2models/intent_test.go @@ -82,7 +82,7 @@ func TestIntentAutoFlex(t *testing.T) { imageResponseCardTF := tflexv2models.ImageResponseCard{ Title: types.StringValue(testString), - Button: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.Button](ctx, buttonsTF), + Button: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.Button](ctx, buttonsTF), ImageURL: types.StringValue(testString), Subtitle: types.StringValue(testString), } @@ -101,10 +101,10 @@ func TestIntentAutoFlex(t *testing.T) { } messageTF := tflexv2models.Message{ - CustomPayload: fwtypes.NewListNestedObjectValueOfPtr(ctx, &customPayloadTF), - ImageResponseCard: fwtypes.NewListNestedObjectValueOfPtr(ctx, &imageResponseCardTF), - PlainTextMessage: fwtypes.NewListNestedObjectValueOfPtr(ctx, &plainTextMessageTF), - SSMLMessage: fwtypes.NewListNestedObjectValueOfPtr(ctx, &ssmlMessageTF), + CustomPayload: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &customPayloadTF), + ImageResponseCard: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &imageResponseCardTF), + PlainTextMessage: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &plainTextMessageTF), + SSMLMessage: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &ssmlMessageTF), } messageAWS := lextypes.Message{ CustomPayload: &customPayloadAWS, @@ -121,8 +121,8 @@ func TestIntentAutoFlex(t *testing.T) { } messageGroupTF := tflexv2models.MessageGroup{ - Message: fwtypes.NewListNestedObjectValueOfPtr(ctx, &messageTF), - Variation: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.Message](ctx, messagesTF), + Message: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &messageTF), + Variation: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.Message](ctx, messagesTF), } messageGroupAWS := []lextypes.MessageGroup{ { @@ -132,7 +132,7 @@ func TestIntentAutoFlex(t *testing.T) { } responseSpecificationTF := tflexv2models.ResponseSpecification{ - MessageGroup: fwtypes.NewListNestedObjectValueOfPtr[tflexv2models.MessageGroup](ctx, &messageGroupTF), + MessageGroup: fwtypes.NewListNestedObjectValueOfPtrMust[tflexv2models.MessageGroup](ctx, &messageGroupTF), AllowInterrupt: types.BoolValue(true), } responseSpecificationAWS := lextypes.ResponseSpecification{ @@ -168,7 +168,7 @@ func TestIntentAutoFlex(t *testing.T) { slotValueOverrideMapTF := tflexv2models.SlotValueOverride{ MapBlockKey: types.StringValue(testString), Shape: fwtypes.StringEnumValue(lextypes.SlotShapeList), - Value: fwtypes.NewListNestedObjectValueOfPtr(ctx, &slotValueTF), + Value: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &slotValueTF), //Values: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []tflexv2models.SlotValueOverride{ // recursive so must be defined in line instead of in variable // { // Shape: types.StringValue(testString), @@ -201,8 +201,8 @@ func TestIntentAutoFlex(t *testing.T) { } dialogStateTF := tflexv2models.DialogState{ - DialogAction: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogActionTF), - Intent: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentOverrideTF), + DialogAction: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogActionTF), + Intent: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentOverrideTF), SessionAttributes: fwtypes.NewMapValueOfMust[basetypes.StringValue](ctx, map[string]attr.Value{ testString: types.StringValue(testString2), }), @@ -220,20 +220,20 @@ func TestIntentAutoFlex(t *testing.T) { } defaultConditionalBranchTF := tflexv2models.DefaultConditionalBranch{ - NextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - Response: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), + NextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + Response: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), } conditionalSpecificationTF := tflexv2models.ConditionalSpecification{ Active: types.BoolValue(true), - ConditionalBranch: fwtypes.NewListNestedObjectValueOfValueSlice(ctx, []tflexv2models.ConditionalBranch{{ - Condition: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionTF), + ConditionalBranch: fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, []tflexv2models.ConditionalBranch{{ + Condition: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionTF), Name: types.StringValue(testString), - NextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - Response: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), + NextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + Response: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), }}), - DefaultBranch: fwtypes.NewListNestedObjectValueOfPtr(ctx, &defaultConditionalBranchTF), + DefaultBranch: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &defaultConditionalBranchTF), } conditionalSpecificationAWS := lextypes.ConditionalSpecification{ Active: aws.Bool(true), @@ -253,9 +253,9 @@ func TestIntentAutoFlex(t *testing.T) { intentClosingSettingTF := tflexv2models.IntentClosingSetting{ Active: types.BoolValue(true), - ClosingResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - Conditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - NextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), + ClosingResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + Conditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + NextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), } intentClosingSettingAWS := lextypes.IntentClosingSetting{ Active: aws.Bool(true), @@ -297,8 +297,8 @@ func TestIntentAutoFlex(t *testing.T) { audioAndDTMFInputSpecificationTF := tflexv2models.AudioAndDTMFInputSpecification{ StartTimeoutMs: types.Int64Value(1), - AudioSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &audioSpecificationTF), - DTMFSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dtmfSpecificationTF), + AudioSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &audioSpecificationTF), + DTMFSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dtmfSpecificationTF), } audioAndDTMFInputSpecificationAWS := lextypes.AudioAndDTMFInputSpecification{ StartTimeoutMs: aws.Int32(1), @@ -315,10 +315,10 @@ func TestIntentAutoFlex(t *testing.T) { promptAttemptSpecificationTF := tflexv2models.PromptAttemptsSpecification{ MapBlockKey: fwtypes.StringEnumValue(tflexv2models.PromptAttemptsTypeInitial), - AllowedInputTypes: fwtypes.NewListNestedObjectValueOfPtr(ctx, &allowedInputTypesTF), + AllowedInputTypes: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &allowedInputTypesTF), AllowInterrupt: types.BoolValue(true), - AudioAndDTMFInputSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &audioAndDTMFInputSpecificationTF), - TextInputSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &textInputSpecificationTF), + AudioAndDTMFInputSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &audioAndDTMFInputSpecificationTF), + TextInputSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &textInputSpecificationTF), } promptAttemptSpecificationAWS := lextypes.PromptAttemptSpecification{ AllowedInputTypes: &allowedInputTypesAWS, @@ -329,7 +329,7 @@ func TestIntentAutoFlex(t *testing.T) { promptSpecificationTF := tflexv2models.PromptSpecification{ MaxRetries: types.Int64Value(1), - MessageGroup: fwtypes.NewListNestedObjectValueOfPtr(ctx, &messageGroupTF), + MessageGroup: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &messageGroupTF), AllowInterrupt: types.BoolValue(true), MessageSelectionStrategy: fwtypes.StringEnumValue(lextypes.MessageSelectionStrategyOrdered), PromptAttemptsSpecification: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &promptAttemptSpecificationTF), @@ -345,15 +345,15 @@ func TestIntentAutoFlex(t *testing.T) { } failureSuccessTimeoutTF := tflexv2models.FailureSuccessTimeout{ - FailureConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - FailureNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - FailureResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - SuccessConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - SuccessNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - SuccessResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - TimeoutConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - TimeoutNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - TimeoutResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), + FailureConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + FailureNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + FailureResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + SuccessConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + SuccessNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + SuccessResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + TimeoutConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + TimeoutNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + TimeoutResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), } postCodeHookSpecificationAWS := lextypes.PostDialogCodeHookInvocationSpecification{ @@ -383,7 +383,7 @@ func TestIntentAutoFlex(t *testing.T) { Active: types.BoolValue(true), EnableCodeHookInvocation: types.BoolValue(true), InvocationLabel: types.StringValue(testString), - PostCodeHookSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &failureSuccessTimeoutTF), + PostCodeHookSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &failureSuccessTimeoutTF), } dialogCodeHookInvocationSettingAWS := lextypes.DialogCodeHookInvocationSetting{ Active: aws.Bool(true), @@ -402,19 +402,19 @@ func TestIntentAutoFlex(t *testing.T) { } intentConfirmationSettingTF := tflexv2models.IntentConfirmationSetting{ - PromptSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &promptSpecificationTF), + PromptSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &promptSpecificationTF), Active: types.BoolValue(true), - CodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogCodeHookInvocationSettingTF), - ConfirmationConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - ConfirmationNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - ConfirmationResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - DeclinationConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - DeclinationNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - DeclinationResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - ElicitationCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &elicitationCodeHookTF), - FailureConditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - FailureNextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), - FailureResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), + CodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogCodeHookInvocationSettingTF), + ConfirmationConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + ConfirmationNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + ConfirmationResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + DeclinationConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + DeclinationNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + DeclinationResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + ElicitationCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &elicitationCodeHookTF), + FailureConditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + FailureNextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), + FailureResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), } intentConfirmationSettingAWS := lextypes.IntentConfirmationSetting{ PromptSpecification: &promptSpecificationAWS, @@ -441,7 +441,7 @@ func TestIntentAutoFlex(t *testing.T) { fulfillmentStartResponseSpecificationTF := tflexv2models.FulfillmentStartResponseSpecification{ DelayInSeconds: types.Int64Value(1), - MessageGroup: fwtypes.NewListNestedObjectValueOfPtr(ctx, &messageGroupTF), + MessageGroup: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &messageGroupTF), AllowInterrupt: types.BoolValue(true), } fulfillmentStartResponseSpecificationAWS := lextypes.FulfillmentStartResponseSpecification{ @@ -452,7 +452,7 @@ func TestIntentAutoFlex(t *testing.T) { fulfillmentUpdateResponseSpecificationTF := tflexv2models.FulfillmentUpdateResponseSpecification{ FrequencyInSeconds: types.Int64Value(1), - MessageGroup: fwtypes.NewListNestedObjectValueOfPtr(ctx, &messageGroupTF), + MessageGroup: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &messageGroupTF), AllowInterrupt: types.BoolValue(true), } fulfillmentUpdateResponseSpecificationAWS := lextypes.FulfillmentUpdateResponseSpecification{ @@ -463,9 +463,9 @@ func TestIntentAutoFlex(t *testing.T) { fulfillmentUpdatesSpecificationTF := tflexv2models.FulfillmentUpdatesSpecification{ Active: types.BoolValue(true), - StartResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentStartResponseSpecificationTF), + StartResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentStartResponseSpecificationTF), TimeoutInSeconds: types.Int64Value(1), - UpdateResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentUpdateResponseSpecificationTF), + UpdateResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentUpdateResponseSpecificationTF), } fulfillmentUpdatesSpecificationAWS := lextypes.FulfillmentUpdatesSpecification{ Active: aws.Bool(true), @@ -477,8 +477,8 @@ func TestIntentAutoFlex(t *testing.T) { fulfillmentCodeHookSettingsTF := tflexv2models.FulfillmentCodeHookSettings{ Enabled: types.BoolValue(true), Active: types.BoolValue(true), - FulfillmentUpdatesSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentUpdatesSpecificationTF), - PostFulfillmentStatusSpecification: fwtypes.NewListNestedObjectValueOfPtr(ctx, &failureSuccessTimeoutTF), + FulfillmentUpdatesSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentUpdatesSpecificationTF), + PostFulfillmentStatusSpecification: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &failureSuccessTimeoutTF), } fulfillmentCodeHookSettingsAWS := lextypes.FulfillmentCodeHookSettings{ Enabled: true, @@ -488,10 +488,10 @@ func TestIntentAutoFlex(t *testing.T) { } initialResponseSettingTF := tflexv2models.InitialResponseSetting{ - CodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogCodeHookInvocationSettingTF), - Conditional: fwtypes.NewListNestedObjectValueOfPtr(ctx, &conditionalSpecificationTF), - InitialResponse: fwtypes.NewListNestedObjectValueOfPtr(ctx, &responseSpecificationTF), - NextStep: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogStateTF), + CodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogCodeHookInvocationSettingTF), + Conditional: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &conditionalSpecificationTF), + InitialResponse: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &responseSpecificationTF), + NextStep: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogStateTF), } initialResponseSettingAWS := lextypes.InitialResponseSetting{ CodeHook: &dialogCodeHookInvocationSettingAWS, @@ -579,16 +579,16 @@ func TestIntentAutoFlex(t *testing.T) { Name: types.StringValue(testString), LocaleID: types.StringValue(testString), Description: types.StringValue(testString), - DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogCodeHookSettingsTF), - FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentCodeHookSettingsTF), - InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &initialResponseSettingTF), - InputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.InputContext](ctx, inputContextsTF), - ClosingSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentClosingSettingTF), - ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentConfirmationSettingTF), - KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtr(ctx, &kendraConfigurationTF), - OutputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.OutputContext](ctx, outputContextsTF), + DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogCodeHookSettingsTF), + FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentCodeHookSettingsTF), + InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &initialResponseSettingTF), + InputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.InputContext](ctx, inputContextsTF), + ClosingSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentClosingSettingTF), + ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentConfirmationSettingTF), + KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &kendraConfigurationTF), + OutputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.OutputContext](ctx, outputContextsTF), ParentIntentSignature: types.StringValue(testString), - SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), + SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), } intentCreateAWS := lexmodelsv2.CreateIntentInput{ BotId: aws.String(testString), @@ -614,17 +614,17 @@ func TestIntentAutoFlex(t *testing.T) { Name: types.StringValue(testString), LocaleID: types.StringValue(testString), Description: types.StringValue(testString), - DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogCodeHookSettingsTF), - FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentCodeHookSettingsTF), - InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &initialResponseSettingTF), - InputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.InputContext](ctx, inputContextsTF), - ClosingSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentClosingSettingTF), - ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentConfirmationSettingTF), - KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtr(ctx, &kendraConfigurationTF), - OutputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.OutputContext](ctx, outputContextsTF), + DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogCodeHookSettingsTF), + FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentCodeHookSettingsTF), + InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &initialResponseSettingTF), + InputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.InputContext](ctx, inputContextsTF), + ClosingSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentClosingSettingTF), + ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentConfirmationSettingTF), + KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &kendraConfigurationTF), + OutputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.OutputContext](ctx, outputContextsTF), ParentIntentSignature: types.StringValue(testString), - SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), - SlotPriority: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.SlotPriority](ctx, slotPrioritiesTF), + SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), + SlotPriority: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.SlotPriority](ctx, slotPrioritiesTF), } intentModifyAWS := lexmodelsv2.UpdateIntentInput{ BotId: aws.String(testString), @@ -651,23 +651,23 @@ func TestIntentAutoFlex(t *testing.T) { intentDescribeTF := tflexv2models.ResourceIntentData{ BotID: types.StringValue(testString), BotVersion: types.StringValue(testString), - ClosingSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentClosingSettingTF), - ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &intentConfirmationSettingTF), + ClosingSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentClosingSettingTF), + ConfirmationSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &intentConfirmationSettingTF), CreationDateTime: fwtypes.TimestampValue(testTimeStr), Description: types.StringValue(testString), - DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dialogCodeHookSettingsTF), - FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtr(ctx, &fulfillmentCodeHookSettingsTF), + DialogCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dialogCodeHookSettingsTF), + FulfillmentCodeHook: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &fulfillmentCodeHookSettingsTF), IntentID: types.StringValue(testString), - InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtr(ctx, &initialResponseSettingTF), - InputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.InputContext](ctx, inputContextsTF), - KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtr(ctx, &kendraConfigurationTF), + InitialResponseSetting: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &initialResponseSettingTF), + InputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.InputContext](ctx, inputContextsTF), + KendraConfiguration: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &kendraConfigurationTF), LastUpdatedDateTime: fwtypes.TimestampValue(testTimeStr), LocaleID: types.StringValue(testString), Name: types.StringValue(testString), - OutputContext: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.OutputContext](ctx, outputContextsTF), + OutputContext: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.OutputContext](ctx, outputContextsTF), ParentIntentSignature: types.StringValue(testString), - SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), - SlotPriority: fwtypes.NewListNestedObjectValueOfValueSlice[tflexv2models.SlotPriority](ctx, slotPrioritiesTF), + SampleUtterance: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.SampleUtterance](ctx, sampleUtterancesTF), + SlotPriority: fwtypes.NewListNestedObjectValueOfValueSliceMust[tflexv2models.SlotPriority](ctx, slotPrioritiesTF), } intentDescribeAWS := lexmodelsv2.DescribeIntentOutput{ BotId: aws.String(testString), diff --git a/internal/service/route53domains/delegation_signer_record.go b/internal/service/route53domains/delegation_signer_record.go index 5196d05b6173..9c09caee78d8 100644 --- a/internal/service/route53domains/delegation_signer_record.go +++ b/internal/service/route53domains/delegation_signer_record.go @@ -191,7 +191,7 @@ func (r *delegationSignerRecordResource) Read(ctx context.Context, request resou return } - data.SigningAttributes = fwtypes.NewListNestedObjectValueOfPtr(ctx, &signingAttributes) + data.SigningAttributes = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &signingAttributes) response.Diagnostics.Append(response.State.Set(ctx, &data)...) } diff --git a/internal/service/s3/directory_bucket.go b/internal/service/s3/directory_bucket.go index 48889f9465f1..c3c7cd8a8e5f 100644 --- a/internal/service/s3/directory_bucket.go +++ b/internal/service/s3/directory_bucket.go @@ -212,7 +212,7 @@ func (r *directoryBucketResource) Read(ctx context.Context, request resource.Rea // No API to return bucket type, location etc. data.DataRedundancy = fwtypes.StringEnumValue(awstypes.DataRedundancySingleAvailabilityZone) if matches := directoryBucketNameRegex.FindStringSubmatch(data.ID.ValueString()); len(matches) == 3 { - data.Location = fwtypes.NewListNestedObjectValueOfPtr(ctx, &locationInfoModel{ + data.Location = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &locationInfoModel{ Name: flex.StringValueToFramework(ctx, matches[2]), Type: fwtypes.StringEnumValue(awstypes.LocationTypeAvailabilityZone), }) diff --git a/internal/service/securitylake/aws_log_source.go b/internal/service/securitylake/aws_log_source.go index 0ccf627bf666..728b7e195f17 100644 --- a/internal/service/securitylake/aws_log_source.go +++ b/internal/service/securitylake/aws_log_source.go @@ -179,7 +179,7 @@ func (r *awsLogSourceResource) Read(ctx context.Context, request resource.ReadRe return } - data.Source = fwtypes.NewListNestedObjectValueOfPtr(ctx, &sourceData) + data.Source = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &sourceData) response.Diagnostics.Append(response.State.Set(ctx, &data)...) } diff --git a/internal/service/securitylake/data_lake.go b/internal/service/securitylake/data_lake.go index 316a385ed24e..f74c1b28507a 100644 --- a/internal/service/securitylake/data_lake.go +++ b/internal/service/securitylake/data_lake.go @@ -218,7 +218,7 @@ func (r *dataLakeResource) Create(ctx context.Context, request resource.CreateRe } // Set values for unknowns after creation is complete. - data.Configurations = fwtypes.NewListNestedObjectValueOfPtr(ctx, &configuration) + data.Configurations = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &configuration) data.S3BucketARN = fwflex.StringToFramework(ctx, dataLake.S3BucketArn) response.Diagnostics.Append(response.State.Set(ctx, &data)...) @@ -260,7 +260,7 @@ func (r *dataLakeResource) Read(ctx context.Context, request resource.ReadReques return } - data.Configurations = fwtypes.NewListNestedObjectValueOfPtr(ctx, &configuration) + data.Configurations = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &configuration) data.S3BucketARN = fwflex.StringToFramework(ctx, dataLake.S3BucketArn) // Transparent tagging fails with "ResourceNotFoundException: The request failed because the specified resource doesn't exist." diff --git a/internal/service/shield/proactive_engagement.go b/internal/service/shield/proactive_engagement.go index b8704cf6a7c4..5607be49d719 100644 --- a/internal/service/shield/proactive_engagement.go +++ b/internal/service/shield/proactive_engagement.go @@ -150,7 +150,7 @@ func (r *proactiveEngagementResource) Read(ctx context.Context, request resource return } - data.EmergencyContactList = fwtypes.NewListNestedObjectValueOfValueSlice[emergencyContactModel](ctx, tfslices.ApplyToAll(emergencyContacts, func(apiObject awstypes.EmergencyContact) emergencyContactModel { + data.EmergencyContactList = fwtypes.NewListNestedObjectValueOfValueSliceMust[emergencyContactModel](ctx, tfslices.ApplyToAll(emergencyContacts, func(apiObject awstypes.EmergencyContact) emergencyContactModel { return emergencyContactModel{ ContactNotes: fwflex.StringToFramework(ctx, apiObject.ContactNotes), EmailAddress: fwflex.StringToFramework(ctx, apiObject.EmailAddress), diff --git a/internal/service/ssmcontacts/rotation.go b/internal/service/ssmcontacts/rotation.go index 876014ee6848..c34658efc5fe 100644 --- a/internal/service/ssmcontacts/rotation.go +++ b/internal/service/ssmcontacts/rotation.go @@ -323,7 +323,7 @@ func (r *resourceRotation) Read(ctx context.Context, request resource.ReadReques state.ARN = flex.StringToFramework(ctx, output.RotationArn) state.Name = flex.StringToFramework(ctx, output.Name) - state.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + state.Recurrence = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, rc) state.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) if output.StartTime != nil { @@ -554,23 +554,23 @@ func flattenShiftCoverages(ctx context.Context, object map[string][]awstypes.Cov var coverageTimes []coverageTimesData for _, v := range value { ct := coverageTimesData{ - End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + End: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &handOffTime{ HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), }), - Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &handOffTime{ + Start: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &handOffTime{ HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), }), } coverageTimes = append(coverageTimes, ct) } - sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, coverageTimes) output = append(output, sc) } - return fwtypes.NewListNestedObjectValueOfValueSlice[shiftCoveragesData](ctx, output) + return fwtypes.NewListNestedObjectValueOfValueSliceMust[shiftCoveragesData](ctx, output) } func findRotationByID(ctx context.Context, conn *ssmcontacts.Client, id string) (*ssmcontacts.GetRotationOutput, error) { diff --git a/internal/service/ssmcontacts/rotation_data_source.go b/internal/service/ssmcontacts/rotation_data_source.go index 6733c96eee63..50dcdbb2ac96 100644 --- a/internal/service/ssmcontacts/rotation_data_source.go +++ b/internal/service/ssmcontacts/rotation_data_source.go @@ -114,7 +114,7 @@ func (d *dataSourceRotation) Read(ctx context.Context, request datasource.ReadRe rc.ShiftCoverages = flattenShiftCoveragesDataSource(ctx, output.Recurrence.ShiftCoverages) data.Name = flex.StringToFramework(ctx, output.Name) - data.Recurrence = fwtypes.NewListNestedObjectValueOfPtr(ctx, rc) + data.Recurrence = fwtypes.NewListNestedObjectValueOfPtrMust(ctx, rc) data.TimeZoneID = flex.StringToFramework(ctx, output.TimeZoneId) data.ID = flex.StringToFramework(ctx, output.RotationArn) @@ -195,21 +195,21 @@ func flattenShiftCoveragesDataSource(ctx context.Context, object map[string][]aw var coverageTimes []dsCoverageTimesData for _, v := range value { ct := dsCoverageTimesData{ - End: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + End: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dsHandOffTime{ HourOfDay: flex.Int32ValueToFramework(ctx, v.End.HourOfDay), MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), }), - Start: fwtypes.NewListNestedObjectValueOfPtr(ctx, &dsHandOffTime{ + Start: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &dsHandOffTime{ HourOfDay: flex.Int32ValueToFramework(ctx, v.Start.HourOfDay), MinuteOfHour: flex.Int32ValueToFramework(ctx, v.End.MinuteOfHour), }), } coverageTimes = append(coverageTimes, ct) } - sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSlice(ctx, coverageTimes) + sc.CoverageTimes = fwtypes.NewListNestedObjectValueOfValueSliceMust(ctx, coverageTimes) output = append(output, sc) } - return fwtypes.NewListNestedObjectValueOfValueSlice[dsShiftCoveragesData](ctx, output) + return fwtypes.NewListNestedObjectValueOfValueSliceMust[dsShiftCoveragesData](ctx, output) } From 93c6af3bfd7307ef81fafdf86b3056d7f055a534 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 12:20:18 -0500 Subject: [PATCH 12/25] Add 'fwtypes.NewListNestedObjectValueOf...'. --- .../framework/types/list_nested_objectof.go | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index 0c9c09d96bca..721710f91671 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -112,7 +112,9 @@ func (t listNestedObjectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr a var diags diag.Diagnostics if v, ok := ptr.(*T); ok { - return NewListNestedObjectValueOfPtrMust(ctx, v), diags + v, d := NewListNestedObjectValueOfPtr(ctx, v) + diags.Append(d...) + return v, d } diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) @@ -123,7 +125,9 @@ func (t listNestedObjectTypeOf[T]) ValueFromObjectSlice(ctx context.Context, sli var diags diag.Diagnostics if v, ok := slice.([]*T); ok { - return NewListNestedObjectValueOfSliceMust(ctx, v), diags + v, d := NewListNestedObjectValueOfSlice(ctx, v) + diags.Append(d...) + return v, d } diags.Append(diag.NewErrorDiagnostic("Invalid slice value", fmt.Sprintf("incorrect type: want %T, got %T", (*[]T)(nil), slice))) @@ -220,16 +224,28 @@ func NewListNestedObjectValueOfUnknown[T any](ctx context.Context) ListNestedObj return ListNestedObjectValueOf[T]{ListValue: basetypes.NewListUnknown(NewObjectTypeOf[T](ctx))} } +func NewListNestedObjectValueOfPtr[T any](ctx context.Context, t *T) (ListNestedObjectValueOf[T], diag.Diagnostics) { + return NewListNestedObjectValueOfSlice(ctx, []*T{t}) +} + func NewListNestedObjectValueOfPtrMust[T any](ctx context.Context, t *T) ListNestedObjectValueOf[T] { - return NewListNestedObjectValueOfSliceMust(ctx, []*T{t}) + return fwdiag.Must(NewListNestedObjectValueOfPtr(ctx, t)) +} + +func NewListNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) (ListNestedObjectValueOf[T], diag.Diagnostics) { + return newListNestedObjectValueOf[T](ctx, ts) } func NewListNestedObjectValueOfSliceMust[T any](ctx context.Context, ts []*T) ListNestedObjectValueOf[T] { - return newListNestedObjectValueOfMust[T](ctx, ts) + return fwdiag.Must(NewListNestedObjectValueOfSlice(ctx, ts)) +} + +func NewListNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) (ListNestedObjectValueOf[T], diag.Diagnostics) { + return newListNestedObjectValueOf[T](ctx, ts) } func NewListNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T) ListNestedObjectValueOf[T] { - return newListNestedObjectValueOfMust[T](ctx, ts) + return fwdiag.Must(NewListNestedObjectValueOfValueSlice(ctx, ts)) } func newListNestedObjectValueOf[T any](ctx context.Context, elements any) (ListNestedObjectValueOf[T], diag.Diagnostics) { @@ -243,7 +259,3 @@ func newListNestedObjectValueOf[T any](ctx context.Context, elements any) (ListN return ListNestedObjectValueOf[T]{ListValue: v}, diags } - -func newListNestedObjectValueOfMust[T any](ctx context.Context, elements any) ListNestedObjectValueOf[T] { - return fwdiag.Must(newListNestedObjectValueOf[T](ctx, elements)) -} From 99513d61162d4a03121346ddd80b9e8ca0678e17 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 12:24:04 -0500 Subject: [PATCH 13/25] 'fwtypes.RegexpValue' -> 'fwtypes.RegexpValueMust'. --- internal/framework/types/regexp.go | 4 ++-- internal/framework/types/regexp_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/framework/types/regexp.go b/internal/framework/types/regexp.go index 6225f240f9a3..1fabdbf2311e 100644 --- a/internal/framework/types/regexp.go +++ b/internal/framework/types/regexp.go @@ -61,7 +61,7 @@ func (t regexpType) ValueFromString(_ context.Context, in types.String) (basetyp return RegexpUnknown(), diags // Must not return validation errors. } - return RegexpValue(valueString), diags + return RegexpValueMust(valueString), diags } func (t regexpType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { @@ -128,7 +128,7 @@ func RegexpUnknown() Regexp { return Regexp{StringValue: basetypes.NewStringUnknown()} } -func RegexpValue(value string) Regexp { +func RegexpValueMust(value string) Regexp { return Regexp{ StringValue: basetypes.NewStringValue(value), value: regexache.MustCompile(value), diff --git a/internal/framework/types/regexp_test.go b/internal/framework/types/regexp_test.go index 5926380b02e2..c708ae552377 100644 --- a/internal/framework/types/regexp_test.go +++ b/internal/framework/types/regexp_test.go @@ -30,7 +30,7 @@ func TestRegexpTypeValueFromTerraform(t *testing.T) { }, "valid Regexp": { val: tftypes.NewValue(tftypes.String, `\w+`), - expected: fwtypes.RegexpValue(`\w+`), + expected: fwtypes.RegexpValueMust(`\w+`), }, "invalid Regexp": { val: tftypes.NewValue(tftypes.String, `(`), @@ -112,7 +112,7 @@ func TestRegexpToStringValue(t *testing.T) { expected types.String }{ "value": { - regexp: fwtypes.RegexpValue(`\w+`), + regexp: fwtypes.RegexpValueMust(`\w+`), expected: types.StringValue(`\w+`), }, "null": { From be35d4624ce845497c787fa6f672176c5b2be0fb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 14:06:01 -0500 Subject: [PATCH 14/25] More 'framework/types' Must-ness. --- internal/framework/flex/auto_expand_test.go | 8 ++-- internal/framework/flex/auto_flatten_test.go | 8 ++-- internal/framework/types/attrtypes.go | 15 +++++--- internal/framework/types/listof.go | 34 ++++++++--------- internal/framework/types/mapof.go | 31 +++++++-------- internal/framework/types/objectof.go | 40 +++++++++++++++++--- internal/framework/types/objectof_test.go | 12 +++--- internal/framework/types/setof.go | 9 +++-- internal/service/appconfig/environment.go | 2 +- 9 files changed, 96 insertions(+), 63 deletions(-) diff --git a/internal/framework/flex/auto_expand_test.go b/internal/framework/flex/auto_expand_test.go index 7fee10dac723..d54e5d4cdc57 100644 --- a/internal/framework/flex/auto_expand_test.go +++ b/internal/framework/flex/auto_expand_test.go @@ -635,7 +635,7 @@ func TestExpandSimpleSingleNestedBlock(t *testing.T) { testCases := autoFlexTestCases{ { TestName: "single nested block pointer", - Source: &tf02{Field1: fwtypes.NewObjectValueOf[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, + Source: &tf02{Field1: fwtypes.NewObjectValueOfMust[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, Target: &aws02{}, WantTarget: &aws02{Field1: &aws01{Field1: aws.String("a"), Field2: 1}}, }, @@ -647,7 +647,7 @@ func TestExpandSimpleSingleNestedBlock(t *testing.T) { }, { TestName: "single nested block value", - Source: &tf02{Field1: fwtypes.NewObjectValueOf[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, + Source: &tf02{Field1: fwtypes.NewObjectValueOfMust[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, Target: &aws03{}, WantTarget: &aws03{Field1: aws01{Field1: aws.String("a"), Field2: 1}}, }, @@ -686,10 +686,10 @@ func TestExpandComplexSingleNestedBlock(t *testing.T) { { TestName: "single nested block pointer", Source: &tf03{ - Field1: fwtypes.NewObjectValueOf[tf02]( + Field1: fwtypes.NewObjectValueOfMust[tf02]( ctx, &tf02{ - Field1: fwtypes.NewObjectValueOf[tf01]( + Field1: fwtypes.NewObjectValueOfMust[tf01]( ctx, &tf01{ Field1: types.BoolValue(true), diff --git a/internal/framework/flex/auto_flatten_test.go b/internal/framework/flex/auto_flatten_test.go index a33fcc54fd5d..ff2a52067843 100644 --- a/internal/framework/flex/auto_flatten_test.go +++ b/internal/framework/flex/auto_flatten_test.go @@ -819,7 +819,7 @@ func TestFlattenSimpleSingleNestedBlock(t *testing.T) { TestName: "single nested block pointer", Source: &aws02{Field1: &aws01{Field1: aws.String("a"), Field2: 1}}, Target: &tf02{}, - WantTarget: &tf02{Field1: fwtypes.NewObjectValueOf[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, + WantTarget: &tf02{Field1: fwtypes.NewObjectValueOfMust[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, }, { TestName: "single nested block nil", @@ -831,7 +831,7 @@ func TestFlattenSimpleSingleNestedBlock(t *testing.T) { TestName: "single nested block value", Source: &aws03{Field1: aws01{Field1: aws.String("a"), Field2: 1}}, Target: &tf02{}, - WantTarget: &tf02{Field1: fwtypes.NewObjectValueOf[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, + WantTarget: &tf02{Field1: fwtypes.NewObjectValueOfMust[tf01](ctx, &tf01{Field1: types.StringValue("a"), Field2: types.Int64Value(1)})}, }, } runAutoFlattenTestCases(ctx, t, testCases) @@ -871,10 +871,10 @@ func TestFlattenComplexSingleNestedBlock(t *testing.T) { Target: &tf03{}, WantTarget: &tf03{ - Field1: fwtypes.NewObjectValueOf[tf02]( + Field1: fwtypes.NewObjectValueOfMust[tf02]( ctx, &tf02{ - Field1: fwtypes.NewObjectValueOf[tf01]( + Field1: fwtypes.NewObjectValueOfMust[tf01]( ctx, &tf01{ Field1: types.BoolValue(true), diff --git a/internal/framework/types/attrtypes.go b/internal/framework/types/attrtypes.go index f3280d1a2d8d..e75ccccf2d1a 100644 --- a/internal/framework/types/attrtypes.go +++ b/internal/framework/types/attrtypes.go @@ -9,12 +9,15 @@ import ( "reflect" "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) // AttributeTypes returns a map of attribute types for the specified type T. // T must be a struct and reflection is used to find exported fields of T with the `tfsdk` tag. -func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { +func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, diag.Diagnostics) { + var diags diag.Diagnostics + var t T val := reflect.ValueOf(t) typ := val.Type() @@ -25,7 +28,8 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { } if typ.Kind() != reflect.Struct { - return nil, fmt.Errorf("%T has unsupported type: %s", t, typ) + diags.Append(diag.NewErrorDiagnostic("Invalid value", fmt.Sprintf("%T has unsupported type: %s", t, typ))) + return nil, diags } attributeTypes := make(map[string]attr.Type) @@ -39,7 +43,8 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { continue // Skip explicitly excluded fields. } if tag == "" { - return nil, fmt.Errorf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name) + diags.Append(diag.NewErrorDiagnostic("Invalid value", fmt.Sprintf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name))) + return nil, diags } if v, ok := val.Field(i).Interface().(attr.Value); ok { @@ -51,5 +56,5 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, error) { } func AttributeTypesMust[T any](ctx context.Context) map[string]attr.Type { - return errs.Must(AttributeTypes[T](ctx)) + return fwdiag.Must(AttributeTypes[T](ctx)) } diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index 4f40829d49a0..99003a737d53 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -33,8 +33,7 @@ type listTypeOf[T attr.Value] struct { } func newListTypeOf[T attr.Value](ctx context.Context) listTypeOf[T] { - var zero T - return listTypeOf[T]{basetypes.ListType{ElemType: zero.Type(ctx)}} + return listTypeOf[T]{basetypes.ListType{ElemType: newAttrTypeOf[T](ctx)}} } func (t listTypeOf[T]) Equal(o attr.Type) bool { @@ -54,7 +53,6 @@ func (t listTypeOf[T]) String() string { func (t listTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue) (basetypes.ListValuable, diag.Diagnostics) { var diags diag.Diagnostics - var zero T if in.IsNull() { return NewListValueOfNull[T](ctx), diags @@ -64,14 +62,14 @@ func (t listTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue return NewListValueOfUnknown[T](ctx), diags } - listValue, d := basetypes.NewListValue(zero.Type(ctx), in.Elements()) + v, d := basetypes.NewListValue(newAttrTypeOf[T](ctx), in.Elements()) diags.Append(d...) if diags.HasError() { return basetypes.NewListUnknown(types.StringType), diags } value := ListValueOf[T]{ - ListValue: listValue, + ListValue: v, } return value, diags @@ -121,24 +119,24 @@ func (v ListValueOf[T]) Type(ctx context.Context) attr.Type { return newListTypeOf[T](ctx) } +func NewListValueOfNull[T attr.Value](ctx context.Context) ListValueOf[T] { + return ListValueOf[T]{ListValue: basetypes.NewListNull(newAttrTypeOf[T](ctx))} +} + +func NewListValueOfUnknown[T attr.Value](ctx context.Context) ListValueOf[T] { + return ListValueOf[T]{ListValue: basetypes.NewListUnknown(newAttrTypeOf[T](ctx))} +} + func NewListValueOf[T attr.Value](ctx context.Context, elements []attr.Value) (ListValueOf[T], diag.Diagnostics) { - var zero T - val, diags := basetypes.NewListValue(zero.Type(ctx), elements) + var diags diag.Diagnostics + + v, d := basetypes.NewListValue(newAttrTypeOf[T](ctx), elements) + diags.Append(d...) if diags.HasError() { return NewListValueOfUnknown[T](ctx), diags } - return ListValueOf[T]{ListValue: val}, diags -} - -func NewListValueOfNull[T attr.Value](ctx context.Context) ListValueOf[T] { - var zero T - return ListValueOf[T]{ListValue: basetypes.NewListNull(zero.Type(ctx))} -} - -func NewListValueOfUnknown[T attr.Value](ctx context.Context) ListValueOf[T] { - var zero T - return ListValueOf[T]{ListValue: basetypes.NewListUnknown(zero.Type(ctx))} + return ListValueOf[T]{ListValue: v}, diags } func NewListValueOfMust[T attr.Value](ctx context.Context, elements []attr.Value) ListValueOf[T] { diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index eccd2ac7885d..6bf940d97fb5 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -30,8 +30,7 @@ type mapTypeOf[T attr.Value] struct { } func NewMapTypeOf[T attr.Value](ctx context.Context) mapTypeOf[T] { - var zero T - return mapTypeOf[T]{basetypes.MapType{ElemType: zero.Type(ctx)}} + return mapTypeOf[T]{basetypes.MapType{ElemType: newAttrTypeOf[T](ctx)}} } func (t mapTypeOf[T]) Equal(o attr.Type) bool { @@ -51,7 +50,6 @@ func (t mapTypeOf[T]) String() string { func (t mapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) (basetypes.MapValuable, diag.Diagnostics) { var diags diag.Diagnostics - var zero T if in.IsNull() { return NewMapValueOfNull[T](ctx), diags @@ -65,7 +63,7 @@ func (t mapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) ( // internal organs of framework and autoflex only to discover the // first argument in this call should be an element type not the map // type. - mapValue, d := basetypes.NewMapValue(zero.Type(ctx), in.Elements()) + mapValue, d := basetypes.NewMapValue(newAttrTypeOf[T](ctx), in.Elements()) diags.Append(d...) if diags.HasError() { return basetypes.NewMapUnknown(types.StringType), diags @@ -121,25 +119,24 @@ func (v MapValueOf[T]) Type(ctx context.Context) attr.Type { return NewMapTypeOf[T](ctx) } +func NewMapValueOfNull[T attr.Value](ctx context.Context) MapValueOf[T] { + return MapValueOf[T]{MapValue: basetypes.NewMapNull(newAttrTypeOf[T](ctx))} +} + +func NewMapValueOfUnknown[T attr.Value](ctx context.Context) MapValueOf[T] { + return MapValueOf[T]{MapValue: basetypes.NewMapUnknown(newAttrTypeOf[T](ctx))} +} + func NewMapValueOf[T attr.Value](ctx context.Context, elements map[string]attr.Value) (MapValueOf[T], diag.Diagnostics) { - var zero T + var diags diag.Diagnostics - mapValue, diags := basetypes.NewMapValue(zero.Type(ctx), elements) + v, d := basetypes.NewMapValue(newAttrTypeOf[T](ctx), elements) + diags.Append(d...) if diags.HasError() { return NewMapValueOfUnknown[T](ctx), diags } - return MapValueOf[T]{MapValue: mapValue}, diags -} - -func NewMapValueOfNull[T attr.Value](ctx context.Context) MapValueOf[T] { - var zero T - return MapValueOf[T]{MapValue: basetypes.NewMapNull(zero.Type(ctx))} -} - -func NewMapValueOfUnknown[T attr.Value](ctx context.Context) MapValueOf[T] { - var zero T - return MapValueOf[T]{MapValue: basetypes.NewMapUnknown(zero.Type(ctx))} + return MapValueOf[T]{MapValue: v}, diags } func NewMapValueOfMust[T attr.Value](ctx context.Context, elements map[string]attr.Value) MapValueOf[T] { diff --git a/internal/framework/types/objectof.go b/internal/framework/types/objectof.go index ff6da2d08540..2e7f2e1523b6 100644 --- a/internal/framework/types/objectof.go +++ b/internal/framework/types/objectof.go @@ -55,14 +55,20 @@ func (t objectTypeOf[T]) ValueFromObject(ctx context.Context, in basetypes.Objec return NewObjectValueOfUnknown[T](ctx), diags } - objectValue, d := basetypes.NewObjectValue(AttributeTypesMust[T](ctx), in.Attributes()) + m, d := AttributeTypes[T](ctx) + diags.Append(d...) + if diags.HasError() { + return NewObjectValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewObjectValue(m, in.Attributes()) diags.Append(d...) if diags.HasError() { return NewObjectValueOfUnknown[T](ctx), diags } value := ObjectValueOf[T]{ - ObjectValue: objectValue, + ObjectValue: v, } return value, diags @@ -108,7 +114,13 @@ func (t objectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr any) (attr. var diags diag.Diagnostics if v, ok := ptr.(*T); ok { - return NewObjectValueOf(ctx, v), diags + v, d := NewObjectValueOf(ctx, v) + diags.Append(d...) + if diags.HasError() { + return NewObjectValueOfUnknown[T](ctx), diags + } + + return v, diags } diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) @@ -168,6 +180,24 @@ func NewObjectValueOfUnknown[T any](ctx context.Context) ObjectValueOf[T] { return ObjectValueOf[T]{ObjectValue: basetypes.NewObjectUnknown(AttributeTypesMust[T](ctx))} } -func NewObjectValueOf[T any](ctx context.Context, t *T) ObjectValueOf[T] { - return ObjectValueOf[T]{ObjectValue: fwdiag.Must(basetypes.NewObjectValueFrom(ctx, AttributeTypesMust[T](ctx), t))} +func NewObjectValueOf[T any](ctx context.Context, t *T) (ObjectValueOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + m, d := AttributeTypes[T](ctx) + diags.Append(d...) + if diags.HasError() { + return NewObjectValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewObjectValueFrom(ctx, m, t) + diags.Append(d...) + if diags.HasError() { + return NewObjectValueOfUnknown[T](ctx), diags + } + + return ObjectValueOf[T]{ObjectValue: v}, diags +} + +func NewObjectValueOfMust[T any](ctx context.Context, t *T) ObjectValueOf[T] { + return fwdiag.Must(NewObjectValueOf[T](ctx, t)) } diff --git a/internal/framework/types/objectof_test.go b/internal/framework/types/objectof_test.go index 1b05d3f51e68..dc38c1a5deb1 100644 --- a/internal/framework/types/objectof_test.go +++ b/internal/framework/types/objectof_test.go @@ -95,11 +95,11 @@ func TestObjectTypeOfValueFromTerraform(t *testing.T) { }, "valid value": { tfVal: objectAValue, - wantVal: fwtypes.NewObjectValueOf[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewObjectValueOfMust[ObjectA](ctx, &objectA), }, "invalid Terraform value": { tfVal: objectBValue, - wantVal: fwtypes.NewObjectValueOf[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewObjectValueOfMust[ObjectA](ctx, &objectA), wantErr: true, }, } @@ -149,14 +149,14 @@ func TestObjectValueOfEqual(t *testing.T) { other: types.StringValue("test"), }, "equal value": { - other: fwtypes.NewObjectValueOf(ctx, &objectA), + other: fwtypes.NewObjectValueOfMust(ctx, &objectA), want: true, }, "struct not equal value": { - other: fwtypes.NewObjectValueOf(ctx, &objectA2), + other: fwtypes.NewObjectValueOfMust(ctx, &objectA2), }, "other struct value": { - other: fwtypes.NewObjectValueOf(ctx, &objectB), + other: fwtypes.NewObjectValueOfMust(ctx, &objectB), }, "null value": { other: fwtypes.NewObjectValueOfNull[ObjectA](ctx), @@ -171,7 +171,7 @@ func TestObjectValueOfEqual(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got := fwtypes.NewObjectValueOf(ctx, &objectA).Equal(testCase.other) + got := fwtypes.NewObjectValueOfMust(ctx, &objectA).Equal(testCase.other) if got != testCase.want { t.Errorf("got = %v, want = %v", got, testCase.want) diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index b8463aa4ad84..3def268d20ca 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -62,14 +62,14 @@ func (t setTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) ( return NewSetValueOfUnknown[T](ctx), diags } - setValue, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), in.Elements()) + v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), in.Elements()) diags.Append(d...) if diags.HasError() { return NewSetValueOfUnknown[T](ctx), diags } value := SetValueOf[T]{ - SetValue: setValue, + SetValue: v, } return value, diags @@ -129,7 +129,10 @@ func NewSetValueOfUnknown[T attr.Value](ctx context.Context) SetValueOf[T] { } func NewSetValueOf[T attr.Value](ctx context.Context, elements []attr.Value) (SetValueOf[T], diag.Diagnostics) { - v, diags := basetypes.NewSetValue(newAttrTypeOf[T](ctx), elements) + var diags diag.Diagnostics + + v, d := basetypes.NewSetValue(newAttrTypeOf[T](ctx), elements) + diags.Append(d...) if diags.HasError() { return NewSetValueOfUnknown[T](ctx), diags } diff --git a/internal/service/appconfig/environment.go b/internal/service/appconfig/environment.go index 16549b639d33..e5ca16ef4935 100644 --- a/internal/service/appconfig/environment.go +++ b/internal/service/appconfig/environment.go @@ -475,5 +475,5 @@ func flattenMonitorData(ctx context.Context, apiObject awstypes.Monitor) *monito } func (m *monitorData) value(ctx context.Context) types.Object { - return fwtypes.NewObjectValueOf[monitorData](ctx, m).ObjectValue + return fwtypes.NewObjectValueOfMust[monitorData](ctx, m).ObjectValue } From 3ef72e6b4c044fccede5f492f893ff81788fc236 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 14:34:15 -0500 Subject: [PATCH 15/25] Even more 'framework/types' Must-ness. --- internal/framework/flex/auto_expand_test.go | 12 ++--- internal/framework/flex/auto_flatten_test.go | 14 ++--- internal/framework/types/attrtypes.go | 4 +- .../framework/types/list_nested_objectof.go | 18 +++++-- internal/framework/types/listof.go | 6 +-- internal/framework/types/mapof.go | 6 +-- internal/framework/types/objectof.go | 18 +++++-- .../framework/types/set_nested_objectof.go | 54 +++++++++++++++---- .../types/set_nested_objectof_test.go | 12 ++--- internal/framework/types/setof.go | 6 +-- internal/service/lexv2models/intent_test.go | 4 +- 11 files changed, 95 insertions(+), 59 deletions(-) diff --git a/internal/framework/flex/auto_expand_test.go b/internal/framework/flex/auto_expand_test.go index d54e5d4cdc57..7fa1f38201db 100644 --- a/internal/framework/flex/auto_expand_test.go +++ b/internal/framework/flex/auto_expand_test.go @@ -307,7 +307,7 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "single set Source and *struct Target", - Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, + Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, Target: &TestFlexAWS06{}, WantTarget: &TestFlexAWS06{Field1: &TestFlexAWS01{Field1: "a"}}, }, @@ -367,13 +367,13 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "empty set Source and empty []*struct Target", - Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSlice(ctx, []*TestFlexTF01{})}, + Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{})}, Target: &TestFlexAWS07{}, WantTarget: &TestFlexAWS07{Field1: []*TestFlexAWS01{}}, }, { TestName: "non-empty set Source and non-empty []*struct Target", - Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSlice(ctx, []*TestFlexTF01{ + Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -385,7 +385,7 @@ func TestExpandGeneric(t *testing.T) { }, { TestName: "non-empty set Source and non-empty []struct Target", - Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{ + Source: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -408,7 +408,7 @@ func TestExpandGeneric(t *testing.T) { "X": types.StringValue("x"), "Y": types.StringValue("y"), }), - Field4: fwtypes.NewSetNestedObjectValueOfValueSlice(ctx, []TestFlexTF02{ + Field4: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF02{ {Field1: types.Int64Value(100)}, {Field1: types.Int64Value(2000)}, {Field1: types.Int64Value(30000)}, @@ -487,7 +487,7 @@ func TestExpandGeneric(t *testing.T) { { TestName: "map block key set", Source: &TestFlexMapBlockKeyTF03{ - MapBlock: fwtypes.NewSetNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), diff --git a/internal/framework/flex/auto_flatten_test.go b/internal/framework/flex/auto_flatten_test.go index ff2a52067843..4b0c46b49059 100644 --- a/internal/framework/flex/auto_flatten_test.go +++ b/internal/framework/flex/auto_flatten_test.go @@ -451,7 +451,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "*struct Source and single set Target", Source: &TestFlexAWS06{Field1: &TestFlexAWS01{Field1: "a"}}, Target: &TestFlexTF06{}, - WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, + WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &TestFlexTF01{Field1: types.StringValue("a")})}, }, { TestName: "nil []struct and null list Target", @@ -475,7 +475,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "empty []struct and empty struct Target", Source: &TestFlexAWS08{Field1: []TestFlexAWS01{}}, Target: &TestFlexTF06{}, - WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{})}, + WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{})}, }, { TestName: "non-empty []struct and non-empty list Target", @@ -496,7 +496,7 @@ func TestFlattenGeneric(t *testing.T) { {Field1: "b"}, }}, Target: &TestFlexTF06{}, - WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSlice(ctx, []TestFlexTF01{ + WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -523,7 +523,7 @@ func TestFlattenGeneric(t *testing.T) { TestName: "empty []*struct and empty set Target", Source: &TestFlexAWS07{Field1: []*TestFlexAWS01{}}, Target: &TestFlexTF06{}, - WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSlice(ctx, []*TestFlexTF01{})}, + WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{})}, }, { TestName: "non-empty []*struct and non-empty list Target", @@ -544,7 +544,7 @@ func TestFlattenGeneric(t *testing.T) { {Field1: "b"}, }}, Target: &TestFlexTF06{}, - WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSlice(ctx, []*TestFlexTF01{ + WantTarget: &TestFlexTF06{Field1: fwtypes.NewSetNestedObjectValueOfSliceMust(ctx, []*TestFlexTF01{ {Field1: types.StringValue("a")}, {Field1: types.StringValue("b")}, })}, @@ -569,7 +569,7 @@ func TestFlattenGeneric(t *testing.T) { "X": types.StringValue("x"), "Y": types.StringValue("y"), }), - Field4: fwtypes.NewSetNestedObjectValueOfValueSlice(ctx, []TestFlexTF02{ + Field4: fwtypes.NewSetNestedObjectValueOfValueSliceMust(ctx, []TestFlexTF02{ {Field1: types.Int64Value(100)}, {Field1: types.Int64Value(2000)}, {Field1: types.Int64Value(30000)}, @@ -641,7 +641,7 @@ func TestFlattenGeneric(t *testing.T) { }, Target: &TestFlexMapBlockKeyTF03{}, WantTarget: &TestFlexMapBlockKeyTF03{ - MapBlock: fwtypes.NewSetNestedObjectValueOfValueSlice[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ + MapBlock: fwtypes.NewSetNestedObjectValueOfValueSliceMust[TestFlexMapBlockKeyTF02](ctx, []TestFlexMapBlockKeyTF02{ { MapBlockKey: types.StringValue("x"), Attr1: types.StringValue("a"), diff --git a/internal/framework/types/attrtypes.go b/internal/framework/types/attrtypes.go index e75ccccf2d1a..1d909b8ac5b7 100644 --- a/internal/framework/types/attrtypes.go +++ b/internal/framework/types/attrtypes.go @@ -28,7 +28,7 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, diag.Diag } if typ.Kind() != reflect.Struct { - diags.Append(diag.NewErrorDiagnostic("Invalid value", fmt.Sprintf("%T has unsupported type: %s", t, typ))) + diags.Append(diag.NewErrorDiagnostic("Invalid type", fmt.Sprintf("%T has unsupported type: %s", t, typ))) return nil, diags } @@ -43,7 +43,7 @@ func AttributeTypes[T any](ctx context.Context) (map[string]attr.Type, diag.Diag continue // Skip explicitly excluded fields. } if tag == "" { - diags.Append(diag.NewErrorDiagnostic("Invalid value", fmt.Sprintf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name))) + diags.Append(diag.NewErrorDiagnostic("Invalid type", fmt.Sprintf(`%T needs a struct tag for "tfsdk" on %s`, t, field.Name))) return nil, diags } diff --git a/internal/framework/types/list_nested_objectof.go b/internal/framework/types/list_nested_objectof.go index 721710f91671..f9ff4d15d501 100644 --- a/internal/framework/types/list_nested_objectof.go +++ b/internal/framework/types/list_nested_objectof.go @@ -55,17 +55,19 @@ func (t listNestedObjectTypeOf[T]) ValueFromList(ctx context.Context, in basetyp return NewListNestedObjectValueOfUnknown[T](ctx), diags } - listValue, d := basetypes.NewListValue(NewObjectTypeOf[T](ctx), in.Elements()) + typ, d := newObjectTypeOf[T](ctx) diags.Append(d...) if diags.HasError() { return NewListNestedObjectValueOfUnknown[T](ctx), diags } - value := ListNestedObjectValueOf[T]{ - ListValue: listValue, + v, d := basetypes.NewListValue(typ, in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewListNestedObjectValueOfUnknown[T](ctx), diags } - return value, diags + return ListNestedObjectValueOf[T]{ListValue: v}, diags } func (t listNestedObjectTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { @@ -251,7 +253,13 @@ func NewListNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T func newListNestedObjectValueOf[T any](ctx context.Context, elements any) (ListNestedObjectValueOf[T], diag.Diagnostics) { var diags diag.Diagnostics - v, d := basetypes.NewListValueFrom(ctx, NewObjectTypeOf[T](ctx), elements) + typ, d := newObjectTypeOf[T](ctx) + diags.Append(d...) + if diags.HasError() { + return NewListNestedObjectValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewListValueFrom(ctx, typ, elements) diags.Append(d...) if diags.HasError() { return NewListNestedObjectValueOfUnknown[T](ctx), diags diff --git a/internal/framework/types/listof.go b/internal/framework/types/listof.go index 99003a737d53..9d567b4f505f 100644 --- a/internal/framework/types/listof.go +++ b/internal/framework/types/listof.go @@ -68,11 +68,7 @@ func (t listTypeOf[T]) ValueFromList(ctx context.Context, in basetypes.ListValue return basetypes.NewListUnknown(types.StringType), diags } - value := ListValueOf[T]{ - ListValue: v, - } - - return value, diags + return ListValueOf[T]{ListValue: v}, diags } func (t listTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { diff --git a/internal/framework/types/mapof.go b/internal/framework/types/mapof.go index 6bf940d97fb5..0bde7ac705f6 100644 --- a/internal/framework/types/mapof.go +++ b/internal/framework/types/mapof.go @@ -69,11 +69,7 @@ func (t mapTypeOf[T]) ValueFromMap(ctx context.Context, in basetypes.MapValue) ( return basetypes.NewMapUnknown(types.StringType), diags } - value := MapValueOf[T]{ - MapValue: mapValue, - } - - return value, diags + return MapValueOf[T]{MapValue: mapValue}, diags } func (t mapTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { diff --git a/internal/framework/types/objectof.go b/internal/framework/types/objectof.go index 2e7f2e1523b6..4a98584c6145 100644 --- a/internal/framework/types/objectof.go +++ b/internal/framework/types/objectof.go @@ -26,8 +26,20 @@ var ( _ NestedObjectValue = (*ObjectValueOf[struct{}])(nil) ) +func newObjectTypeOf[T any](ctx context.Context) (objectTypeOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + m, d := AttributeTypes[T](ctx) + diags.Append(d...) + if diags.HasError() { + return objectTypeOf[T]{}, diags + } + + return objectTypeOf[T]{basetypes.ObjectType{AttrTypes: m}}, diags +} + func NewObjectTypeOf[T any](ctx context.Context) objectTypeOf[T] { - return objectTypeOf[T]{basetypes.ObjectType{AttrTypes: AttributeTypesMust[T](ctx)}} + return fwdiag.Must(newObjectTypeOf[T](ctx)) } func (t objectTypeOf[T]) Equal(o attr.Type) bool { @@ -116,10 +128,6 @@ func (t objectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr any) (attr. if v, ok := ptr.(*T); ok { v, d := NewObjectValueOf(ctx, v) diags.Append(d...) - if diags.HasError() { - return NewObjectValueOfUnknown[T](ctx), diags - } - return v, diags } diff --git a/internal/framework/types/set_nested_objectof.go b/internal/framework/types/set_nested_objectof.go index ed00c8dc00f0..2aa215dd4ed4 100644 --- a/internal/framework/types/set_nested_objectof.go +++ b/internal/framework/types/set_nested_objectof.go @@ -55,17 +55,19 @@ func (t setNestedObjectTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes return NewSetNestedObjectValueOfUnknown[T](ctx), diags } - setValue, d := basetypes.NewSetValue(NewObjectTypeOf[T](ctx), in.Elements()) + typ, d := newObjectTypeOf[T](ctx) diags.Append(d...) if diags.HasError() { return NewSetNestedObjectValueOfUnknown[T](ctx), diags } - value := SetNestedObjectValueOf[T]{ - SetValue: setValue, + v, d := basetypes.NewSetValue(typ, in.Elements()) + diags.Append(d...) + if diags.HasError() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags } - return value, diags + return SetNestedObjectValueOf[T]{SetValue: v}, diags } func (t setNestedObjectTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { @@ -112,7 +114,9 @@ func (t setNestedObjectTypeOf[T]) ValueFromObjectPtr(ctx context.Context, ptr an var diags diag.Diagnostics if v, ok := ptr.(*T); ok { - return NewSetNestedObjectValueOfPtr(ctx, v), diags + v, d := NewSetNestedObjectValueOfPtr(ctx, v) + diags.Append(d...) + return v, d } diags.Append(diag.NewErrorDiagnostic("Invalid pointer value", fmt.Sprintf("incorrect type: want %T, got %T", (*T)(nil), ptr))) @@ -123,7 +127,9 @@ func (t setNestedObjectTypeOf[T]) ValueFromObjectSlice(ctx context.Context, slic var diags diag.Diagnostics if v, ok := slice.([]*T); ok { - return NewSetNestedObjectValueOfSlice(ctx, v), diags + v, d := NewSetNestedObjectValueOfSlice(ctx, v) + diags.Append(d...) + return v, d } diags.Append(diag.NewErrorDiagnostic("Invalid slice value", fmt.Sprintf("incorrect type: want %T, got %T", (*[]T)(nil), slice))) @@ -175,18 +181,44 @@ func NewSetNestedObjectValueOfUnknown[T any](ctx context.Context) SetNestedObjec return SetNestedObjectValueOf[T]{SetValue: basetypes.NewSetUnknown(NewObjectTypeOf[T](ctx))} } -func NewSetNestedObjectValueOfPtr[T any](ctx context.Context, t *T) SetNestedObjectValueOf[T] { +func NewSetNestedObjectValueOfPtr[T any](ctx context.Context, t *T) (SetNestedObjectValueOf[T], diag.Diagnostics) { return NewSetNestedObjectValueOfSlice(ctx, []*T{t}) } -func NewSetNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) SetNestedObjectValueOf[T] { +func NewSetNestedObjectValueOfPtrMust[T any](ctx context.Context, t *T) SetNestedObjectValueOf[T] { + return fwdiag.Must(NewSetNestedObjectValueOfPtr(ctx, t)) +} + +func NewSetNestedObjectValueOfSlice[T any](ctx context.Context, ts []*T) (SetNestedObjectValueOf[T], diag.Diagnostics) { return newSetNestedObjectValueOf[T](ctx, ts) } -func NewSetNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) SetNestedObjectValueOf[T] { +func NewSetNestedObjectValueOfSliceMust[T any](ctx context.Context, ts []*T) SetNestedObjectValueOf[T] { + return fwdiag.Must(NewSetNestedObjectValueOfSlice(ctx, ts)) +} + +func NewSetNestedObjectValueOfValueSlice[T any](ctx context.Context, ts []T) (SetNestedObjectValueOf[T], diag.Diagnostics) { return newSetNestedObjectValueOf[T](ctx, ts) } -func newSetNestedObjectValueOf[T any](ctx context.Context, elements any) SetNestedObjectValueOf[T] { - return SetNestedObjectValueOf[T]{SetValue: fwdiag.Must(basetypes.NewSetValueFrom(ctx, NewObjectTypeOf[T](ctx), elements))} +func NewSetNestedObjectValueOfValueSliceMust[T any](ctx context.Context, ts []T) SetNestedObjectValueOf[T] { + return fwdiag.Must(NewSetNestedObjectValueOfValueSlice(ctx, ts)) +} + +func newSetNestedObjectValueOf[T any](ctx context.Context, elements any) (SetNestedObjectValueOf[T], diag.Diagnostics) { + var diags diag.Diagnostics + + typ, d := newObjectTypeOf[T](ctx) + diags.Append(d...) + if diags.HasError() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags + } + + v, d := basetypes.NewSetValueFrom(ctx, typ, elements) + diags.Append(d...) + if diags.HasError() { + return NewSetNestedObjectValueOfUnknown[T](ctx), diags + } + + return SetNestedObjectValueOf[T]{SetValue: v}, diags } diff --git a/internal/framework/types/set_nested_objectof_test.go b/internal/framework/types/set_nested_objectof_test.go index 12c6b9ea8f75..b6f6b9d40a70 100644 --- a/internal/framework/types/set_nested_objectof_test.go +++ b/internal/framework/types/set_nested_objectof_test.go @@ -90,11 +90,11 @@ func TestSetNestedObjectTypeOfValueFromTerraform(t *testing.T) { }, "valid value": { tfVal: objectASetValue, - wantVal: fwtypes.NewSetNestedObjectValueOfPtr[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewSetNestedObjectValueOfPtrMust[ObjectA](ctx, &objectA), }, "invalid Terraform value": { tfVal: objectBSetValue, - wantVal: fwtypes.NewSetNestedObjectValueOfPtr[ObjectA](ctx, &objectA), + wantVal: fwtypes.NewSetNestedObjectValueOfPtrMust[ObjectA](ctx, &objectA), wantErr: true, }, } @@ -144,14 +144,14 @@ func TestSetNestedObjectValueOfEqual(t *testing.T) { other: types.StringValue("test"), }, "equal value": { - other: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &objectA), + other: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &objectA), want: true, }, "struct not equal value": { - other: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &objectA2), + other: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &objectA2), }, "other struct value": { - other: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &objectB), + other: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &objectB), }, "null value": { other: fwtypes.NewSetNestedObjectValueOfNull[ObjectA](ctx), @@ -166,7 +166,7 @@ func TestSetNestedObjectValueOfEqual(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - got := fwtypes.NewSetNestedObjectValueOfPtr(ctx, &objectA).Equal(testCase.other) + got := fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &objectA).Equal(testCase.other) if got != testCase.want { t.Errorf("got = %v, want = %v", got, testCase.want) diff --git a/internal/framework/types/setof.go b/internal/framework/types/setof.go index 3def268d20ca..bc6720f6583f 100644 --- a/internal/framework/types/setof.go +++ b/internal/framework/types/setof.go @@ -68,11 +68,7 @@ func (t setTypeOf[T]) ValueFromSet(ctx context.Context, in basetypes.SetValue) ( return NewSetValueOfUnknown[T](ctx), diags } - value := SetValueOf[T]{ - SetValue: v, - } - - return value, diags + return SetValueOf[T]{SetValue: v}, diags } func (t setTypeOf[T]) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { diff --git a/internal/service/lexv2models/intent_test.go b/internal/service/lexv2models/intent_test.go index 0e9ce216860f..a91f30046819 100644 --- a/internal/service/lexv2models/intent_test.go +++ b/internal/service/lexv2models/intent_test.go @@ -182,7 +182,7 @@ func TestIntentAutoFlex(t *testing.T) { intentOverrideTF := tflexv2models.IntentOverride{ Name: types.StringValue(testString), - Slot: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &slotValueOverrideMapTF), + Slot: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &slotValueOverrideMapTF), } intentOverrideAWS := lextypes.IntentOverride{ Name: aws.String(testString), @@ -332,7 +332,7 @@ func TestIntentAutoFlex(t *testing.T) { MessageGroup: fwtypes.NewListNestedObjectValueOfPtrMust(ctx, &messageGroupTF), AllowInterrupt: types.BoolValue(true), MessageSelectionStrategy: fwtypes.StringEnumValue(lextypes.MessageSelectionStrategyOrdered), - PromptAttemptsSpecification: fwtypes.NewSetNestedObjectValueOfPtr(ctx, &promptAttemptSpecificationTF), + PromptAttemptsSpecification: fwtypes.NewSetNestedObjectValueOfPtrMust(ctx, &promptAttemptSpecificationTF), } promptSpecificationAWS := lextypes.PromptSpecification{ MaxRetries: aws.Int32(1), From d356157de19dfc116f4b465a0d3ee79e760ff1dc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 17:01:30 -0500 Subject: [PATCH 16/25] r/aws_shield_drt_access_log_bucket_association: Change `log_bucket` and `role_arn_association_id` to ForceNew. --- .changelog/34667.txt | 8 + .../drt_access_log_bucket_association.go | 347 ++++++------------ internal/service/shield/exports_test.go | 3 +- .../service/shield/proactive_engagement.go | 7 + .../service/shield/service_package_gen.go | 2 +- ...ccess_log_bucket_association.html.markdown | 28 +- 6 files changed, 150 insertions(+), 245 deletions(-) diff --git a/.changelog/34667.txt b/.changelog/34667.txt index 8b86558133e3..084e995dd7f9 100644 --- a/.changelog/34667.txt +++ b/.changelog/34667.txt @@ -1,3 +1,11 @@ ```release-note:new-resource aws_shield_proactive_engagement +``` + +```release-note:bug +resource/aws_shield_drt_access_log_bucket_association: Change `log_bucket` and `role_arn_association_id` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) +``` + +```release-note:enhancement +resource/aws_shield_drt_access_log_bucket_association: Support resource import ``` \ No newline at end of file diff --git a/internal/service/shield/drt_access_log_bucket_association.go b/internal/service/shield/drt_access_log_bucket_association.go index f3869863ade5..378eb4b8380d 100644 --- a/internal/service/shield/drt_access_log_bucket_association.go +++ b/internal/service/shield/drt_access_log_bucket_association.go @@ -5,7 +5,7 @@ package shield import ( "context" - "errors" + "fmt" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -15,333 +15,200 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @FrameworkResource(name="DRT Access Log Bucket Association") -func newResourceDRTAccessLogBucketAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceDRTAccessLogBucketAssociation{} +// @FrameworkResource(name="DRT Log Bucket Association") +func newDRTAccessLogBucketAssociationResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &drtAccessLogBucketAssociationResource{} r.SetDefaultCreateTimeout(30 * time.Minute) - r.SetDefaultUpdateTimeout(30 * time.Minute) r.SetDefaultDeleteTimeout(30 * time.Minute) return r, nil } -const ( - ResNameDRTAccessLogBucketAssociation = "DRT Access Log Bucket Association" -) - -type resourceDRTAccessLogBucketAssociation struct { +type drtAccessLogBucketAssociationResource struct { framework.ResourceWithConfigure + framework.WithNoUpdate + framework.WithImportByID framework.WithTimeouts } -func (r *resourceDRTAccessLogBucketAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_shield_drt_access_log_bucket_association" +func (r *drtAccessLogBucketAssociationResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_shield_drt_access_log_bucket_association" } -func (r *resourceDRTAccessLogBucketAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *drtAccessLogBucketAssociationResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": framework.IDAttribute(), + names.AttrID: framework.IDAttribute(), "log_bucket": schema.StringAttribute{ Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Validators: []validator.String{ - // Validate string value length must be at least 3 characters and max 63. stringvalidator.LengthBetween(3, 63), }, }, "role_arn_association_id": schema.StringAttribute{ Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: `Unused`, }, }, Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, timeouts.Opts{ Create: true, Delete: true, - Read: true, }), }, } } -func (r *resourceDRTAccessLogBucketAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().ShieldClient(ctx) - - var plan resourceDRTAccessLogBucketAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *drtAccessLogBucketAssociationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data drtAccessLogBucketAssociationResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.AssociateDRTLogBucketInput{ - LogBucket: flex.StringFromFramework(ctx, plan.LogBucket), + conn := r.Meta().ShieldClient(ctx) + + logBucket := data.LogBucket.ValueString() + input := &shield.AssociateDRTLogBucketInput{ + LogBucket: aws.String(logBucket), } - out, err := conn.AssociateDRTLogBucket(ctx, in) + + _, err := conn.AssociateDRTLogBucket(ctx, input) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), - err.Error(), - ) - return - } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), nil), - errors.New("empty output").Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("creating Shield DRT Log Bucket Association (%s)", logBucket), err.Error()) + return } - createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitDRTAccessLogBucketAssociationCreated(ctx, conn, plan.LogBucket.ValueString(), createTimeout) + // Set values for unknowns. + data.setID() + + _, err = tfresource.RetryWhenNotFound(ctx, r.CreateTimeout(ctx, data.Timeouts), func() (interface{}, error) { + return findDRTLogBucketAssociation(ctx, conn, logBucket) + }) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield DRT Log Bucket Association (%s) create", logBucket), err.Error()) + return } - plan.ID = plan.LogBucket - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *resourceDRTAccessLogBucketAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceDRTAccessLogBucketAssociationData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *drtAccessLogBucketAssociationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data drtAccessLogBucketAssociationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.DescribeDRTAccessInput{} - out, err := conn.DescribeDRTAccess(ctx, in) - if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) - return - } + if err := data.InitFromID(); err != nil { + response.Diagnostics.AddError("parsing resource ID", err.Error()) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), - err.Error(), - ) return } - var associatedLogBucket *string - if out != nil { - associatedLogBucket = getAssociatedLogBucket(state.LogBucket.ValueString(), out.LogBucketList) - if len(out.LogBucketList) > 0 && associatedLogBucket == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), nil), - errors.New("log Bucket not in list").Error(), - ) - } - } - if state.ID.IsNull() || state.ID.IsUnknown() { - // Setting ID of state - required by hashicorps terraform plugin testing framework for Import. See issue https://github.com/hashicorp/terraform-plugin-testing/issues/84 - state.ID = state.LogBucket - } - state.LogBucket = flex.StringToFramework(ctx, associatedLogBucket) + conn := r.Meta().ShieldClient(ctx) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} + logBucket := data.ID.ValueString() + _, err := findDRTLogBucketAssociation(ctx, conn, logBucket) -func (r *resourceDRTAccessLogBucketAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().ShieldClient(ctx) + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) - var plan, state resourceDRTAccessLogBucketAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { return } - if !plan.LogBucket.Equal(state.LogBucket) { - in := &shield.AssociateDRTLogBucketInput{ - LogBucket: flex.StringFromFramework(ctx, plan.LogBucket), - } - out, err := conn.AssociateDRTLogBucket(ctx, in) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), - err.Error(), - ) - return - } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), nil), - errors.New("empty output").Error(), - ) - return - } - } - - updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err := waitDRTAccessLogBucketAssociationUpdated(ctx, conn, plan.LogBucket.ValueString(), updateTimeout) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameDRTAccessLogBucketAssociation, plan.LogBucket.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Shield DRT Log Bucket Association (%s)", logBucket), err.Error()) + return } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *resourceDRTAccessLogBucketAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceDRTAccessLogBucketAssociationData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - if state.LogBucket.ValueString() == "" { +func (r *drtAccessLogBucketAssociationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data drtAccessLogBucketAssociationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.DisassociateDRTLogBucketInput{ - LogBucket: aws.String(state.LogBucket.ValueString()), + conn := r.Meta().ShieldClient(ctx) + + logBucket := data.ID.ValueString() + input := &shield.DisassociateDRTLogBucketInput{ + LogBucket: aws.String(logBucket), } - _, err := conn.DisassociateDRTLogBucket(ctx, in) + _, err := conn.DisassociateDRTLogBucket(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("deleting Shield DRT Log Bucket Association (%s)", logBucket), err.Error()) - deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) - _, err = waitDRTAccessLogBucketAssociationDeleted(ctx, conn, state.LogBucket.ValueString(), deleteTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameDRTAccessLogBucketAssociation, state.LogBucket.String(), err), - err.Error(), - ) return } -} -const ( - statusChangePending = "Pending" - statusDeleting = "Deleting" - statusNormal = "Normal" - statusUpdated = "Updated" -) - -func waitDRTAccessLogBucketAssociationCreated(ctx context.Context, conn *shield.Client, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{statusNormal}, - Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), - Timeout: timeout, - NotFoundChecks: 2, - ContinuousTargetOccurence: 2, - } + _, err = tfresource.RetryUntilNotFound(ctx, r.DeleteTimeout(ctx, data.Timeouts), func() (interface{}, error) { + return findDRTLogBucketAssociation(ctx, conn, logBucket) + }) - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err - } - - return nil, err -} - -func waitDRTAccessLogBucketAssociationUpdated(ctx context.Context, conn *shield.Client, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated}, - Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), - Timeout: timeout, - NotFoundChecks: 2, - ContinuousTargetOccurence: 2, - } + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield DRT Log Bucket Association (%s) delete", logBucket), err.Error()) - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err + return } - - return nil, err } -func waitDRTAccessLogBucketAssociationDeleted(ctx context.Context, conn *shield.Client, bucket string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, - Target: []string{}, - Refresh: statusDRTAccessLogBucketAssociation(ctx, conn, bucket), - Timeout: timeout, - } +func findDRTLogBucketAssociation(ctx context.Context, conn *shield.Client, name string) (*string, error) { + output, err := findDRTAccess(ctx, conn) - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err + if err != nil { + return nil, err } - return nil, err -} - -func statusDRTAccessLogBucketAssociation(ctx context.Context, conn *shield.Client, bucket string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeDRTAccessLogBucketAssociation(ctx, conn, bucket) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - if out == nil || out.LogBucketList == nil || len(out.LogBucketList) == 0 { - return nil, "", nil - } - - if out != nil && len(out.LogBucketList) > 0 { - for _, bkt := range out.LogBucketList { - if bkt == bucket { - return out, statusNormal, nil - } - } - return nil, "", nil - } - - return out, statusNormal, nil - } + return tfresource.AssertSingleValueResult(tfslices.Filter(output.LogBucketList, func(v string) bool { + return v == name + })) } -func describeDRTAccessLogBucketAssociation(ctx context.Context, conn *shield.Client, bucketName string) (*shield.DescribeDRTAccessOutput, error) { - in := &shield.DescribeDRTAccessInput{} +func findDRTAccess(ctx context.Context, conn *shield.Client) (*shield.DescribeDRTAccessOutput, error) { + input := &shield.DescribeDRTAccessInput{} - out, err := conn.DescribeDRTAccess(ctx, in) + output, err := conn.DescribeDRTAccess(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, - LastRequest: in, + LastRequest: input, } } @@ -349,30 +216,26 @@ func describeDRTAccessLogBucketAssociation(ctx context.Context, conn *shield.Cli return nil, err } - if out == nil || out.LogBucketList == nil || len(out.LogBucketList) == 0 { - return nil, tfresource.NewEmptyResultError(in) + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - for _, bucket := range out.LogBucketList { - if bucket == bucketName { - return out, nil - } - } - return nil, err + return output, nil } -func getAssociatedLogBucket(bucket string, bucketList []string) *string { - for _, bkt := range bucketList { - if bkt == bucket { - return &bkt - } - } - return nil -} - -type resourceDRTAccessLogBucketAssociationData struct { +type drtAccessLogBucketAssociationResourceModel struct { ID types.String `tfsdk:"id"` - RoleArnAssociationID types.String `tfsdk:"role_arn_association_id"` LogBucket types.String `tfsdk:"log_bucket"` + RoleARNAssociationID types.String `tfsdk:"role_arn_association_id"` Timeouts timeouts.Value `tfsdk:"timeouts"` } + +func (model *drtAccessLogBucketAssociationResourceModel) InitFromID() error { + model.LogBucket = model.ID + + return nil +} + +func (model *drtAccessLogBucketAssociationResourceModel) setID() { + model.ID = model.LogBucket +} diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index 9adfed69366b..697822063d01 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -6,9 +6,10 @@ package shield // Exports for use in tests only. var ( ResourceDRTAccessRoleARNAssociation = newResourceDRTAccessRoleARNAssociation - ResourceDRTAccessLogBucketAssociation = newResourceDRTAccessLogBucketAssociation + ResourceDRTAccessLogBucketAssociation = newDRTAccessLogBucketAssociationResource ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse ResourceProactiveEngagement = newProactiveEngagementResource + FindDRTLogBucketAssociation = findDRTLogBucketAssociation FindEmergencyContactSettings = findEmergencyContactSettings ) diff --git a/internal/service/shield/proactive_engagement.go b/internal/service/shield/proactive_engagement.go index 5607be49d719..dfa75942c2bd 100644 --- a/internal/service/shield/proactive_engagement.go +++ b/internal/service/shield/proactive_engagement.go @@ -107,6 +107,7 @@ func (r *proactiveEngagementResource) Create(ctx context.Context, request resour return } + // Set values for unknowns. data.ID = types.StringValue(r.Meta().AccountID) response.Diagnostics.Append(updateEmergencyContactSettings(ctx, conn, &data)...) @@ -150,6 +151,12 @@ func (r *proactiveEngagementResource) Read(ctx context.Context, request resource return } + if err != nil { + response.Diagnostics.AddError("reading Shield Proactive Engagement", err.Error()) + + return + } + data.EmergencyContactList = fwtypes.NewListNestedObjectValueOfValueSliceMust[emergencyContactModel](ctx, tfslices.ApplyToAll(emergencyContacts, func(apiObject awstypes.EmergencyContact) emergencyContactModel { return emergencyContactModel{ ContactNotes: fwflex.StringToFramework(ctx, apiObject.ContactNotes), diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index 76140307f8e1..0bf292e7a8b6 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -27,7 +27,7 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Name: "Application Layer Automatic Response", }, { - Factory: newResourceDRTAccessLogBucketAssociation, + Factory: newDRTAccessLogBucketAssociationResource, Name: "DRT Access Log Bucket Association", }, { diff --git a/website/docs/r/shield_drt_access_log_bucket_association.html.markdown b/website/docs/r/shield_drt_access_log_bucket_association.html.markdown index bc391f86ba3b..48ba0fd33db4 100644 --- a/website/docs/r/shield_drt_access_log_bucket_association.html.markdown +++ b/website/docs/r/shield_drt_access_log_bucket_association.html.markdown @@ -5,9 +5,11 @@ page_title: "AWS: aws_shield_drt_access_log_bucket_association" description: |- Terraform resource for managing an AWS Shield DRT Access Log Bucket Association. --- + # Resource: aws_shield_drt_access_log_bucket_association -Terraform resource for managing an AWS Shield DRT Access Log Bucket Association. Up to 10 log buckets can be associated for DRT Access sharing with the Shield Response Team (SRT). +Terraform resource for managing an AWS Shield DRT Access Log Bucket Association. +Up to 10 log buckets can be associated for DRT Access sharing with the Shield Response Team (SRT). ## Example Usage @@ -34,3 +36,27 @@ The following arguments are required: ## Attribute Reference This resource exports no additional attributes. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Shield DRT access log bucket associations using the `log_bucket`. For example: + +```terraform +import { + to = aws_shield_drt_access_log_bucket_association.example + id = "example-bucket" +} +``` + +Using `terraform import`, import Shield DRT access log bucket associations using the `log_bucket`. For example: + +```console +% terraform import aws_shield_drt_access_log_bucket_association.example example-bucket +``` From 13f96c48b697fb7c379f04daabed90c6b84a6eeb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Wed, 28 Feb 2024 17:33:51 -0500 Subject: [PATCH 17/25] r/aws_shield_drt_access_role_arn_association: Support resource import. --- .changelog/34667.txt | 4 + internal/service/shield/consts.go | 12 + .../shield/drt_access_role_arn_association.go | 325 ++++++------------ internal/service/shield/exports_test.go | 3 +- .../service/shield/proactive_engagement.go | 6 - .../service/shield/service_package_gen.go | 16 +- ..._access_role_arn_association.html.markdown | 31 +- 7 files changed, 155 insertions(+), 242 deletions(-) create mode 100644 internal/service/shield/consts.go diff --git a/.changelog/34667.txt b/.changelog/34667.txt index 084e995dd7f9..be3d7ec81be5 100644 --- a/.changelog/34667.txt +++ b/.changelog/34667.txt @@ -8,4 +8,8 @@ resource/aws_shield_drt_access_log_bucket_association: Change `log_bucket` and ` ```release-note:enhancement resource/aws_shield_drt_access_log_bucket_association: Support resource import +``` + +```release-note:enhancement +resource/aws_shield_drt_access_role_arn_association: Support resource import ``` \ No newline at end of file diff --git a/internal/service/shield/consts.go b/internal/service/shield/consts.go new file mode 100644 index 000000000000..88c18b084dc9 --- /dev/null +++ b/internal/service/shield/consts.go @@ -0,0 +1,12 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "time" +) + +const ( + propagationTimeout = 2 * time.Minute +) diff --git a/internal/service/shield/drt_access_role_arn_association.go b/internal/service/shield/drt_access_role_arn_association.go index aaddccf448cb..bd0453dee00d 100644 --- a/internal/service/shield/drt_access_role_arn_association.go +++ b/internal/service/shield/drt_access_role_arn_association.go @@ -5,7 +5,7 @@ package shield import ( "context" - "errors" + "fmt" "time" "github.com/aws/aws-sdk-go-v2/aws" @@ -15,19 +15,17 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + 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" ) -// Function annotations are used for resource registration to the Provider. DO NOT EDIT. -// @FrameworkResource(name="DRT Access Role ARN Association") -func newResourceDRTAccessRoleARNAssociation(_ context.Context) (resource.ResourceWithConfigure, error) { +// @FrameworkResource(name="DRT Role ARN Association") +func newDRTAccessRoleARNAssociationResource(context.Context) (resource.ResourceWithConfigure, error) { r := &resourceDRTAccessRoleARNAssociation{} r.SetDefaultCreateTimeout(30 * time.Minute) @@ -37,27 +35,20 @@ func newResourceDRTAccessRoleARNAssociation(_ context.Context) (resource.Resourc return r, nil } -const ( - ResNameDRTAccessRoleARNAssociation = "DRT Access Role ARN Association" -) - -const ( - propagationTimeout = 2 * time.Minute -) - type resourceDRTAccessRoleARNAssociation struct { framework.ResourceWithConfigure + framework.WithImportByID framework.WithTimeouts } -func (r *resourceDRTAccessRoleARNAssociation) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_shield_drt_access_role_arn_association" +func (r *resourceDRTAccessRoleARNAssociation) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_shield_drt_access_role_arn_association" } -func (r *resourceDRTAccessRoleARNAssociation) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *resourceDRTAccessRoleARNAssociation) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": framework.IDAttribute(), + names.AttrID: framework.IDAttribute(), "role_arn": schema.StringAttribute{ CustomType: fwtypes.ARNType, Required: true, @@ -66,290 +57,174 @@ func (r *resourceDRTAccessRoleARNAssociation) Schema(ctx context.Context, req re Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, timeouts.Opts{ Create: true, + Update: true, Delete: true, - Read: true, }), }, } } -func (r *resourceDRTAccessRoleARNAssociation) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().ShieldClient(ctx) - - var plan resourceDRTAccessRoleARNAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *resourceDRTAccessRoleARNAssociation) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data drtAccessRoleARNAssociationResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.AssociateDRTRoleInput{ - RoleArn: flex.StringFromFramework(ctx, plan.RoleARN), + conn := r.Meta().ShieldClient(ctx) + + roleARN := data.RoleARN.ValueString() + input := &shield.AssociateDRTRoleInput{ + RoleArn: aws.String(roleARN), } - outputRaw, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) { - return conn.AssociateDRTRole(ctx, in) - }, "InvalidParameterException", "role does not have a valid DRT managed policy") + _, err := tfresource.RetryWhenIsAErrorMessageContains[*awstypes.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.AssociateDRTRole(ctx, input) + }, "role does not have a valid DRT managed policy") if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), - err.Error(), - ) - return - } - - out := outputRaw.(*shield.AssociateDRTRoleOutput) + response.Diagnostics.AddError(fmt.Sprintf("creating Shield DRT Role ARN Association (%s)", roleARN), err.Error()) - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), nil), - errors.New("empty output").Error(), - ) return } - createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitDRTAccessRoleARNAssociationCreated(ctx, conn, plan.RoleARN.ValueString(), createTimeout) + // Set values for unknowns. + data.ID = types.StringValue(r.Meta().AccountID) + + _, err = tfresource.RetryWhenNotFound(ctx, r.CreateTimeout(ctx, data.Timeouts), func() (interface{}, error) { + return findDRTRoleARNAssociation(ctx, conn, roleARN) + }) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield DRT Role ARN Association (%s) create", roleARN), err.Error()) + return } - plan.ID = flex.StringValueToFramework(ctx, plan.RoleARN.ValueString()) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *resourceDRTAccessRoleARNAssociation) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceDRTAccessRoleARNAssociationData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *resourceDRTAccessRoleARNAssociation) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data drtAccessRoleARNAssociationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.DescribeDRTAccessInput{} - out, err := conn.DescribeDRTAccess(ctx, in) + conn := r.Meta().ShieldClient(ctx) + + output, err := findDRTAccess(ctx, conn) if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError("reading Shield DRT Access", err.Error()) + return } - if state.ID.IsNull() || state.ID.IsUnknown() { - // Setting ID of state - required by hashicorps terraform plugin testing framework for Import. See issue https://github.com/hashicorp/terraform-plugin-testing/issues/84 - state.ID = flex.StringValueToFramework(ctx, state.RoleARN.ValueString()) - } - state.RoleARN = flex.StringToFrameworkARN(ctx, out.RoleArn) - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} + data.RoleARN = fwflex.StringToFrameworkARN(ctx, output.RoleArn) -func (r *resourceDRTAccessRoleARNAssociation) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().ShieldClient(ctx) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} - var plan, state resourceDRTAccessRoleARNAssociationData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *resourceDRTAccessRoleARNAssociation) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new drtAccessRoleARNAssociationResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { return } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + conn := r.Meta().ShieldClient(ctx) - if !plan.RoleARN.Equal(state.RoleARN) { - in := &shield.AssociateDRTRoleInput{ - RoleArn: flex.StringFromFramework(ctx, plan.RoleARN), + if !new.RoleARN.Equal(old.RoleARN) { + roleARN := new.RoleARN.ValueString() + input := &shield.AssociateDRTRoleInput{ + RoleArn: aws.String(roleARN), } - out, err := conn.AssociateDRTRole(ctx, in) + _, err := tfresource.RetryWhenIsAErrorMessageContains[*awstypes.InvalidParameterException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.AssociateDRTRole(ctx, input) + }, "role does not have a valid DRT managed policy") + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("updating Shield DRT Role ARN Association (%s)", roleARN), err.Error()) + return } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), nil), - errors.New("empty output").Error(), - ) + + _, err = tfresource.RetryWhenNotFound(ctx, r.UpdateTimeout(ctx, new.Timeouts), func() (interface{}, error) { + return findDRTRoleARNAssociation(ctx, conn, roleARN) + }) + + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield DRT Role ARN Association (%s) update", roleARN), err.Error()) + return } } - updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err := waitDRTAccessRoleARNAssociationUpdated(ctx, conn, plan.RoleARN.ValueString(), updateTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameDRTAccessRoleARNAssociation, plan.RoleARN.String(), err), - err.Error(), - ) + response.Diagnostics.Append(response.State.Set(ctx, &new)...) +} + +func (r *resourceDRTAccessRoleARNAssociation) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data drtAccessRoleARNAssociationResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) -} - -func (r *resourceDRTAccessRoleARNAssociation) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().ShieldClient(ctx) - var state resourceDRTAccessRoleARNAssociationData + roleARN := data.RoleARN.ValueString() + input := &shield.DisassociateDRTRoleInput{} - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - in := &shield.DisassociateDRTRoleInput{} - - _, err := conn.DisassociateDRTRole(ctx, in) + _, err := conn.DisassociateDRTRole(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("deleting Shield DRT Role ARN Association (%s)", roleARN), err.Error()) - deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) - _, err = waitDRTAccessRoleARNAssociationDeleted(ctx, conn, state.RoleARN.ValueString(), deleteTimeout) - - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameDRTAccessRoleARNAssociation, state.RoleARN.String(), err), - err.Error(), - ) return } -} - -func waitDRTAccessRoleARNAssociationCreated(ctx context.Context, conn *shield.Client, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{statusNormal}, - Refresh: statusDRTAccessRoleARNAssociation(ctx, conn, roleARN), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err - } - - return nil, err -} - -func waitDRTAccessRoleARNAssociationUpdated(ctx context.Context, conn *shield.Client, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated}, - Refresh: statusDRTAccessRoleARNAssociation(ctx, conn, roleARN), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err - } - - return nil, err -} - -func waitDRTAccessRoleARNAssociationDeleted(ctx context.Context, conn *shield.Client, roleARN string, timeout time.Duration) (*shield.DescribeDRTAccessOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, - Target: []string{}, - Refresh: statusDRTAccessRoleARNAssociationDeleted(ctx, conn, roleARN), - Timeout: timeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) + _, err = tfresource.RetryUntilNotFound(ctx, r.DeleteTimeout(ctx, data.Timeouts), func() (interface{}, error) { + return findDRTRoleARNAssociation(ctx, conn, roleARN) + }) - if out, ok := outputRaw.(*shield.DescribeDRTAccessOutput); ok { - return out, err - } - - return nil, err -} - -func statusDRTAccessRoleARNAssociation(ctx context.Context, conn *shield.Client, roleARN string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeDRTAccessRoleARNAssociation(ctx, conn, roleARN) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - return out, statusNormal, nil - } -} - -func statusDRTAccessRoleARNAssociationDeleted(ctx context.Context, conn *shield.Client, roleARN string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeDRTAccessRoleARNAssociation(ctx, conn, roleARN) - - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - if out.RoleArn != nil && aws.ToString(out.RoleArn) == roleARN { - return out, statusDeleting, nil - } + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield DRT Role ARN Association (%s) delete", roleARN), err.Error()) - return out, statusDeleting, nil + return } } -func describeDRTAccessRoleARNAssociation(ctx context.Context, conn *shield.Client, roleARN string) (*shield.DescribeDRTAccessOutput, error) { - in := &shield.DescribeDRTAccessInput{} - - out, err := conn.DescribeDRTAccess(ctx, in) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } +func findDRTRoleARNAssociation(ctx context.Context, conn *shield.Client, arn string) (*string, error) { + output, err := findDRTAccess(ctx, conn) if err != nil { return nil, err } - if out == nil || out.RoleArn == nil || aws.ToString(out.RoleArn) != roleARN { - return nil, tfresource.NewEmptyResultError(in) + if aws.ToString(output.RoleArn) != arn { + return nil, tfresource.NewEmptyResultError(nil) } - return out, nil + return output.RoleArn, nil } -type resourceDRTAccessRoleARNAssociationData struct { +type drtAccessRoleARNAssociationResourceModel struct { ID types.String `tfsdk:"id"` RoleARN fwtypes.ARN `tfsdk:"role_arn"` Timeouts timeouts.Value `tfsdk:"timeouts"` diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index 697822063d01..b5ad46c0cce6 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -5,11 +5,12 @@ package shield // Exports for use in tests only. var ( - ResourceDRTAccessRoleARNAssociation = newResourceDRTAccessRoleARNAssociation + ResourceDRTAccessRoleARNAssociation = newDRTAccessRoleARNAssociationResource ResourceDRTAccessLogBucketAssociation = newDRTAccessLogBucketAssociationResource ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse ResourceProactiveEngagement = newProactiveEngagementResource FindDRTLogBucketAssociation = findDRTLogBucketAssociation + FindDRTRoleARNAssociation = findDRTRoleARNAssociation FindEmergencyContactSettings = findEmergencyContactSettings ) diff --git a/internal/service/shield/proactive_engagement.go b/internal/service/shield/proactive_engagement.go index dfa75942c2bd..b62901bef97c 100644 --- a/internal/service/shield/proactive_engagement.go +++ b/internal/service/shield/proactive_engagement.go @@ -200,12 +200,6 @@ func (r *proactiveEngagementResource) Update(ctx context.Context, request resour } func (r *proactiveEngagementResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - var data proactiveEngagementResourceModel - response.Diagnostics.Append(request.State.Get(ctx, &data)...) - if response.Diagnostics.HasError() { - return - } - conn := r.Meta().ShieldClient(ctx) inputD := &shield.DisableProactiveEngagementInput{} diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index 0bf292e7a8b6..abeb5aa8edec 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -19,20 +19,20 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newProactiveEngagementResource, - Name: "Proactive Engagement", + Factory: newDRTAccessLogBucketAssociationResource, + Name: "DRT Log Bucket Association", }, { - Factory: newResourceApplicationLayerAutomaticResponse, - Name: "Application Layer Automatic Response", + Factory: newDRTAccessRoleARNAssociationResource, + Name: "DRT Role ARN Association", }, { - Factory: newDRTAccessLogBucketAssociationResource, - Name: "DRT Access Log Bucket Association", + Factory: newProactiveEngagementResource, + Name: "Proactive Engagement", }, { - Factory: newResourceDRTAccessRoleARNAssociation, - Name: "DRT Access Role ARN Association", + Factory: newResourceApplicationLayerAutomaticResponse, + Name: "Application Layer Automatic Response", }, } } diff --git a/website/docs/r/shield_drt_access_role_arn_association.html.markdown b/website/docs/r/shield_drt_access_role_arn_association.html.markdown index efdc1437e7d9..ad662c8dc9ed 100644 --- a/website/docs/r/shield_drt_access_role_arn_association.html.markdown +++ b/website/docs/r/shield_drt_access_role_arn_association.html.markdown @@ -3,11 +3,13 @@ subcategory: "Shield" layout: "aws" page_title: "AWS: aws_shield_drt_access_role_arn_association" description: |- - Terraform resource for managing an AWS Shield DRT Access Role Arn Association. + Terraform resource for managing an AWS Shield DRT Access Role ARN Association. --- + # Resource: aws_shield_drt_access_role_arn_association -Authorizes the Shield Response Team (SRT) using the specified role, to access your AWS account to assist with DDoS attack mitigation during potential attacks. For more information see [Configure AWS SRT Support](https://docs.aws.amazon.com/waf/latest/developerguide/authorize-srt.html) +Authorizes the Shield Response Team (SRT) using the specified role, to access your AWS account to assist with DDoS attack mitigation during potential attacks. +For more information see [Configure AWS SRT Support](https://docs.aws.amazon.com/waf/latest/developerguide/authorize-srt.html) ## Example Usage @@ -50,3 +52,28 @@ The following arguments are required: ## Attribute Reference This resource exports no additional attributes. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `30m`) +* `update` - (Default `30m`) +* `delete` - (Default `30m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Shield DRT access role ARN association using the AWS account ID. For example: + +```terraform +import { + to = aws_shield_drt_access_role_arn_association.example + id = "123456789012" +} +``` + +Using `terraform import`, import Shield DRT access role ARN association using the AWS account ID. For example: + +```console +% terraform import aws_shield_drt_access_role_arn_association.example 123456789012 +``` From 775cf4ba77aafde1b6d9a0d4a268c324c8b37684 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 08:31:52 -0500 Subject: [PATCH 18/25] Add 'fwtypes.ARNValue'. --- internal/framework/types/arn.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/internal/framework/types/arn.go b/internal/framework/types/arn.go index cbc1fd9025e2..58e9985c70ca 100644 --- a/internal/framework/types/arn.go +++ b/internal/framework/types/arn.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" - "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" ) type arnType struct { @@ -128,11 +128,23 @@ func ARNUnknown() ARN { return ARN{StringValue: basetypes.NewStringUnknown()} } -func ARNValueMust(value string) ARN { +func ARNValue(value string) (ARN, diag.Diagnostics) { + var diags diag.Diagnostics + + v, err := arn.Parse(value) + if err != nil { + diags.Append(diag.NewErrorDiagnostic("Invalid ARN", err.Error())) + return ARNUnknown(), diags + } + return ARN{ StringValue: basetypes.NewStringValue(value), - value: errs.Must(arn.Parse(value)), - } + value: v, + }, diags +} + +func ARNValueMust(value string) ARN { + return fwdiag.Must(ARNValue(value)) } type ARN struct { From 8be30e3eee121f62f4cd187ff13afa2e05edada5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 10:42:27 -0500 Subject: [PATCH 19/25] r/aws_shield_protection: Tidy up. --- internal/service/shield/exports_test.go | 2 + internal/service/shield/protection.go | 50 +++++++++++-------- internal/service/shield/protection_test.go | 31 ++++-------- .../service/shield/service_package_gen.go | 2 +- 4 files changed, 42 insertions(+), 43 deletions(-) diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index b5ad46c0cce6..bea6e795e522 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -9,8 +9,10 @@ var ( ResourceDRTAccessLogBucketAssociation = newDRTAccessLogBucketAssociationResource ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse ResourceProactiveEngagement = newProactiveEngagementResource + ResourceProtection = resourceProtection FindDRTLogBucketAssociation = findDRTLogBucketAssociation FindDRTRoleARNAssociation = findDRTRoleARNAssociation FindEmergencyContactSettings = findEmergencyContactSettings + FindProtectionByID = findProtectionByID ) diff --git a/internal/service/shield/protection.go b/internal/service/shield/protection.go index 618a708b4855..44cc829149b3 100644 --- a/internal/service/shield/protection.go +++ b/internal/service/shield/protection.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "github.com/aws/aws-sdk-go-v2/service/shield/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -24,12 +24,13 @@ import ( // @SDKResource("aws_shield_protection", name="Protection") // @Tags(identifierAttribute="arn") -func ResourceProtection() *schema.Resource { +func resourceProtection() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceProtectionCreate, UpdateWithoutTimeout: resourceProtectionUpdate, ReadWithoutTimeout: resourceProtectionRead, DeleteWithoutTimeout: resourceProtectionDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -53,6 +54,7 @@ func ResourceProtection() *schema.Resource { names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), }, + CustomizeDiff: verify.SetTagsDiff, } } @@ -61,17 +63,21 @@ func resourceProtectionCreate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ShieldClient(ctx) + name := d.Get("name").(string) input := &shield.CreateProtectionInput{ - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), ResourceArn: aws.String(d.Get("resource_arn").(string)), Tags: getTagsIn(ctx), } - resp, err := conn.CreateProtection(ctx, input) + output, err := conn.CreateProtection(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Shield Protection: %s", err) + return sdkdiag.AppendErrorf(diags, "creating Shield Protection (%s): %s", name, err) } - d.SetId(aws.ToString(resp.ProtectionId)) + + d.SetId(aws.ToString(output.ProtectionId)) + return append(diags, resourceProtectionRead(ctx, d, meta)...) } @@ -79,7 +85,7 @@ func resourceProtectionRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ShieldClient(ctx) - resp, err := findProtectionByID(ctx, conn, d.Id()) + protection, err := findProtectionByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] Shield Protection (%s) not found, removing from state", d.Id()) @@ -91,10 +97,9 @@ func resourceProtectionRead(ctx context.Context, d *schema.ResourceData, meta in return sdkdiag.AppendErrorf(diags, "reading Shield Protection (%s): %s", d.Id(), err) } - arn := aws.ToString(resp.ProtectionArn) - d.Set("arn", arn) - d.Set("name", resp.Name) - d.Set("resource_arn", resp.ResourceArn) + d.Set("arn", protection.ProtectionArn) + d.Set("name", protection.Name) + d.Set("resource_arn", protection.ResourceArn) return diags } @@ -111,13 +116,12 @@ func resourceProtectionDelete(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ShieldClient(ctx) - input := &shield.DeleteProtectionInput{ + log.Printf("[DEBUG] Deleting Shield Protection: %s", d.Id()) + _, err := conn.DeleteProtection(ctx, &shield.DeleteProtectionInput{ ProtectionId: aws.String(d.Id()), - } - - _, err := conn.DeleteProtection(ctx, input) + }) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { + if errs.IsA[*types.ResourceNotFoundException](err) { return diags } @@ -127,14 +131,18 @@ func resourceProtectionDelete(ctx context.Context, d *schema.ResourceData, meta return diags } -func findProtectionByID(ctx context.Context, conn *shield.Client, id string) (*awstypes.Protection, error) { +func findProtectionByID(ctx context.Context, conn *shield.Client, id string) (*types.Protection, error) { input := &shield.DescribeProtectionInput{ ProtectionId: aws.String(id), } - resp, err := conn.DescribeProtection(ctx, input) + return findProtection(ctx, conn, input) +} + +func findProtection(ctx context.Context, conn *shield.Client, input *shield.DescribeProtectionInput) (*types.Protection, error) { + output, err := conn.DescribeProtection(ctx, input) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { + if errs.IsA[*types.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -145,9 +153,9 @@ func findProtectionByID(ctx context.Context, conn *shield.Client, id string) (*a return nil, err } - if resp.Protection == nil { + if output == nil || output.Protection == nil { return nil, tfresource.NewEmptyResultError(input) } - return resp.Protection, nil + return output.Protection, nil } diff --git a/internal/service/shield/protection_test.go b/internal/service/shield/protection_test.go index 249203f21bf0..5689feb3a9fa 100644 --- a/internal/service/shield/protection_test.go +++ b/internal/service/shield/protection_test.go @@ -9,9 +9,8 @@ import ( "os" "testing" - "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "github.com/aws/aws-sdk-go-v2/service/shield/types" "github.com/aws/aws-sdk-go/service/cloudfront" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" @@ -19,8 +18,8 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -304,13 +303,9 @@ func testAccCheckProtectionDestroy(ctx context.Context) resource.TestCheckFunc { continue } - input := &shield.DescribeProtectionInput{ - ProtectionId: aws.String(rs.Primary.ID), - } - - resp, err := conn.DescribeProtection(ctx, input) + _, err := tfshield.FindProtectionByID(ctx, conn, rs.Primary.ID) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { + if tfresource.NotFound(err) { continue } @@ -318,29 +313,23 @@ func testAccCheckProtectionDestroy(ctx context.Context) resource.TestCheckFunc { return err } - if resp != nil && resp.Protection != nil && aws.ToString(resp.Protection.Id) == rs.Primary.ID { - return fmt.Errorf("The Shield protection with ID %v still exists", rs.Primary.ID) - } + return fmt.Errorf("Shield Protection %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckProtectionExists(ctx context.Context, name string) resource.TestCheckFunc { +func testAccCheckProtectionExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", name) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - input := &shield.DescribeProtectionInput{ - ProtectionId: aws.String(rs.Primary.ID), - } - - _, err := conn.DescribeProtection(ctx, input) + _, err := tfshield.FindProtectionByID(ctx, conn, rs.Primary.ID) return err } @@ -351,7 +340,7 @@ func testAccPreCheck(ctx context.Context, t *testing.T) { input := &shield.ListProtectionsInput{} - errResourceNotFoundException := &awstypes.ResourceNotFoundException{} + errResourceNotFoundException := &types.ResourceNotFoundException{} _, err := conn.ListProtections(ctx, input) diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index abeb5aa8edec..edef1e1d6359 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -44,7 +44,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceProtection, + Factory: resourceProtection, TypeName: "aws_shield_protection", Name: "Protection", Tags: &types.ServicePackageResourceTags{ From 3aa9fd5fca7fd36e65cc6ff74995c39f823bf706 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 11:22:35 -0500 Subject: [PATCH 20/25] r/aws_shield_application_layer_automatic_response: Tidy up. --- .../application_layer_automatic_response.go | 417 ++++++++---------- internal/service/shield/exports_test.go | 2 +- internal/service/shield/protection.go | 8 + .../service/shield/service_package_gen.go | 2 +- 4 files changed, 187 insertions(+), 242 deletions(-) diff --git a/internal/service/shield/application_layer_automatic_response.go b/internal/service/shield/application_layer_automatic_response.go index f1e7bc2023c4..6d33b17daa96 100644 --- a/internal/service/shield/application_layer_automatic_response.go +++ b/internal/service/shield/application_layer_automatic_response.go @@ -5,32 +5,46 @@ package shield import ( "context" - "errors" + "fmt" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/shield" awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/diag" "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-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-provider-aws/internal/create" + "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/framework" + 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" ) +type applicationLayerAutomaticResponseAction string + +const ( + applicationLayerAutomaticResponseActionBlock applicationLayerAutomaticResponseAction = "BLOCK" + applicationLayerAutomaticResponseActionCount applicationLayerAutomaticResponseAction = "COUNT" +) + +func (applicationLayerAutomaticResponseAction) Values() []applicationLayerAutomaticResponseAction { + return []applicationLayerAutomaticResponseAction{ + applicationLayerAutomaticResponseActionBlock, + applicationLayerAutomaticResponseActionCount, + } +} + // @FrameworkResource(name="Application Layer Automatic Response") -func newResourceApplicationLayerAutomaticResponse(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceApplicationLayerAutomaticResponse{} +func newApplicationLayerAutomaticResponseResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &applicationLayerAutomaticResponseResource{} r.SetDefaultCreateTimeout(30 * time.Minute) r.SetDefaultUpdateTimeout(30 * time.Minute) @@ -39,35 +53,31 @@ func newResourceApplicationLayerAutomaticResponse(_ context.Context) (resource.R return r, nil } -const ( - ResNameApplicationLayerAutomaticResponse = "Application Layer Automatic Response" -) - -type resourceApplicationLayerAutomaticResponse struct { +type applicationLayerAutomaticResponseResource struct { framework.ResourceWithConfigure + framework.WithImportByID framework.WithTimeouts } -func (r *resourceApplicationLayerAutomaticResponse) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_shield_application_layer_automatic_response" +func (r *applicationLayerAutomaticResponseResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_shield_application_layer_automatic_response" } -func (r *resourceApplicationLayerAutomaticResponse) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *applicationLayerAutomaticResponseResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - "id": framework.IDAttribute(), + "action": schema.StringAttribute{ + CustomType: fwtypes.StringEnumType[applicationLayerAutomaticResponseAction](), + Required: true, + }, + names.AttrID: framework.IDAttribute(), "resource_arn": schema.StringAttribute{ - Required: true, + CustomType: fwtypes.ARNType, + Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, - "action": schema.StringAttribute{ - Required: true, - Validators: []validator.String{ - stringvalidator.OneOf([]string{"BLOCK", "COUNT"}...), - }, - }, }, Blocks: map[string]schema.Block{ "timeouts": timeouts.Block(ctx, timeouts.Opts{ @@ -79,334 +89,261 @@ func (r *resourceApplicationLayerAutomaticResponse) Schema(ctx context.Context, } } -func (r *resourceApplicationLayerAutomaticResponse) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - conn := r.Meta().ShieldClient(ctx) - - var plan resourceApplicationLayerAutomaticResponseData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *applicationLayerAutomaticResponseResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data applicationLayerAutomaticResponseResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } + conn := r.Meta().ShieldClient(ctx) + action := &awstypes.ResponseAction{} - switch plan.Action.ValueString() { - case "BLOCK": + switch data.Action.ValueEnum() { + case applicationLayerAutomaticResponseActionBlock: action.Block = &awstypes.BlockAction{} - case "COUNT": + case applicationLayerAutomaticResponseActionCount: action.Count = &awstypes.CountAction{} } - in := &shield.EnableApplicationLayerAutomaticResponseInput{ - ResourceArn: aws.String(plan.ResourceARN.ValueString()), + resourceARN := data.ResourceARN.ValueString() + input := &shield.EnableApplicationLayerAutomaticResponseInput{ Action: action, + ResourceArn: aws.String(resourceARN), } - _, err := conn.EnableApplicationLayerAutomaticResponse(ctx, in) + _, err := conn.EnableApplicationLayerAutomaticResponse(ctx, input) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionCreating, ResNameApplicationLayerAutomaticResponse, plan.ResourceARN.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("enabling Shield Application Layer Automatic Response (%s)", resourceARN), err.Error()) + return } - createTimeout := r.CreateTimeout(ctx, plan.Timeouts) - _, err = waitApplicationLayerAutomaticResponseCreated(ctx, conn, plan.ResourceARN.ValueString(), createTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForCreation, ResNameApplicationLayerAutomaticResponse, plan.ResourceARN.String(), err), - err.Error(), - ) + // Set values for unknowns. + data.setID() + + if _, err := waitApplicationLayerAutomaticResponseEnabled(ctx, conn, resourceARN, r.CreateTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield Application Layer Automatic Response (%s) create", resourceARN), err.Error()) + return } - plan.ID = types.StringValue(plan.ResourceARN.ValueString()) - - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *resourceApplicationLayerAutomaticResponse) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceApplicationLayerAutomaticResponseData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *applicationLayerAutomaticResponseResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data applicationLayerAutomaticResponseResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.DescribeProtectionInput{ - ResourceArn: aws.String(state.ID.ValueString()), + response.Diagnostics.Append(data.InitFromID()...) + if response.Diagnostics.HasError() { + return } - out, err := conn.DescribeProtection(ctx, in) + conn := r.Meta().ShieldClient(ctx) + + resourceARN := data.ID.ValueString() + output, err := findApplicationLayerAutomaticResponseByResourceARN(ctx, conn, resourceARN) + if tfresource.NotFound(err) { - resp.State.RemoveResource(ctx) + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionSetting, ResNameApplicationLayerAutomaticResponse, state.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading Shield Application Layer Automatic Response (%s)", resourceARN), err.Error()) + return } - if out != nil && - out.Protection != nil && - out.Protection.ApplicationLayerAutomaticResponseConfiguration != nil && - out.Protection.ApplicationLayerAutomaticResponseConfiguration.Action != nil { - if out.Protection.ApplicationLayerAutomaticResponseConfiguration.Action.Block != nil { - state.Action = types.StringValue("BLOCK") - } - if out.Protection.ApplicationLayerAutomaticResponseConfiguration.Action.Count != nil { - state.Action = types.StringValue("COUNT") - } + + if output.Action.Block != nil { + data.Action = fwtypes.StringEnumValue(applicationLayerAutomaticResponseActionBlock) + } else if output.Action.Count != nil { + data.Action = fwtypes.StringEnumValue(applicationLayerAutomaticResponseActionCount) } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} -func (r *resourceApplicationLayerAutomaticResponse) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().ShieldClient(ctx) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} - var plan, state resourceApplicationLayerAutomaticResponseData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *applicationLayerAutomaticResponseResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new applicationLayerAutomaticResponseResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { return } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { + return + } + + conn := r.Meta().ShieldClient(ctx) - if !plan.Action.Equal(state.Action) { + if !new.Action.Equal(old.Action) { action := &awstypes.ResponseAction{} - switch plan.Action.ValueString() { - case "BLOCK": + switch new.Action.ValueEnum() { + case applicationLayerAutomaticResponseActionBlock: action.Block = &awstypes.BlockAction{} - case "COUNT": + case applicationLayerAutomaticResponseActionCount: action.Count = &awstypes.CountAction{} } - in := &shield.UpdateApplicationLayerAutomaticResponseInput{ - ResourceArn: aws.String(plan.ResourceARN.ValueString()), + + resourceARN := new.ResourceARN.ValueString() + input := &shield.UpdateApplicationLayerAutomaticResponseInput{ Action: action, + ResourceArn: aws.String(resourceARN), } - out, err := conn.UpdateApplicationLayerAutomaticResponse(ctx, in) + _, err := conn.UpdateApplicationLayerAutomaticResponse(ctx, input) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameApplicationLayerAutomaticResponse, plan.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("updating Shield Application Layer Automatic Response (%s)", resourceARN), err.Error()) + return } - if out == nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionUpdating, ResNameApplicationLayerAutomaticResponse, plan.ID.String(), nil), - errors.New("empty output").Error(), - ) + + if _, err := waitApplicationLayerAutomaticResponseEnabled(ctx, conn, resourceARN, r.UpdateTimeout(ctx, new.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield Application Layer Automatic Response (%s) update", resourceARN), err.Error()) + return } } - updateTimeout := r.UpdateTimeout(ctx, plan.Timeouts) - _, err := waitApplicationLayerAutomaticResponseUpdated(ctx, conn, plan.ResourceARN.ValueString(), updateTimeout) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForUpdate, ResNameApplicationLayerAutomaticResponse, plan.ID.String(), err), - err.Error(), - ) - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &new)...) } -func (r *resourceApplicationLayerAutomaticResponse) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().ShieldClient(ctx) - - var state resourceApplicationLayerAutomaticResponseData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *applicationLayerAutomaticResponseResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data applicationLayerAutomaticResponseResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - in := &shield.DisableApplicationLayerAutomaticResponseInput{ - ResourceArn: aws.String(state.ResourceARN.ValueString()), + conn := r.Meta().ShieldClient(ctx) + + resourceARN := data.ID.ValueString() + input := &shield.DisableApplicationLayerAutomaticResponseInput{ + ResourceArn: aws.String(resourceARN), } - protectionOutput, err := conn.DescribeProtection(ctx, &shield.DescribeProtectionInput{ - ResourceArn: aws.String(state.ResourceARN.ValueString()), - }) + _, err := conn.DisableApplicationLayerAutomaticResponse(ctx, input) if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameApplicationLayerAutomaticResponse, state.ID.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError(fmt.Sprintf("disabling Application Layer Automatic Response (%s)", resourceARN), err.Error()) - if protectionOutput != nil { - if protectionOutput.Protection.ApplicationLayerAutomaticResponseConfiguration != nil && protectionOutput.Protection.ApplicationLayerAutomaticResponseConfiguration.Status != "" { - if protectionOutput.Protection.ApplicationLayerAutomaticResponseConfiguration.Status == awstypes.ApplicationLayerAutomaticResponseStatusDisabled { - return - } - } - } else { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameApplicationLayerAutomaticResponse, state.ID.String(), nil), - errors.New("empty output").Error(), - ) + return } - _, err = conn.DisableApplicationLayerAutomaticResponse(ctx, in) + if _, err := waitApplicationLayerAutomaticResponseDeleted(ctx, conn, resourceARN, r.DeleteTimeout(ctx, data.Timeouts)); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("waiting for Shield Application Layer Automatic Response (%s) delete", resourceARN), err.Error()) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { return } +} + +func findApplicationLayerAutomaticResponseByResourceARN(ctx context.Context, conn *shield.Client, arn string) (*awstypes.ApplicationLayerAutomaticResponseConfiguration, error) { + output, err := findProtectionByResourceARN(ctx, conn, arn) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionDeleting, ResNameApplicationLayerAutomaticResponse, state.ID.String(), err), - err.Error(), - ) - return + return nil, err } - deleteTimeout := r.DeleteTimeout(ctx, state.Timeouts) - _, err = waitApplicationLayerAutomaticResponseDeleted(ctx, conn, state.ResourceARN.ValueString(), deleteTimeout) + if output.ApplicationLayerAutomaticResponseConfiguration == nil || output.ApplicationLayerAutomaticResponseConfiguration.Action == nil { + return nil, tfresource.NewEmptyResultError(arn) + } - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.Shield, create.ErrActionWaitingForDeletion, ResNameApplicationLayerAutomaticResponse, state.ID.String(), err), - err.Error(), - ) - return + if status := output.ApplicationLayerAutomaticResponseConfiguration.Status; status == awstypes.ApplicationLayerAutomaticResponseStatusDisabled { + return nil, &retry.NotFoundError{ + Message: string(status), + LastRequest: arn, + } } -} -func (r *resourceApplicationLayerAutomaticResponse) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + return output.ApplicationLayerAutomaticResponseConfiguration, nil } -func waitApplicationLayerAutomaticResponseCreated(ctx context.Context, conn *shield.Client, resourceArn string, timeout time.Duration) (*shield.DescribeProtectionOutput, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{}, - Target: []string{statusNormal}, - Refresh: statusApplicationLayerAutomaticResponse(ctx, conn, resourceArn), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - } +func statusApplicationLayerAutomaticResponse(ctx context.Context, conn *shield.Client, resourceARN string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findApplicationLayerAutomaticResponseByResourceARN(ctx, conn, resourceARN) - outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeProtectionOutput); ok { - return out, err - } + if tfresource.NotFound(err) { + return nil, "", nil + } - return nil, err + if err != nil { + return nil, "", err + } + + return output, string(output.Status), nil + } } -func waitApplicationLayerAutomaticResponseUpdated(ctx context.Context, conn *shield.Client, resourceArn string, timeout time.Duration) (*shield.DescribeProtectionOutput, error) { +func waitApplicationLayerAutomaticResponseEnabled(ctx context.Context, conn *shield.Client, resourceARN string, timeout time.Duration) (*awstypes.ApplicationLayerAutomaticResponseConfiguration, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{statusChangePending}, - Target: []string{statusUpdated}, - Refresh: statusApplicationLayerAutomaticResponse(ctx, conn, resourceArn), + Pending: []string{}, + Target: enum.Slice(awstypes.ApplicationLayerAutomaticResponseStatusEnabled), + Refresh: statusApplicationLayerAutomaticResponse(ctx, conn, resourceARN), Timeout: timeout, NotFoundChecks: 20, ContinuousTargetOccurence: 2, } outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeProtectionOutput); ok { - return out, err + + if output, ok := outputRaw.(*awstypes.ApplicationLayerAutomaticResponseConfiguration); ok { + return output, err } return nil, err } -func waitApplicationLayerAutomaticResponseDeleted(ctx context.Context, conn *shield.Client, resourceArn string, timeout time.Duration) (*shield.DescribeProtectionOutput, error) { +func waitApplicationLayerAutomaticResponseDeleted(ctx context.Context, conn *shield.Client, resourceARN string, timeout time.Duration) (*awstypes.ApplicationLayerAutomaticResponseConfiguration, error) { stateConf := &retry.StateChangeConf{ - Pending: []string{statusDeleting, statusNormal}, + Pending: enum.Slice(awstypes.ApplicationLayerAutomaticResponseStatusEnabled), Target: []string{}, - Refresh: statusApplicationLayerAutomaticResponseDeleted(ctx, conn, resourceArn), + Refresh: statusApplicationLayerAutomaticResponse(ctx, conn, resourceARN), Timeout: timeout, } + outputRaw, err := stateConf.WaitForStateContext(ctx) - if out, ok := outputRaw.(*shield.DescribeProtectionOutput); ok { - return out, err + if output, ok := outputRaw.(*awstypes.ApplicationLayerAutomaticResponseConfiguration); ok { + return output, err } return nil, err } -func statusApplicationLayerAutomaticResponse(ctx context.Context, conn *shield.Client, resourceArn string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeApplicationLayerAutomaticResponse(ctx, conn, resourceArn) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - curStatus := out.Protection.ApplicationLayerAutomaticResponseConfiguration.Status - - if curStatus == awstypes.ApplicationLayerAutomaticResponseStatusEnabled { - return out, statusNormal, nil - } - return nil, statusChangePending, nil - } -} - -func statusApplicationLayerAutomaticResponseDeleted(ctx context.Context, conn *shield.Client, resourceArn string) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := describeApplicationLayerAutomaticResponse(ctx, conn, resourceArn) - if tfresource.NotFound(err) { - return nil, "", nil - } - - if err != nil { - return nil, "", err - } - - curStatus := out.Protection.ApplicationLayerAutomaticResponseConfiguration.Status - - if curStatus == awstypes.ApplicationLayerAutomaticResponseStatusDisabled { - return nil, "", nil - } - return out, statusDeleting, nil - } +type applicationLayerAutomaticResponseResourceModel struct { + Action fwtypes.StringEnum[applicationLayerAutomaticResponseAction] `tfsdk:"action"` + ID types.String `tfsdk:"id"` + ResourceARN fwtypes.ARN `tfsdk:"resource_arn"` + Timeouts timeouts.Value `tfsdk:"timeouts"` } -func describeApplicationLayerAutomaticResponse(ctx context.Context, conn *shield.Client, resourceArn string) (*shield.DescribeProtectionOutput, error) { - in := &shield.DescribeProtectionInput{ - ResourceArn: aws.String(resourceArn), - } - out, err := conn.DescribeProtection(ctx, in) - - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: in, - } - } +func (model *applicationLayerAutomaticResponseResourceModel) InitFromID() diag.Diagnostics { + var diags diag.Diagnostics - if err != nil { - return nil, err + arn, d := fwtypes.ARNValue(model.ID.ValueString()) + diags.Append(d...) + if diags.HasError() { + return diags } - if out == nil || out.Protection == nil || out.Protection.ApplicationLayerAutomaticResponseConfiguration == nil { - return nil, tfresource.NewEmptyResultError(in) - } + model.ResourceARN = arn - return out, nil + return nil } -type resourceApplicationLayerAutomaticResponseData struct { - ID types.String `tfsdk:"id"` - ResourceARN types.String `tfsdk:"resource_arn"` - Action types.String `tfsdk:"action"` - Timeouts timeouts.Value `tfsdk:"timeouts"` +func (model *applicationLayerAutomaticResponseResourceModel) setID() { + model.ID = model.ResourceARN.StringValue } diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index bea6e795e522..0ac2a632193c 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -7,7 +7,7 @@ package shield var ( ResourceDRTAccessRoleARNAssociation = newDRTAccessRoleARNAssociationResource ResourceDRTAccessLogBucketAssociation = newDRTAccessLogBucketAssociationResource - ResourceApplicationLayerAutomaticResponse = newResourceApplicationLayerAutomaticResponse + ResourceApplicationLayerAutomaticResponse = newApplicationLayerAutomaticResponseResource ResourceProactiveEngagement = newProactiveEngagementResource ResourceProtection = resourceProtection diff --git a/internal/service/shield/protection.go b/internal/service/shield/protection.go index 44cc829149b3..a660baf7d80b 100644 --- a/internal/service/shield/protection.go +++ b/internal/service/shield/protection.go @@ -139,6 +139,14 @@ func findProtectionByID(ctx context.Context, conn *shield.Client, id string) (*t return findProtection(ctx, conn, input) } +func findProtectionByResourceARN(ctx context.Context, conn *shield.Client, arn string) (*types.Protection, error) { + input := &shield.DescribeProtectionInput{ + ResourceArn: aws.String(arn), + } + + return findProtection(ctx, conn, input) +} + func findProtection(ctx context.Context, conn *shield.Client, input *shield.DescribeProtectionInput) (*types.Protection, error) { output, err := conn.DescribeProtection(ctx, input) diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index edef1e1d6359..0e84c65dba83 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -31,7 +31,7 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Name: "Proactive Engagement", }, { - Factory: newResourceApplicationLayerAutomaticResponse, + Factory: newApplicationLayerAutomaticResponseResource, Name: "Application Layer Automatic Response", }, } From 59afc074253ec81cddf81cb5aa9e5ca63314ecf7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 11:47:50 -0500 Subject: [PATCH 21/25] shield: Tidy up acceptance tests. --- ...plication_layer_automatic_response_test.go | 82 ++++++------- .../drt_access_log_bucket_association_test.go | 76 ++++-------- .../drt_access_role_arn_association_test.go | 108 +++++++++++------- internal/service/shield/exports_test.go | 9 +- .../service/shield/service_package_gen.go | 8 +- internal/service/shield/shield_test.go | 2 +- 6 files changed, 135 insertions(+), 150 deletions(-) diff --git a/internal/service/shield/application_layer_automatic_response_test.go b/internal/service/shield/application_layer_automatic_response_test.go index 9f870c079b18..76198e4456ae 100644 --- a/internal/service/shield/application_layer_automatic_response_test.go +++ b/internal/service/shield/application_layer_automatic_response_test.go @@ -5,33 +5,22 @@ package shield_test import ( "context" - "errors" "fmt" "testing" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" + "github.com/aws/aws-sdk-go-v2/service/shield/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "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" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccShieldApplicationLayerAutomaticResponse_basic(t *testing.T) { ctx := acctest.Context(t) - // TIP: This is a long-running test guard for tests that run longer than - // 300s (5 min) generally. - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var applicationlayerautomaticresponse shield.DescribeProtectionOutput + var applicationlayerautomaticresponse types.ApplicationLayerAutomaticResponseConfiguration rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_application_layer_automatic_response.test" @@ -45,9 +34,22 @@ func TestAccShieldApplicationLayerAutomaticResponse_basic(t *testing.T) { CheckDestroy: testAccCheckApplicationLayerAutomaticResponseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccApplicationLayerAutomaticResponseConfig_basic(rName), + Config: testAccApplicationLayerAutomaticResponseConfig_basic(rName, "COUNT"), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationLayerAutomaticResponseExists(ctx, resourceName, &applicationlayerautomaticresponse), + resource.TestCheckResourceAttr(resourceName, "action", "COUNT"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccApplicationLayerAutomaticResponseConfig_basic(rName, "BLOCK"), Check: resource.ComposeTestCheckFunc( testAccCheckApplicationLayerAutomaticResponseExists(ctx, resourceName, &applicationlayerautomaticresponse), + resource.TestCheckResourceAttr(resourceName, "action", "BLOCK"), ), }, }, @@ -56,11 +58,7 @@ func TestAccShieldApplicationLayerAutomaticResponse_basic(t *testing.T) { func TestAccShieldApplicationLayerAutomaticResponse_disappears(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var applicationlayerautomaticresponse shield.DescribeProtectionOutput + var applicationlayerautomaticresponse types.ApplicationLayerAutomaticResponseConfiguration rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_application_layer_automatic_response.test" @@ -74,7 +72,7 @@ func TestAccShieldApplicationLayerAutomaticResponse_disappears(t *testing.T) { CheckDestroy: testAccCheckApplicationLayerAutomaticResponseDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccApplicationLayerAutomaticResponseConfig_basic(rName), + Config: testAccApplicationLayerAutomaticResponseConfig_basic(rName, "COUNT"), Check: resource.ComposeTestCheckFunc( testAccCheckApplicationLayerAutomaticResponseExists(ctx, resourceName, &applicationlayerautomaticresponse), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceApplicationLayerAutomaticResponse, resourceName), @@ -93,56 +91,45 @@ func testAccCheckApplicationLayerAutomaticResponseDestroy(ctx context.Context) r continue } - input := &shield.DescribeProtectionInput{ - ResourceArn: aws.String(rs.Primary.ID), - } - resp, err := conn.DescribeProtection(ctx, input) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil + _, err := tfshield.FindApplicationLayerAutomaticResponseByResourceARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } if err != nil { return err } - if resp != nil && resp.Protection.ApplicationLayerAutomaticResponseConfiguration.Status == awstypes.ApplicationLayerAutomaticResponseStatusDisabled { - return nil - } - - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameApplicationLayerAutomaticResponse, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("Shield Application Layer Automatic Response %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckApplicationLayerAutomaticResponseExists(ctx context.Context, name string, applicationlayerautomaticresponse *shield.DescribeProtectionOutput) resource.TestCheckFunc { +func testAccCheckApplicationLayerAutomaticResponseExists(ctx context.Context, n string, v *types.ApplicationLayerAutomaticResponseConfiguration) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameApplicationLayerAutomaticResponse, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameApplicationLayerAutomaticResponse, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - resp, err := conn.DescribeProtection(ctx, &shield.DescribeProtectionInput{ - ResourceArn: aws.String(rs.Primary.ID), - }) + + output, err := tfshield.FindApplicationLayerAutomaticResponseByResourceARN(ctx, conn, rs.Primary.ID) if err != nil { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameApplicationLayerAutomaticResponse, rs.Primary.ID, err) + return err } - *applicationlayerautomaticresponse = *resp + *v = *output return nil } } -func testAccApplicationLayerAutomaticResponseConfig_basic(rName string) string { +func testAccApplicationLayerAutomaticResponseConfig_basic(rName, action string) string { return fmt.Sprintf(` resource "aws_wafv2_web_acl" "test" { name = %[1]q @@ -214,9 +201,10 @@ resource "aws_shield_protection" "test" { name = %[1]q resource_arn = aws_cloudfront_distribution.test.arn } + resource "aws_shield_application_layer_automatic_response" "test" { resource_arn = aws_cloudfront_distribution.test.arn - action = "COUNT" + action = %[2]q depends_on = [ aws_shield_protection.test, @@ -224,5 +212,5 @@ resource "aws_shield_application_layer_automatic_response" "test" { aws_wafv2_web_acl.test ] } -`, rName) +`, rName, action) } diff --git a/internal/service/shield/drt_access_log_bucket_association_test.go b/internal/service/shield/drt_access_log_bucket_association_test.go index 36c515843d43..a1cd77efc5f3 100644 --- a/internal/service/shield/drt_access_log_bucket_association_test.go +++ b/internal/service/shield/drt_access_log_bucket_association_test.go @@ -5,30 +5,21 @@ package shield_test import ( "context" - "errors" "fmt" "testing" "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "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" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func testAccDRTAccessLogBucketAssociation_basic(t *testing.T) { ctx := acctest.Context(t) - - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_drt_access_log_bucket_association.test" @@ -43,19 +34,21 @@ func testAccDRTAccessLogBucketAssociation_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccDRTAccessLogBucketAssociationConfig_basic(rName, bucketName), - Check: testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName, &drtaccesslogbucketassociation), + Check: resource.ComposeTestCheckFunc( + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, }, }, }) } -func testAccDRTAccessLogBucketAssociation_multibucket(t *testing.T) { +func testAccDRTAccessLogBucketAssociation_multiBucket(t *testing.T) { ctx := acctest.Context(t) - - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) var buckets = []string{} for i := 0; i < 2; i++ { @@ -75,8 +68,8 @@ func testAccDRTAccessLogBucketAssociation_multibucket(t *testing.T) { { Config: testAccDRTAccessLogBucketAssociationConfig_multibucket(rName, buckets), Check: resource.ComposeTestCheckFunc( - testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName1, &drtaccesslogbucketassociation), - testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName2, &drtaccesslogbucketassociation), + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName1), + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName2), ), }, }, @@ -85,10 +78,6 @@ func testAccDRTAccessLogBucketAssociation_multibucket(t *testing.T) { func testAccDRTAccessLogBucketAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var drtaccesslogbucketassociation shield.DescribeDRTAccessOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_drt_access_log_bucket_association.test" @@ -104,7 +93,7 @@ func testAccDRTAccessLogBucketAssociation_disappears(t *testing.T) { { Config: testAccDRTAccessLogBucketAssociationConfig_basic(rName, bucketName), Check: resource.ComposeTestCheckFunc( - testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName, &drtaccesslogbucketassociation), + testAccCheckDRTAccessLogBucketAssociationExists(ctx, resourceName), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceDRTAccessLogBucketAssociation, resourceName), ), ExpectNonEmptyPlan: true, @@ -122,56 +111,35 @@ func testAccCheckDRTAccessLogBucketAssociationDestroy(ctx context.Context) resou continue } - input := &shield.DescribeDRTAccessInput{} - resp, err := conn.DescribeDRTAccess(ctx, input) + _, err := tfshield.FindDRTLogBucketAssociation(ctx, conn, rs.Primary.ID) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil + if tfresource.NotFound(err) { + continue } if err != nil { return err } - if resp != nil { - if resp.LogBucketList != nil && len(resp.LogBucketList) > 0 { - for _, bucket := range resp.LogBucketList { - if bucket == rs.Primary.Attributes["log_bucket"] { - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, errors.New("bucket association not destroyed")) - } - } - } - return nil - } - - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("Shield DRT Role ARN Association %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckDRTAccessLogBucketAssociationExists(ctx context.Context, name string, drtaccesslogbucketassociation *shield.DescribeDRTAccessOutput) resource.TestCheckFunc { +func testAccCheckDRTAccessLogBucketAssociationExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] - + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - resp, err := conn.DescribeDRTAccess(ctx, &shield.DescribeDRTAccessInput{}) - if err != nil { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, rs.Primary.ID, err) - } - *drtaccesslogbucketassociation = *resp + _, err := tfshield.FindDRTLogBucketAssociation(ctx, conn, rs.Primary.ID) - return nil + return err } } diff --git a/internal/service/shield/drt_access_role_arn_association_test.go b/internal/service/shield/drt_access_role_arn_association_test.go index 8bf2d96a4912..eb1ffc44a811 100644 --- a/internal/service/shield/drt_access_role_arn_association_test.go +++ b/internal/service/shield/drt_access_role_arn_association_test.go @@ -5,32 +5,21 @@ package shield_test import ( "context" - "errors" "fmt" "testing" "github.com/aws/aws-sdk-go-v2/service/shield" - awstypes "github.com/aws/aws-sdk-go-v2/service/shield/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "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" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfshield "github.com/hashicorp/terraform-provider-aws/internal/service/shield" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// Acceptance test access AWS and cost money to run. func testAccDRTAccessRoleARNAssociation_basic(t *testing.T) { ctx := acctest.Context(t) - - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var drtaccessrolearnassociation shield.DescribeDRTAccessOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_drt_access_role_arn_association.test" @@ -45,7 +34,20 @@ func testAccDRTAccessRoleARNAssociation_basic(t *testing.T) { { Config: testAccDRTAccessRoleARNAssociationConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName, &drtaccessrolearnassociation), + testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDRTAccessRoleARNAssociationConfig_update(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "role_arn", "aws_iam_role.test2", "arn"), ), }, }, @@ -54,11 +56,6 @@ func testAccDRTAccessRoleARNAssociation_basic(t *testing.T) { func testAccDRTAccessRoleARNAssociation_disappears(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var drtaccessrolearnassociation shield.DescribeDRTAccessOutput rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_shield_drt_access_role_arn_association.test" @@ -73,7 +70,7 @@ func testAccDRTAccessRoleARNAssociation_disappears(t *testing.T) { { Config: testAccDRTAccessRoleARNAssociationConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName, &drtaccessrolearnassociation), + testAccCheckDRTAccessRoleARNAssociationExists(ctx, resourceName), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceDRTAccessRoleARNAssociation, resourceName), ), ExpectNonEmptyPlan: true, @@ -85,46 +82,41 @@ func testAccDRTAccessRoleARNAssociation_disappears(t *testing.T) { func testAccCheckDRTAccessRoleARNAssociationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) + for _, rs := range s.RootModule().Resources { if rs.Type != "aws_shield_drt_access_role_arn_association" { continue } - input := &shield.DescribeDRTAccessInput{} - resp, err := conn.DescribeDRTAccess(ctx, input) + _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.ID) - if errs.IsA[*awstypes.ResourceNotFoundException](err) { - return nil + if tfresource.NotFound(err) { + continue } - if resp != nil && (resp.RoleArn == nil || *resp.RoleArn == "") { - return nil + if err != nil { + return err } - return create.Error(names.Shield, create.ErrActionCheckingDestroyed, tfshield.ResNameDRTAccessRoleARNAssociation, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("Shield DRT Role ARN Association %s still exists", rs.Primary.ID) } + return nil } } -func testAccCheckDRTAccessRoleARNAssociationExists(ctx context.Context, name string, drtaccessrolearnassociation *shield.DescribeDRTAccessOutput) resource.TestCheckFunc { +func testAccCheckDRTAccessRoleARNAssociationExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not found")) + return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessLogBucketAssociation, name, errors.New("not set")) - } conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - resp, err := conn.DescribeDRTAccess(ctx, &shield.DescribeDRTAccessInput{}) - if err != nil { - return create.Error(names.Shield, create.ErrActionCheckingExistence, tfshield.ResNameDRTAccessRoleARNAssociation, "testing", err) - } - *drtaccessrolearnassociation = *resp - return nil + _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.ID) + + return err } } @@ -141,7 +133,7 @@ func testAccPreCheckRoleARN(ctx context.Context, t *testing.T) { } } -func testAccDRTAccessRoleARNAssociationConfig_basic(rName string) string { +func testAccDRTAccessRoleARNAssociationConfig_base(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -172,11 +164,47 @@ resource "aws_iam_role_policy_attachment" "test" { role = aws_iam_role.test.name policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" } +`, rName) +} +func testAccDRTAccessRoleARNAssociationConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccDRTAccessRoleARNAssociationConfig_base(rName), ` resource "aws_shield_drt_access_role_arn_association" "test" { role_arn = aws_iam_role.test.arn depends_on = [aws_iam_role_policy_attachment.test] } -`, rName) +`) +} + +func testAccDRTAccessRoleARNAssociationConfig_update(rName string) string { + return acctest.ConfigCompose(testAccDRTAccessRoleARNAssociationConfig_base(rName), fmt.Sprintf(` +resource "aws_iam_role" "test2" { + name = "%[1]s-2" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "", + "Effect" : "Allow", + "Principal" : { + "Service" : "drt.shield.amazonaws.com" + }, + "Action" : "sts:AssumeRole" + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "test2" { + role = aws_iam_role.test2.name + policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" +} + +resource "aws_shield_drt_access_role_arn_association" "test" { + role_arn = aws_iam_role.test2.arn + + depends_on = [aws_iam_role_policy_attachment.test2] +} +`, rName)) } diff --git a/internal/service/shield/exports_test.go b/internal/service/shield/exports_test.go index 0ac2a632193c..e4d727344522 100644 --- a/internal/service/shield/exports_test.go +++ b/internal/service/shield/exports_test.go @@ -11,8 +11,9 @@ var ( ResourceProactiveEngagement = newProactiveEngagementResource ResourceProtection = resourceProtection - FindDRTLogBucketAssociation = findDRTLogBucketAssociation - FindDRTRoleARNAssociation = findDRTRoleARNAssociation - FindEmergencyContactSettings = findEmergencyContactSettings - FindProtectionByID = findProtectionByID + FindApplicationLayerAutomaticResponseByResourceARN = findApplicationLayerAutomaticResponseByResourceARN + FindDRTLogBucketAssociation = findDRTLogBucketAssociation + FindDRTRoleARNAssociation = findDRTRoleARNAssociation + FindEmergencyContactSettings = findEmergencyContactSettings + FindProtectionByID = findProtectionByID ) diff --git a/internal/service/shield/service_package_gen.go b/internal/service/shield/service_package_gen.go index 0e84c65dba83..25cd9af5c1d0 100644 --- a/internal/service/shield/service_package_gen.go +++ b/internal/service/shield/service_package_gen.go @@ -18,6 +18,10 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ + { + Factory: newApplicationLayerAutomaticResponseResource, + Name: "Application Layer Automatic Response", + }, { Factory: newDRTAccessLogBucketAssociationResource, Name: "DRT Log Bucket Association", @@ -30,10 +34,6 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newProactiveEngagementResource, Name: "Proactive Engagement", }, - { - Factory: newApplicationLayerAutomaticResponseResource, - Name: "Application Layer Automatic Response", - }, } } diff --git a/internal/service/shield/shield_test.go b/internal/service/shield/shield_test.go index c365d9acfdce..9ee540ff9517 100644 --- a/internal/service/shield/shield_test.go +++ b/internal/service/shield/shield_test.go @@ -15,7 +15,7 @@ func TestAccShield_serial(t *testing.T) { testCases := map[string]map[string]func(t *testing.T){ "DRTAccessLogBucketAssociation": { "basic": testAccDRTAccessLogBucketAssociation_basic, - "multibucket": testAccDRTAccessLogBucketAssociation_multibucket, + "multibucket": testAccDRTAccessLogBucketAssociation_multiBucket, "disappears": testAccDRTAccessLogBucketAssociation_disappears, }, "DRTAccessRoleARNAssociation": { From 10cd8179cd717b259d0b8e04a1fbf5c0bf1f0236 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 12:16:56 -0500 Subject: [PATCH 22/25] shield: Add sweepers. --- .../shield/drt_access_role_arn_association.go | 2 +- internal/service/shield/sweep.go | 150 ++++++++++++++++++ internal/sweep/register_gen_test.go | 2 + 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 internal/service/shield/sweep.go diff --git a/internal/service/shield/drt_access_role_arn_association.go b/internal/service/shield/drt_access_role_arn_association.go index bd0453dee00d..9f26ffd9f47d 100644 --- a/internal/service/shield/drt_access_role_arn_association.go +++ b/internal/service/shield/drt_access_role_arn_association.go @@ -184,7 +184,7 @@ func (r *resourceDRTAccessRoleARNAssociation) Delete(ctx context.Context, reques conn := r.Meta().ShieldClient(ctx) - roleARN := data.RoleARN.ValueString() + roleARN := data.ID.ValueString() input := &shield.DisassociateDRTRoleInput{} _, err := conn.DisassociateDRTRole(ctx, input) diff --git a/internal/service/shield/sweep.go b/internal/service/shield/sweep.go new file mode 100644 index 000000000000..02ed2624a3d8 --- /dev/null +++ b/internal/service/shield/sweep.go @@ -0,0 +1,150 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package shield + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/shield" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/sweep" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" + "github.com/hashicorp/terraform-provider-aws/internal/sweep/framework" +) + +func RegisterSweepers() { + resource.AddTestSweepers("aws_shield_drt_access_log_bucket_association", &resource.Sweeper{ + Name: "aws_shield_drt_access_log_bucket_association", + F: sweepDRTAccessLogBucketAssociations, + }) + + resource.AddTestSweepers("aws_shield_drt_access_role_arn_association", &resource.Sweeper{ + Name: "aws_shield_drt_access_role_arn_association", + F: sweepDRTAccessRoleARNAssociations, + Dependencies: []string{ + "aws_shield_drt_access_log_bucket_association", + }, + }) + + resource.AddTestSweepers("aws_shield_proactive_engagement", &resource.Sweeper{ + Name: "aws_shield_proactive_engagement", + F: sweepProactiveEngagements, + Dependencies: []string{ + "aws_shield_drt_access_role_arn_association", + }, + }) +} + +func sweepDRTAccessLogBucketAssociations(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + input := &shield.DescribeDRTAccessInput{} + conn := client.ShieldClient(ctx) + sweepResources := make([]sweep.Sweepable, 0) + + output, err := conn.DescribeDRTAccess(ctx, input) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Shield DRT Log Bucket Association sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Shield DRT Log Bucket Associations (%s): %w", region, err) + } + + for _, v := range output.LogBucketList { + log.Printf("[INFO] Deleting Shield DRT Log Bucket Association: %s", v) + sweepResources = append(sweepResources, framework.NewSweepResource(newDRTAccessLogBucketAssociationResource, client, + framework.NewAttribute("id", v), + )) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Shield DRT Log Bucket Associations (%s): %w", region, err) + } + + return nil +} + +func sweepDRTAccessRoleARNAssociations(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + input := &shield.DescribeDRTAccessInput{} + conn := client.ShieldClient(ctx) + sweepResources := make([]sweep.Sweepable, 0) + + output, err := conn.DescribeDRTAccess(ctx, input) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Shield DRT Role ARN Association sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Shield DRT Role ARN Associations (%s): %w", region, err) + } + + if v := aws.ToString(output.RoleArn); v != "" { + log.Printf("[INFO] Deleting Shield DRT Role ARN Association: %s", v) + sweepResources = append(sweepResources, framework.NewSweepResource(newDRTAccessRoleARNAssociationResource, client, + framework.NewAttribute("id", client.AccountID), + )) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Shield DRT Role ARN Associations (%s): %w", region, err) + } + + return nil +} + +func sweepProactiveEngagements(region string) error { + ctx := sweep.Context(region) + client, err := sweep.SharedRegionalSweepClient(ctx, region) + if err != nil { + return fmt.Errorf("error getting client: %w", err) + } + input := &shield.DescribeSubscriptionInput{} + conn := client.ShieldClient(ctx) + sweepResources := make([]sweep.Sweepable, 0) + + output, err := conn.DescribeSubscription(ctx, input) + + if awsv2.SkipSweepError(err) { + log.Printf("[WARN] Skipping Shield Proactive Engagement sweep for %s: %s", region, err) + return nil + } + + if err != nil { + return fmt.Errorf("error listing Shield Proactive Engagements (%s): %w", region, err) + } + + if output.Subscription.ProactiveEngagementStatus != "" { + log.Printf("[INFO] Deleting Shield Proactive Engagement") + sweepResources = append(sweepResources, framework.NewSweepResource(newProactiveEngagementResource, client, + framework.NewAttribute("id", client.AccountID), + )) + } + + err = sweep.SweepOrchestrator(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping Shield Proactive Engagements (%s): %w", region, err) + } + + return nil +} diff --git a/internal/sweep/register_gen_test.go b/internal/sweep/register_gen_test.go index 1e0a409a8b8e..d6861528327c 100644 --- a/internal/sweep/register_gen_test.go +++ b/internal/sweep/register_gen_test.go @@ -132,6 +132,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/service/ses" "github.com/hashicorp/terraform-provider-aws/internal/service/sesv2" "github.com/hashicorp/terraform-provider-aws/internal/service/sfn" + "github.com/hashicorp/terraform-provider-aws/internal/service/shield" "github.com/hashicorp/terraform-provider-aws/internal/service/signer" "github.com/hashicorp/terraform-provider-aws/internal/service/simpledb" "github.com/hashicorp/terraform-provider-aws/internal/service/sns" @@ -283,6 +284,7 @@ func registerSweepers() { ses.RegisterSweepers() sesv2.RegisterSweepers() sfn.RegisterSweepers() + shield.RegisterSweepers() signer.RegisterSweepers() simpledb.RegisterSweepers() sns.RegisterSweepers() From 560f5aff2ed540b3e09896a5270c9229c4e18fc6 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 13:10:30 -0500 Subject: [PATCH 23/25] Acceptance test fixes. --- .../shield/drt_access_log_bucket_association_test.go | 7 ++++--- .../service/shield/drt_access_role_arn_association_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/service/shield/drt_access_log_bucket_association_test.go b/internal/service/shield/drt_access_log_bucket_association_test.go index a1cd77efc5f3..85a5aaae5b92 100644 --- a/internal/service/shield/drt_access_log_bucket_association_test.go +++ b/internal/service/shield/drt_access_log_bucket_association_test.go @@ -39,9 +39,10 @@ func testAccDRTAccessLogBucketAssociation_basic(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"role_arn_association_id"}, }, }, }) diff --git a/internal/service/shield/drt_access_role_arn_association_test.go b/internal/service/shield/drt_access_role_arn_association_test.go index eb1ffc44a811..92fd0294bfad 100644 --- a/internal/service/shield/drt_access_role_arn_association_test.go +++ b/internal/service/shield/drt_access_role_arn_association_test.go @@ -88,7 +88,7 @@ func testAccCheckDRTAccessRoleARNAssociationDestroy(ctx context.Context) resourc continue } - _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.ID) + _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.Attributes["role_arn"]) if tfresource.NotFound(err) { continue @@ -114,7 +114,7 @@ func testAccCheckDRTAccessRoleARNAssociationExists(ctx context.Context, n string conn := acctest.Provider.Meta().(*conns.AWSClient).ShieldClient(ctx) - _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.ID) + _, err := tfshield.FindDRTRoleARNAssociation(ctx, conn, rs.Primary.Attributes["role_arn"]) return err } From c137c2bc8af86518aa0d9e15a0911b78b8de05aa Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 13:26:37 -0500 Subject: [PATCH 24/25] r/aws_shield_proactive_engagement: Fix acceptance tests. --- .../service/shield/proactive_engagement.go | 3 +- .../shield/proactive_engagement_test.go | 75 ++++--------------- 2 files changed, 17 insertions(+), 61 deletions(-) diff --git a/internal/service/shield/proactive_engagement.go b/internal/service/shield/proactive_engagement.go index b62901bef97c..2b299ac4b76f 100644 --- a/internal/service/shield/proactive_engagement.go +++ b/internal/service/shield/proactive_engagement.go @@ -101,7 +101,8 @@ func (r *proactiveEngagementResource) Create(ctx context.Context, request resour _, err := conn.AssociateProactiveEngagementDetails(ctx, input) - if err != nil { + // "InvalidOperationException: Proactive engagement details are already associated with the subscription. Please use Enable/DisableProactiveEngagement APIs to update it's status". + if err != nil && !errs.IsA[*awstypes.InvalidOperationException](err) { response.Diagnostics.AddError("creating Shield Proactive Engagement", err.Error()) return diff --git a/internal/service/shield/proactive_engagement_test.go b/internal/service/shield/proactive_engagement_test.go index 92a9951aa38b..13897023b015 100644 --- a/internal/service/shield/proactive_engagement_test.go +++ b/internal/service/shield/proactive_engagement_test.go @@ -37,11 +37,18 @@ func testAccProactiveEngagement_basic(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementConfig_basic(rName, address1, address2), + Config: testAccProactiveEngagementConfig_basic(rName, address1, address2, true), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), + resource.TestCheckResourceAttr(resourceName, "emergency_contact.#", "2"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -64,11 +71,12 @@ func testAccProactiveEngagement_disabled(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementConfig_disabled(rName, address1, address2), + Config: testAccProactiveEngagementConfig_basic(rName, address1, address2, false), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), + resource.TestCheckResourceAttr(resourceName, "emergency_contact.#", "2"), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), ), - ExpectNonEmptyPlan: true, }, }, }) @@ -92,7 +100,7 @@ func testAccProactiveEngagement_disappears(t *testing.T) { CheckDestroy: testAccCheckProactiveEngagementAssociationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccProactiveEngagementConfig_basic(rName, address1, address2), + Config: testAccProactiveEngagementConfig_basic(rName, address1, address2, true), Check: resource.ComposeTestCheckFunc( testAccCheckProactiveEngagementAssociationExists(ctx, resourceName, &proactiveengagementassociation), acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfshield.ResourceProactiveEngagement, resourceName), @@ -164,7 +172,7 @@ func testAccPreCheckProactiveEngagement(ctx context.Context, t *testing.T) { } } -func testAccProactiveEngagementConfig_basic(rName string, email1 string, email2 string) string { +func testAccProactiveEngagementConfig_basic(rName, email1, email2 string, enabled bool) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -197,7 +205,7 @@ resource "aws_shield_drt_access_role_arn_association" "test" { } resource "aws_shield_proactive_engagement" "test" { - enabled = true + enabled = %[4]t emergency_contact { contact_notes = "Notes" @@ -213,58 +221,5 @@ resource "aws_shield_proactive_engagement" "test" { depends_on = [aws_shield_drt_access_role_arn_association.test] } -`, rName, email1, email2) -} - -func testAccProactiveEngagementConfig_disabled(rName string, email1 string, email2 string) string { - return fmt.Sprintf(` -data "aws_partition" "current" {} - -resource "aws_iam_role" "test" { - name = %[1]q - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - "Sid" : "", - "Effect" : "Allow", - "Principal" : { - "Service" : "drt.shield.amazonaws.com" - }, - "Action" : "sts:AssumeRole" - }, - ] - }) -} - -resource "aws_iam_role_policy_attachment" "test" { - role = aws_iam_role.test.name - policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSShieldDRTAccessPolicy" -} - -resource "aws_shield_drt_access_role_arn_association" "test" { - role_arn = aws_iam_role.test.arn - - depends_on = [aws_iam_role_policy_attachment.test] -} - -resource "aws_shield_proactive_engagement" "test" { - enabled = false - - emergency_contact { - contact_notes = "Notes" - email_address = %[2]q - phone_number = "+12358132134" - } - - emergency_contact { - contact_notes = "Notes 2" - email_address = %[3]q - phone_number = "+12358132134" - } - - depends_on = [aws_shield_drt_access_role_arn_association.test] -} - -`, rName, email1, email2) +`, rName, email1, email2, enabled) } From caf15c8d41d33958192e5e543a468ada1a5ffbc7 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 29 Feb 2024 14:13:57 -0500 Subject: [PATCH 25/25] Fix golangci-lint 'unparam'. --- internal/service/shield/application_layer_automatic_response.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/shield/application_layer_automatic_response.go b/internal/service/shield/application_layer_automatic_response.go index 6d33b17daa96..869cde55344e 100644 --- a/internal/service/shield/application_layer_automatic_response.go +++ b/internal/service/shield/application_layer_automatic_response.go @@ -287,7 +287,7 @@ func statusApplicationLayerAutomaticResponse(ctx context.Context, conn *shield.C } } -func waitApplicationLayerAutomaticResponseEnabled(ctx context.Context, conn *shield.Client, resourceARN string, timeout time.Duration) (*awstypes.ApplicationLayerAutomaticResponseConfiguration, error) { +func waitApplicationLayerAutomaticResponseEnabled(ctx context.Context, conn *shield.Client, resourceARN string, timeout time.Duration) (*awstypes.ApplicationLayerAutomaticResponseConfiguration, error) { //nolint:unparam stateConf := &retry.StateChangeConf{ Pending: []string{}, Target: enum.Slice(awstypes.ApplicationLayerAutomaticResponseStatusEnabled),