diff --git a/.changelog/38254.txt b/.changelog/38254.txt new file mode 100644 index 00000000000..aa9d1cdd021 --- /dev/null +++ b/.changelog/38254.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_inspector2_enabler: Fix `AccessDeniedException: Lambda code scanning is not supported in ...` errors +``` + +```release-note:bug +resource/aws_inspector2_member_association: Improve handling of `AccessDeniedException` errors during creation +``` diff --git a/internal/acctest/partition.go b/internal/acctest/partition.go index daaef7784cb..e3c06456a40 100644 --- a/internal/acctest/partition.go +++ b/internal/acctest/partition.go @@ -5,7 +5,7 @@ package acctest import ( "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/hashicorp/aws-sdk-go-base/v2/endpoints" "github.com/hashicorp/terraform-provider-aws/names" ) diff --git a/internal/errs/errs.go b/internal/errs/errs.go index c378555c749..e6f3b0cb95c 100644 --- a/internal/errs/errs.go +++ b/internal/errs/errs.go @@ -6,8 +6,6 @@ package errs import ( "errors" "strings" - - "github.com/aws/aws-sdk-go/aws/awserr" ) // errorMessager is a simple interface for types with ErrorMessage(). @@ -48,23 +46,6 @@ func Contains(err error, needle string) bool { return false } -// MessageContains unwraps the error and returns true if the error matches -// all these conditions: -// - err is of type awserr.Error, Error.Code() equals code, and Error.Message() contains message -// - OR err if not of type awserr.Error as string contains both code and message -func MessageContains(err error, code string, message string) bool { - var awsErr awserr.Error - if AsContains(err, &awsErr, message) { - return true - } - - if Contains(err, code) && Contains(err, message) { - return true - } - - return false -} - // IsA indicates whether an error matches an error type func IsA[T error](err error) bool { _, ok := As[T](err) diff --git a/internal/service/inspector2/delegated_admin_account.go b/internal/service/inspector2/delegated_admin_account.go index 7fedf1b77cd..1b82787b348 100644 --- a/internal/service/inspector2/delegated_admin_account.go +++ b/internal/service/inspector2/delegated_admin_account.go @@ -12,21 +12,22 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/inspector2" - "github.com/aws/aws-sdk-go-v2/service/inspector2/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/inspector2/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "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/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_inspector2_delegated_admin_account") -func ResourceDelegatedAdminAccount() *schema.Resource { +// @SDKResource("aws_inspector2_delegated_admin_account", name="Delegated Admin Account") +func resourceDelegatedAdminAccount() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDelegatedAdminAccountCreate, ReadWithoutTimeout: resourceDelegatedAdminAccountRead, @@ -55,34 +56,26 @@ func ResourceDelegatedAdminAccount() *schema.Resource { } } -const ( - ResNameDelegatedAdminAccount = "Delegated Admin Account" -) - func resourceDelegatedAdminAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - in := &inspector2.EnableDelegatedAdminAccountInput{ - DelegatedAdminAccountId: aws.String(d.Get(names.AttrAccountID).(string)), + accountID := d.Get(names.AttrAccountID).(string) + input := &inspector2.EnableDelegatedAdminAccountInput{ + DelegatedAdminAccountId: aws.String(accountID), ClientToken: aws.String(id.UniqueId()), } - out, err := conn.EnableDelegatedAdminAccount(ctx, in) + _, err := conn.EnableDelegatedAdminAccount(ctx, input) - if err != nil && !errs.MessageContains(err, "ConflictException", "already enabled") { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionCreating, ResNameDelegatedAdminAccount, d.Get(names.AttrAccountID).(string), err) + if err != nil && !errs.IsAErrorMessageContains[*awstypes.ConflictException](err, fmt.Sprintf("Delegated administrator %s is already enabled for the organization", accountID)) { + return sdkdiag.AppendErrorf(diags, "enabling Inspector2 Delegated Admin Account (%s): %s", accountID, err) } - if err == nil && (out == nil || out.DelegatedAdminAccountId == nil) { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionCreating, ResNameDelegatedAdminAccount, d.Get(names.AttrAccountID).(string), errors.New("empty output")) - } - - d.SetId(d.Get(names.AttrAccountID).(string)) + d.SetId(accountID) - if err := WaitDelegatedAdminAccountEnabled(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionWaitingForCreation, ResNameDelegatedAdminAccount, d.Id(), err) + if _, err := waitDelegatedAdminAccountEnabled(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Delegated Admin Account (%s) create: %s", d.Id(), err) } return append(diags, resourceDelegatedAdminAccountRead(ctx, d, meta)...) @@ -90,113 +83,112 @@ func resourceDelegatedAdminAccountCreate(ctx context.Context, d *schema.Resource func resourceDelegatedAdminAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - st, ai, err := FindDelegatedAdminAccountStatusID(ctx, conn, d.Id()) + output, err := findDelegatedAdminAccountByID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Inspector Delegated Admin Account (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Inspector2 Delegated Admin Account (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionReading, ResNameDelegatedAdminAccount, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Inspector2 Delegated Admin Account (%s): %s", d.Id(), err) } - d.Set(names.AttrAccountID, ai) - d.Set("relationship_status", st) + d.Set(names.AttrAccountID, output.AccountId) + d.Set("relationship_status", output.Status) return diags } func resourceDelegatedAdminAccountDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - log.Printf("[INFO] Deleting Inspector DelegatedAdminAccount %s", d.Id()) - + log.Printf("[INFO] Deleting Inspector2 Delegated Admin Account: %s", d.Id()) _, err := conn.DisableDelegatedAdminAccount(ctx, &inspector2.DisableDelegatedAdminAccountInput{ DelegatedAdminAccountId: aws.String(d.Get(names.AttrAccountID).(string)), }) - if err != nil { - var nfe *types.ResourceNotFoundException - if errors.As(err, &nfe) { - return diags - } + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return diags + } - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionDeleting, ResNameDelegatedAdminAccount, d.Id(), err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "disabling Inspector2 Delegated Admin Account (%s): %s", d.Id(), err) } - if err := WaitDelegatedAdminAccountDisabled(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionWaitingForDeletion, ResNameDelegatedAdminAccount, d.Id(), err) + if _, err := waitDelegatedAdminAccountDisabled(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Delegated Admin Account (%s) delete: %s", d.Id(), err) } return diags } -type DelegatedAccountStatus string - -// Enum values for DelegatedAccountStatus -const ( - DelegatedAccountStatusDisableInProgress DelegatedAccountStatus = "DISABLE_IN_PROGRESS" - DelegatedAccountStatusEnableInProgress DelegatedAccountStatus = "ENABLE_IN_PROGRESS" - DelegatedAccountStatusEnabling DelegatedAccountStatus = "ENABLING" +var ( + errIsDelegatedAdmin = errors.New("is the delegated administrator") ) -// Values returns all known values for DelegatedAccountStatus. -func (DelegatedAccountStatus) Values() []DelegatedAccountStatus { - return []DelegatedAccountStatus{ - DelegatedAccountStatusDisableInProgress, - DelegatedAccountStatusEnableInProgress, - DelegatedAccountStatusEnabling, - DelegatedAccountStatus(types.RelationshipStatusAccountSuspended), - DelegatedAccountStatus(types.RelationshipStatusCannotCreateDetectorInOrgMaster), - DelegatedAccountStatus(types.RelationshipStatusCreated), - DelegatedAccountStatus(types.RelationshipStatusDeleted), - DelegatedAccountStatus(types.RelationshipStatusDisabled), - DelegatedAccountStatus(types.RelationshipStatusEmailVerificationFailed), - DelegatedAccountStatus(types.RelationshipStatusEmailVerificationInProgress), - DelegatedAccountStatus(types.RelationshipStatusEnabled), - DelegatedAccountStatus(types.RelationshipStatusInvited), - DelegatedAccountStatus(types.RelationshipStatusRegionDisabled), - DelegatedAccountStatus(types.RelationshipStatusRemoved), - DelegatedAccountStatus(types.RelationshipStatusResigned), - } -} +func findDelegatedAdminAccountByID(ctx context.Context, conn *inspector2.Client, accountID string) (*awstypes.DelegatedAdminAccount, error) { + input := &inspector2.ListDelegatedAdminAccountsInput{} + output, err := findDelegatedAdminAccount(ctx, conn, input, func(v *awstypes.DelegatedAdminAccount) bool { + return aws.ToString(v.AccountId) == accountID + }) -func WaitDelegatedAdminAccountEnabled(ctx context.Context, conn *inspector2.Client, accountID string, timeout time.Duration) error { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(DelegatedAccountStatusDisableInProgress, DelegatedAccountStatusEnableInProgress, DelegatedAccountStatusEnabling), - Target: enum.Slice(types.RelationshipStatusEnabled), - Refresh: statusDelegatedAdminAccount(ctx, conn, accountID), - Timeout: timeout, + if errors.Is(err, errIsDelegatedAdmin) { + return &awstypes.DelegatedAdminAccount{ + AccountId: aws.String(accountID), + Status: awstypes.DelegatedAdminStatusEnabled, + }, nil } - _, err := stateConf.WaitForStateContext(ctx) + if err != nil { + return nil, err + } - return err + return output, nil } -func WaitDelegatedAdminAccountDisabled(ctx context.Context, conn *inspector2.Client, accountID string, timeout time.Duration) error { - stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(DelegatedAccountStatusDisableInProgress, DelegatedAccountStatus(types.RelationshipStatusCreated), DelegatedAccountStatus(types.RelationshipStatusEnabled)), - Target: []string{}, - Refresh: statusDelegatedAdminAccount(ctx, conn, accountID), - Timeout: timeout, +func findDelegatedAdminAccount(ctx context.Context, conn *inspector2.Client, input *inspector2.ListDelegatedAdminAccountsInput, filter tfslices.Predicate[*awstypes.DelegatedAdminAccount]) (*awstypes.DelegatedAdminAccount, error) { + output, err := findDelegatedAdminAccounts(ctx, conn, input, filter) + + if err != nil { + return nil, err } - _, err := stateConf.WaitForStateContext(ctx) + return tfresource.AssertSingleValueResult(output) +} + +func findDelegatedAdminAccounts(ctx context.Context, conn *inspector2.Client, input *inspector2.ListDelegatedAdminAccountsInput, filter tfslices.Predicate[*awstypes.DelegatedAdminAccount]) ([]awstypes.DelegatedAdminAccount, error) { + var output []awstypes.DelegatedAdminAccount + + pages := inspector2.NewListDelegatedAdminAccountsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) - return err + if errs.IsAErrorMessageContains[*awstypes.ValidationException](err, "is the delegated admin") { + return nil, errIsDelegatedAdmin + } + + if err != nil { + return nil, err + } + + for _, v := range page.DelegatedAdminAccounts { + if filter(&v) { + output = append(output, v) + } + } + } + + return output, nil } func statusDelegatedAdminAccount(ctx context.Context, conn *inspector2.Client, accountID string) retry.StateRefreshFunc { return func() (interface{}, string, error) { - s, _, err := FindDelegatedAdminAccountStatusID(ctx, conn, accountID) + output, err := findDelegatedAdminAccountByID(ctx, conn, accountID) if tfresource.NotFound(err) { return nil, "", nil @@ -206,32 +198,50 @@ func statusDelegatedAdminAccount(ctx context.Context, conn *inspector2.Client, a return nil, "", err } - return "", s, nil + return output, string(output.Status), nil } } -func FindDelegatedAdminAccountStatusID(ctx context.Context, conn *inspector2.Client, accountID string) (string, string, error) { - pages := inspector2.NewListDelegatedAdminAccountsPaginator(conn, &inspector2.ListDelegatedAdminAccountsInput{}) +type delegatedAdminStatus string - for pages.HasMorePages() { - page, err := pages.NextPage(ctx) +const ( + delegatedAdminStatusDisableInProgress delegatedAdminStatus = delegatedAdminStatus(awstypes.DelegatedAdminStatusDisableInProgress) + delegatedAdminStatusEnabling delegatedAdminStatus = "ENABLING" + delegatedAdminStatusEnableInProgress delegatedAdminStatus = "ENABLE_IN_PROGRESS" + delegatedAdminStatusEnabled delegatedAdminStatus = delegatedAdminStatus(awstypes.DelegatedAdminStatusEnabled) + delegatedAdminStatusCreated delegatedAdminStatus = "CREATED" +) - if errs.IsAErrorMessageContains[*types.ValidationException](err, "is the delegated admin") { - return string(types.RelationshipStatusEnabled), accountID, nil - } +func waitDelegatedAdminAccountEnabled(ctx context.Context, conn *inspector2.Client, accountID string, timeout time.Duration) (*awstypes.DelegatedAdminAccount, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(delegatedAdminStatusDisableInProgress, delegatedAdminStatusEnableInProgress, delegatedAdminStatusEnabling), + Target: enum.Slice(delegatedAdminStatusEnabled), + Refresh: statusDelegatedAdminAccount(ctx, conn, accountID), + Timeout: timeout, + } - if err != nil { - return "", "", err - } + outputRaw, err := stateConf.WaitForStateContext(ctx) - for _, account := range page.DelegatedAdminAccounts { - if aws.ToString(account.AccountId) == accountID { - return string(account.Status), aws.ToString(account.AccountId), nil - } - } + if output, ok := outputRaw.(*awstypes.DelegatedAdminAccount); ok { + return output, err } - return "", "", &retry.NotFoundError{ - Message: fmt.Sprintf("delegated admin account not found for %s", accountID), + return nil, err +} + +func waitDelegatedAdminAccountDisabled(ctx context.Context, conn *inspector2.Client, accountID string, timeout time.Duration) (*awstypes.DelegatedAdminAccount, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(delegatedAdminStatusDisableInProgress, delegatedAdminStatusCreated, delegatedAdminStatusEnabled), + Target: []string{}, + Refresh: statusDelegatedAdminAccount(ctx, conn, accountID), + Timeout: timeout, } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.DelegatedAdminAccount); ok { + return output, err + } + + return nil, err } diff --git a/internal/service/inspector2/delegated_admin_account_test.go b/internal/service/inspector2/delegated_admin_account_test.go index e3c99e0ca68..1764d9bf93d 100644 --- a/internal/service/inspector2/delegated_admin_account_test.go +++ b/internal/service/inspector2/delegated_admin_account_test.go @@ -5,7 +5,6 @@ package inspector2_test import ( "context" - "errors" "fmt" "testing" @@ -14,9 +13,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/create" - "github.com/hashicorp/terraform-provider-aws/internal/errs" tfinspector2 "github.com/hashicorp/terraform-provider-aws/internal/service/inspector2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -107,43 +105,35 @@ func testAccCheckDelegatedAdminAccountDestroy(ctx context.Context) resource.Test continue } - st, _, err := tfinspector2.FindDelegatedAdminAccountStatusID(ctx, conn, rs.Primary.ID) + _, err := tfinspector2.FindDelegatedAdminAccountByID(ctx, conn, rs.Primary.ID) - if st == "" && errs.Contains(err, "admin account not found") { - return nil + if tfresource.NotFound(err) { + continue } if err != nil { return err } - return create.Error(names.Inspector2, create.ErrActionCheckingDestroyed, tfinspector2.ResNameDelegatedAdminAccount, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("Inspector2 Delegated Admin Account %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckDelegatedAdminAccountExists(ctx context.Context, name string) resource.TestCheckFunc { +func testAccCheckDelegatedAdminAccountExists(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.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameDelegatedAdminAccount, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameDelegatedAdminAccount, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).Inspector2Client(ctx) - _, _, err := tfinspector2.FindDelegatedAdminAccountStatusID(ctx, conn, rs.Primary.ID) + _, err := tfinspector2.FindDelegatedAdminAccountByID(ctx, conn, rs.Primary.ID) - if err != nil { - return create.Error(names.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameDelegatedAdminAccount, rs.Primary.ID, err) - } - - return nil + return err } } diff --git a/internal/service/inspector2/enabler.go b/internal/service/inspector2/enabler.go index d894e8b76c1..8ddc47354b9 100644 --- a/internal/service/inspector2/enabler.go +++ b/internal/service/inspector2/enabler.go @@ -27,6 +27,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/flex" tfmaps "github.com/hashicorp/terraform-provider-aws/internal/maps" + "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -85,11 +86,7 @@ func ResourceEnabler() *schema.Resource { } } -type resourceGetter interface { - Get(key string) any -} - -func getAccountIDs(d resourceGetter) []string { +func getAccountIDs(d sdkv2.ResourceDiffer) []string { return flex.ExpandStringValueSet(d.Get("account_ids").(*schema.Set)) } @@ -128,11 +125,7 @@ func resourceEnablerCreate(ctx context.Context, d *schema.ResourceData, meta int return nil } - var errs []error - for _, acct := range out.FailedAccounts { - errs = append(errs, newFailedAccountError(acct)) - } - err = errors.Join(errs...) + err = errors.Join(tfslices.ApplyToAll(out.FailedAccounts, newFailedAccountError)...) if tfslices.All(out.FailedAccounts, func(acct types.FailedAccount) bool { switch acct.ErrorCode { @@ -171,6 +164,11 @@ func resourceEnablerCreate(ctx context.Context, d *schema.ResourceData, meta int for _, resourceType := range typeEnable { delete(resourceStatuses, resourceType) } + for resourceType, typeStatus := range resourceStatuses { + if typeStatus == types.StatusDisabled { + delete(resourceStatuses, resourceType) + } + } if len(resourceStatuses) > 0 { disableAccountIDs = append(disableAccountIDs, acctID) in := &inspector2.DisableInput{ @@ -186,7 +184,7 @@ func resourceEnablerCreate(ctx context.Context, d *schema.ResourceData, meta int } if len(disableAccountIDs) > 0 { - if _, err := waitEnabled(ctx, conn, disableAccountIDs, d.Timeout(schema.TimeoutCreate)); err != nil { + if err := waitDisabled(ctx, conn, disableAccountIDs, d.Timeout(schema.TimeoutCreate)); err != nil { return create.AppendDiagError(diags, names.Inspector2, create.ErrActionWaitingForUpdate, ResNameEnabler, id, err) } } @@ -247,7 +245,8 @@ func resourceEnablerUpdate(ctx context.Context, d *schema.ResourceData, meta int typeEnable := flex.ExpandStringyValueSet[types.ResourceScanType](d.Get("resource_types").(*schema.Set)) var typeDisable []types.ResourceScanType if d.HasChange("resource_types") { - for _, v := range types.ResourceScanType("").Values() { + o, _ := d.GetChange("resource_types") + for _, v := range flex.ExpandStringyValueSet[types.ResourceScanType](o.(*schema.Set)) { if !slices.Contains(typeEnable, v) { typeDisable = append(typeDisable, v) } @@ -350,9 +349,28 @@ func resourceEnablerDelete(ctx context.Context, d *schema.ResourceData, meta int func disableAccounts(ctx context.Context, conn *inspector2.Client, d *schema.ResourceData, accountIDs []string) diag.Diagnostics { var diags diag.Diagnostics + + s, err := AccountStatuses(ctx, conn, accountIDs) + if err != nil { + return create.AppendDiagError(diags, names.Inspector2, create.ErrActionReading, ResNameEnabler, d.Id(), err) + } + + var resourceTypes []types.ResourceScanType + for _, st := range s { + for k, a := range st.ResourceStatuses { + if a != types.StatusDisabled && !slices.Contains(resourceTypes, k) { + resourceTypes = append(resourceTypes, k) + } + } + } + + if len(resourceTypes) == 0 { + return diags + } + in := &inspector2.DisableInput{ AccountIds: accountIDs, - ResourceTypes: types.ResourceScanType("").Values(), + ResourceTypes: resourceTypes, } out, err := conn.Disable(ctx, in) diff --git a/internal/service/inspector2/enabler_test.go b/internal/service/inspector2/enabler_test.go index 1eab2ae5cd7..f108b1fbb40 100644 --- a/internal/service/inspector2/enabler_test.go +++ b/internal/service/inspector2/enabler_test.go @@ -251,6 +251,41 @@ func testAccEnabler_lambda(t *testing.T) { }) } +func testAccEnabler_lambdaCode(t *testing.T) { + ctx := acctest.Context(t) + + resourceName := "aws_inspector2_enabler.test" + resourceTypes := []types.ResourceScanType{types.ResourceScanTypeLambda, types.ResourceScanTypeLambdaCode} + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.Inspector2EndpointID) + acctest.PreCheckInspector2(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) + // https://docs.aws.amazon.com/inspector/latest/user/inspector_regions.html#ins-regional-feature-availability. + acctest.PreCheckRegion(t, names.USWest2RegionID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.Inspector2ServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckEnablerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccEnablerConfig_basic(resourceTypes), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckEnablerExists(ctx, resourceName, resourceTypes), + testAccCheckEnablerID(resourceName, resourceTypes), + resource.TestCheckResourceAttr(resourceName, "account_ids.#", acctest.Ct1), + resource.TestCheckTypeSetElemAttrPair(resourceName, "account_ids.*", "data.aws_caller_identity.current", names.AttrAccountID), + resource.TestCheckResourceAttr(resourceName, "resource_types.#", acctest.Ct2), + resource.TestCheckTypeSetElemAttr(resourceName, "resource_types.*", string(types.ResourceScanTypeLambda)), + resource.TestCheckTypeSetElemAttr(resourceName, "resource_types.*", string(types.ResourceScanTypeLambdaCode)), + ), + }, + }, + }) +} + func testAccEnabler_memberAccount_basic(t *testing.T) { ctx := acctest.Context(t) diff --git a/internal/service/inspector2/exports_test.go b/internal/service/inspector2/exports_test.go index 5ffd77545a6..f1d50a6ae07 100644 --- a/internal/service/inspector2/exports_test.go +++ b/internal/service/inspector2/exports_test.go @@ -5,6 +5,14 @@ package inspector2 // Exports for use in tests only. var ( + ResourceDelegatedAdminAccount = resourceDelegatedAdminAccount + ResourceMemberAssociation = resourceMemberAssociation + ResourceOrganizationConfiguration = resourceOrganizationConfiguration + + FindDelegatedAdminAccountByID = findDelegatedAdminAccountByID + FindMemberByAccountID = findMemberByAccountID + FindOrganizationConfiguration = findOrganizationConfiguration + EnablerID = enablerID ParseEnablerID = parseEnablerID ) diff --git a/internal/service/inspector2/inspector2_test.go b/internal/service/inspector2/inspector2_test.go index b1cc8c3f8bc..00265097727 100644 --- a/internal/service/inspector2/inspector2_test.go +++ b/internal/service/inspector2/inspector2_test.go @@ -18,6 +18,7 @@ func TestAccInspector2_serial(t *testing.T) { "accountID": testAccEnabler_accountID, acctest.CtDisappears: testAccEnabler_disappears, "lambda": testAccEnabler_lambda, + "lambdaCode": testAccEnabler_lambdaCode, "updateResourceTypes": testAccEnabler_updateResourceTypes, "updateResourceTypes_disjoint": testAccEnabler_updateResourceTypes_disjoint, "memberAccount_basic": testAccEnabler_memberAccount_basic, diff --git a/internal/service/inspector2/member_association.go b/internal/service/inspector2/member_association.go index 71477ec0270..a8bf332fd0a 100644 --- a/internal/service/inspector2/member_association.go +++ b/internal/service/inspector2/member_association.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/inspector2" - "github.com/aws/aws-sdk-go-v2/service/inspector2/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/inspector2/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" @@ -23,8 +23,8 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_inspector2_member_association") -func ResourceMemberAssociation() *schema.Resource { +// @SDKResource("aws_inspector2_member_association", name="Member Association") +func resourceMemberAssociation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceMemberAssociationCreate, ReadWithoutTimeout: resourceMemberAssociationRead, @@ -74,13 +74,13 @@ func resourceMemberAssociationCreate(ctx context.Context, d *schema.ResourceData _, err := conn.AssociateMember(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Amazon Inspector Member Association (%s): %s", accountID, err) + return sdkdiag.AppendErrorf(diags, "creating Inspector2 Member Association (%s): %s", accountID, err) } d.SetId(accountID) - if err := waitMemberAssociationCreated(ctx, conn, accountID, d.Timeout(schema.TimeoutCreate)); err != nil { - return sdkdiag.AppendErrorf(diags, "creating Amazon Inspector Member Association (%s): waiting for completion: %s", accountID, err) + if _, err := waitMemberAssociationCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Member Association (%s) create: %s", d.Id(), err) } return append(diags, resourceMemberAssociationRead(ctx, d, meta)...) @@ -90,16 +90,16 @@ func resourceMemberAssociationRead(ctx context.Context, d *schema.ResourceData, var diags diag.Diagnostics conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - member, err := FindMemberByAccountID(ctx, conn, d.Id()) + member, err := findMemberByAccountID(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Amazon Inspector Member Association (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Inspector2 Member Association (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Amazon Inspector Member Association (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Inspector2 Member Association (%s): %s", d.Id(), err) } d.Set(names.AttrAccountID, member.AccountId) @@ -114,37 +114,51 @@ func resourceMemberAssociationDelete(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - log.Printf("[DEBUG] Deleting Amazon Inspector Member Association: %s", d.Id()) - - accountID := d.Get(names.AttrAccountID).(string) + log.Printf("[DEBUG] Deleting Inspector2 Member Association: %s", d.Id()) _, err := conn.DisassociateMember(ctx, &inspector2.DisassociateMemberInput{ - AccountId: aws.String(accountID), + AccountId: aws.String(d.Id()), }) // An error occurred (ValidationException) when calling the DisassociateMember operation: The request is rejected because the current account cannot disassociate the given member account ID since the latter is not yet associated to it. - if errs.IsAErrorMessageContains[*types.ValidationException](err, "is not yet associated to it") { + if errs.IsAErrorMessageContains[*awstypes.ValidationException](err, "is not yet associated to it") { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Amazon Inspector Member Association (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting Inspector2 Member Association (%s): %s", d.Id(), err) } - if err := waitMemberAssociationDeleted(ctx, conn, accountID, d.Timeout(schema.TimeoutDelete)); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Amazon Inspector Member Association (%s): waiting for completion: %s", accountID, err) + if _, err := waitMemberAssociationDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Member Association (%s) delete: %s", d.Id(), err) } return diags } -func FindMemberByAccountID(ctx context.Context, conn *inspector2.Client, id string) (*types.Member, error) { +func findMemberByAccountID(ctx context.Context, conn *inspector2.Client, id string) (*awstypes.Member, error) { input := &inspector2.GetMemberInput{ AccountId: aws.String(id), } + output, err := findMember(ctx, conn, input) + + if err != nil { + return nil, err + } + if status := output.RelationshipStatus; status == awstypes.RelationshipStatusRemoved { + return nil, &retry.NotFoundError{ + Message: string(status), + LastRequest: input, + } + } + + return output, nil +} + +func findMember(ctx context.Context, conn *inspector2.Client, input *inspector2.GetMemberInput) (*awstypes.Member, error) { output, err := conn.GetMember(ctx, input) - if errs.IsA[*types.AccessDeniedException](err) || errs.IsA[*types.ResourceNotFoundException](err) { + if errs.IsAErrorMessageContains[*awstypes.AccessDeniedException](err, "Invoking account does not have access to get member account") || errs.IsA[*awstypes.ResourceNotFoundException](err) { return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, @@ -159,50 +173,54 @@ func FindMemberByAccountID(ctx context.Context, conn *inspector2.Client, id stri return nil, tfresource.NewEmptyResultError(input) } - if status := output.Member.RelationshipStatus; status == types.RelationshipStatusRemoved { - return nil, &retry.NotFoundError{ - Message: string(status), - LastRequest: input, + return output.Member, nil +} + +func statusMemberAssociation(ctx context.Context, conn *inspector2.Client, id string) retry.StateRefreshFunc { + return func() (any, string, error) { + output, err := findMemberByAccountID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + if err != nil { + return nil, "", err } - } - return output.Member, nil + return output, string(output.RelationshipStatus), nil + } } -func waitMemberAssociationCreated(ctx context.Context, conn *inspector2.Client, id string, timeout time.Duration) error { +func waitMemberAssociationCreated(ctx context.Context, conn *inspector2.Client, id string, timeout time.Duration) (*awstypes.Member, error) { stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(types.RelationshipStatusCreated), - Target: enum.Slice(types.RelationshipStatusEnabled), + Pending: enum.Slice(awstypes.RelationshipStatusCreated), + Target: enum.Slice(awstypes.RelationshipStatusEnabled), Refresh: statusMemberAssociation(ctx, conn, id), Timeout: timeout, } - _, err := stateConf.WaitForStateContext(ctx) - return err + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*awstypes.Member); ok { + return output, err + } + + return nil, err } -func waitMemberAssociationDeleted(ctx context.Context, conn *inspector2.Client, id string, timeout time.Duration) error { +func waitMemberAssociationDeleted(ctx context.Context, conn *inspector2.Client, id string, timeout time.Duration) (*awstypes.Member, error) { stateConf := &retry.StateChangeConf{ - Pending: enum.Slice(types.RelationshipStatusCreated, types.RelationshipStatusEnabled), + Pending: enum.Slice(awstypes.RelationshipStatusCreated, awstypes.RelationshipStatusEnabled), Target: []string{}, Refresh: statusMemberAssociation(ctx, conn, id), Timeout: timeout, } - _, err := stateConf.WaitForStateContext(ctx) - return err -} + outputRaw, err := stateConf.WaitForStateContext(ctx) -func statusMemberAssociation(ctx context.Context, conn *inspector2.Client, id string) retry.StateRefreshFunc { - return func() (any, string, error) { - member, err := FindMemberByAccountID(ctx, conn, id) - if tfresource.NotFound(err) { - return nil, "", nil - } - if err != nil { - return nil, "", err - } - - return member, string(member.RelationshipStatus), nil + if output, ok := outputRaw.(*awstypes.Member); ok { + return output, err } + + return nil, err } diff --git a/internal/service/inspector2/member_association_test.go b/internal/service/inspector2/member_association_test.go index 8eb577e2e35..6eaafd756c1 100644 --- a/internal/service/inspector2/member_association_test.go +++ b/internal/service/inspector2/member_association_test.go @@ -88,10 +88,6 @@ func testAccCheckMemberAssociationExists(ctx context.Context, n string) resource return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return fmt.Errorf("No Inspector2 Member Association ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).Inspector2Client(ctx) _, err := tfinspector2.FindMemberByAccountID(ctx, conn, rs.Primary.ID) diff --git a/internal/service/inspector2/organization_configuration.go b/internal/service/inspector2/organization_configuration.go index 79639412642..1e2125c7ed4 100644 --- a/internal/service/inspector2/organization_configuration.go +++ b/internal/service/inspector2/organization_configuration.go @@ -5,24 +5,23 @@ package inspector2 import ( "context" - "fmt" "log" "time" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/inspector2" - "github.com/aws/aws-sdk-go-v2/service/inspector2/types" + awstypes "github.com/aws/aws-sdk-go-v2/service/inspector2/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" "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/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_inspector2_organization_configuration") -func ResourceOrganizationConfiguration() *schema.Resource { +// @SDKResource("aws_inspector2_organization_configuration", name="Organization Configuration") +func resourceOrganizationConfiguration() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationConfigurationCreate, ReadWithoutTimeout: resourceOrganizationConfigurationRead, @@ -73,8 +72,7 @@ func ResourceOrganizationConfiguration() *schema.Resource { } const ( - ResNameOrganizationConfiguration = "Organization Configuration" - orgConfigMutex = "f14b54d7-2b10-58c2-9c1b-c48260a4825d" + orgConfigMutex = "f14b54d7-2b10-58c2-9c1b-c48260a4825d" ) func resourceOrganizationConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -87,59 +85,53 @@ func resourceOrganizationConfigurationCreate(ctx context.Context, d *schema.Reso func resourceOrganizationConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - out, err := conn.DescribeOrganizationConfiguration(ctx, &inspector2.DescribeOrganizationConfigurationInput{}) + output, err := findOrganizationConfiguration(ctx, conn) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Inspector2 OrganizationConfiguration (%s) not found, removing from state", d.Id()) + log.Printf("[WARN] Inspector2 Organization Configuration (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionReading, ResNameOrganizationConfiguration, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Inspector2 Organization Configuration (%s): %s", d.Id(), err) } - if err := d.Set("auto_enable", []interface{}{flattenAutoEnable(out.AutoEnable)}); err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionSetting, ResNameOrganizationConfiguration, d.Id(), err) + if err := d.Set("auto_enable", []interface{}{flattenAutoEnable(output.AutoEnable)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting auto_enable: %s", err) } - - d.Set("max_account_limit_reached", out.MaxAccountLimitReached) + d.Set("max_account_limit_reached", output.MaxAccountLimitReached) return diags } func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) - update := false - - in := &inspector2.UpdateOrganizationConfigurationInput{} - - if d.HasChanges("auto_enable") { - in.AutoEnable = expandAutoEnable(d.Get("auto_enable").([]interface{})[0].(map[string]interface{})) - update = true - } - - if !update { - return diags + autoEnable := expandAutoEnable(d.Get("auto_enable").([]interface{})[0].(map[string]interface{})) + input := &inspector2.UpdateOrganizationConfigurationInput{ + AutoEnable: autoEnable, } conns.GlobalMutexKV.Lock(orgConfigMutex) defer conns.GlobalMutexKV.Unlock(orgConfigMutex) - log.Printf("[DEBUG] Updating Inspector2 Organization Configuration (%s): %#v", d.Id(), in) - _, err := conn.UpdateOrganizationConfiguration(ctx, in) + _, err := conn.UpdateOrganizationConfiguration(ctx, input) + if err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionUpdating, ResNameOrganizationConfiguration, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Inspector2 Organization Configuration (%s): %s", d.Id(), err) + } + + timeout := d.Timeout(schema.TimeoutUpdate) + if d.IsNewResource() { + timeout = d.Timeout(schema.TimeoutCreate) } - if err := waitOrganizationConfigurationUpdated(ctx, conn, d.Get("auto_enable.0.ec2").(bool), d.Get("auto_enable.0.ecr").(bool), d.Get("auto_enable.0.lambda").(bool), d.Get("auto_enable.0.lambda_code").(bool), d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionWaitingForUpdate, ResNameOrganizationConfiguration, d.Id(), err) + if _, err := waitOrganizationConfigurationUpdated(ctx, conn, autoEnable, timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Organization Configuration (%s) update: %s", d.Id(), err) } return append(diags, resourceOrganizationConfigurationRead(ctx, d, meta)...) @@ -147,141 +139,129 @@ func resourceOrganizationConfigurationUpdate(ctx context.Context, d *schema.Reso func resourceOrganizationConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).Inspector2Client(ctx) conns.GlobalMutexKV.Lock(orgConfigMutex) defer conns.GlobalMutexKV.Unlock(orgConfigMutex) - in := &inspector2.UpdateOrganizationConfigurationInput{ - AutoEnable: &types.AutoEnable{ - Ec2: aws.Bool(false), - Ecr: aws.Bool(false), - Lambda: aws.Bool(false), - LambdaCode: aws.Bool(false), - }, + log.Printf("[DEBUG] Deleting Inspector2 Organization Configuration: %s", d.Id()) + autoEnable := &awstypes.AutoEnable{ + Ec2: aws.Bool(false), + Ecr: aws.Bool(false), + Lambda: aws.Bool(false), + LambdaCode: aws.Bool(false), } + _, err := conn.UpdateOrganizationConfiguration(ctx, &inspector2.UpdateOrganizationConfigurationInput{ + AutoEnable: autoEnable, + }) - log.Printf("[DEBUG] Setting Inspector2 Organization Configuration (%s): %#v", d.Id(), in) - _, err := conn.UpdateOrganizationConfiguration(ctx, in) if err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionUpdating, ResNameOrganizationConfiguration, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating Inspector2 Organization Configuration (%s): %s", d.Id(), err) } - if err := waitOrganizationConfigurationUpdated(ctx, conn, false, false, false, false, d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.AppendDiagError(diags, names.Inspector2, create.ErrActionWaitingForUpdate, ResNameOrganizationConfiguration, d.Id(), err) + if _, err := waitOrganizationConfigurationUpdated(ctx, conn, autoEnable, d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Inspector2 Organization Configuration (%s) delete: %s", d.Id(), err) } return diags } -func waitOrganizationConfigurationUpdated(ctx context.Context, conn *inspector2.Client, ec2, ecr, lambda, lambda_code bool, timeout time.Duration) error { - needle := fmt.Sprintf("%t:%t:%t:%t", ec2, ecr, lambda, lambda_code) - - all := []string{ - fmt.Sprintf("%t:%t:%t:%t", false, false, false, false), - fmt.Sprintf("%t:%t:%t:%t", false, false, false, true), - fmt.Sprintf("%t:%t:%t:%t", false, true, false, false), - fmt.Sprintf("%t:%t:%t:%t", false, true, false, true), - fmt.Sprintf("%t:%t:%t:%t", false, false, true, false), - fmt.Sprintf("%t:%t:%t:%t", false, false, true, true), - fmt.Sprintf("%t:%t:%t:%t", false, true, true, false), - fmt.Sprintf("%t:%t:%t:%t", false, true, true, true), - fmt.Sprintf("%t:%t:%t:%t", true, false, false, false), - fmt.Sprintf("%t:%t:%t:%t", true, false, false, true), - fmt.Sprintf("%t:%t:%t:%t", true, false, true, false), - fmt.Sprintf("%t:%t:%t:%t", true, false, true, true), - fmt.Sprintf("%t:%t:%t:%t", true, true, false, false), - fmt.Sprintf("%t:%t:%t:%t", true, true, false, true), - fmt.Sprintf("%t:%t:%t:%t", true, true, true, false), - fmt.Sprintf("%t:%t:%t:%t", true, true, true, true), - } +func findOrganizationConfiguration(ctx context.Context, conn *inspector2.Client) (*inspector2.DescribeOrganizationConfigurationOutput, error) { + input := &inspector2.DescribeOrganizationConfigurationInput{} + output, err := conn.DescribeOrganizationConfiguration(ctx, input) - for i, v := range all { - if v == needle { - all = append(all[:i], all[i+1:]...) - break + if errs.IsAErrorMessageContains[*awstypes.AccessDeniedException](err, "Invoking account does not have access to describe the organization configuration") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } } - stateConf := &retry.StateChangeConf{ - Pending: all, - Target: []string{needle}, - Refresh: statusOrganizationConfiguration(ctx, conn), - Timeout: timeout, - NotFoundChecks: 20, - ContinuousTargetOccurence: 2, - MinTimeout: time.Second * 5, + if err != nil { + return nil, err } - _, err := stateConf.WaitForStateContext(ctx) + if output == nil || output.AutoEnable == nil { + return nil, tfresource.NewEmptyResultError(input) + } - return err + return output, nil } -func statusOrganizationConfiguration(ctx context.Context, conn *inspector2.Client) retry.StateRefreshFunc { - return func() (interface{}, string, error) { - out, err := conn.DescribeOrganizationConfiguration(ctx, &inspector2.DescribeOrganizationConfigurationInput{}) - if tfresource.NotFound(err) { - return nil, "", nil - } +func waitOrganizationConfigurationUpdated(ctx context.Context, conn *inspector2.Client, target *awstypes.AutoEnable, timeout time.Duration) (*inspector2.DescribeOrganizationConfigurationOutput, error) { //nolint:unparam + var output *inspector2.DescribeOrganizationConfigurationOutput + + _, err := tfresource.RetryUntilEqual(ctx, timeout, true, func() (bool, error) { + var err error + output, err = findOrganizationConfiguration(ctx, conn) if err != nil { - return nil, "", err + return false, err } - return out, fmt.Sprintf("%t:%t:%t:%t", aws.ToBool(out.AutoEnable.Ec2), aws.ToBool(out.AutoEnable.Ecr), aws.ToBool(out.AutoEnable.Lambda), aws.ToBool(out.AutoEnable.LambdaCode)), nil + equal := aws.ToBool(output.AutoEnable.Ec2) == aws.ToBool(target.Ec2) + equal = equal && aws.ToBool(output.AutoEnable.Ecr) == aws.ToBool(target.Ecr) + equal = equal && aws.ToBool(output.AutoEnable.Lambda) == aws.ToBool(target.Lambda) + equal = equal && aws.ToBool(output.AutoEnable.LambdaCode) == aws.ToBool(target.LambdaCode) + + return equal, nil + }) + + if err != nil { + return nil, err } + + return output, nil } -func flattenAutoEnable(apiObject *types.AutoEnable) map[string]interface{} { +func flattenAutoEnable(apiObject *awstypes.AutoEnable) map[string]interface{} { if apiObject == nil { return nil } - m := map[string]interface{}{} + tfMap := map[string]interface{}{} if v := apiObject.Ec2; v != nil { - m["ec2"] = aws.ToBool(v) + tfMap["ec2"] = aws.ToBool(v) } if v := apiObject.Ecr; v != nil { - m["ecr"] = aws.ToBool(v) + tfMap["ecr"] = aws.ToBool(v) } if v := apiObject.Lambda; v != nil { - m["lambda"] = aws.ToBool(v) + tfMap["lambda"] = aws.ToBool(v) } if v := apiObject.LambdaCode; v != nil { - m["lambda_code"] = aws.ToBool(v) + tfMap["lambda_code"] = aws.ToBool(v) } - return m + return tfMap } -func expandAutoEnable(tfMap map[string]interface{}) *types.AutoEnable { +func expandAutoEnable(tfMap map[string]interface{}) *awstypes.AutoEnable { if tfMap == nil { return nil } - a := &types.AutoEnable{} + apiObject := &awstypes.AutoEnable{} if v, ok := tfMap["ec2"].(bool); ok { - a.Ec2 = aws.Bool(v) + apiObject.Ec2 = aws.Bool(v) } if v, ok := tfMap["ecr"].(bool); ok { - a.Ecr = aws.Bool(v) + apiObject.Ecr = aws.Bool(v) } if v, ok := tfMap["lambda"].(bool); ok { - a.Lambda = aws.Bool(v) + apiObject.Lambda = aws.Bool(v) } if v, ok := tfMap["lambda_code"].(bool); ok { - a.LambdaCode = aws.Bool(v) + apiObject.LambdaCode = aws.Bool(v) } - return a + return apiObject } diff --git a/internal/service/inspector2/organization_configuration_test.go b/internal/service/inspector2/organization_configuration_test.go index dce4d02abba..f3ba0a2a33e 100644 --- a/internal/service/inspector2/organization_configuration_test.go +++ b/internal/service/inspector2/organization_configuration_test.go @@ -5,20 +5,15 @@ package inspector2_test import ( "context" - "errors" "fmt" "testing" - "time" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/inspector2" "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" tfinspector2 "github.com/hashicorp/terraform-provider-aws/internal/service/inspector2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -165,108 +160,40 @@ func testAccCheckOrganizationConfigurationDestroy(ctx context.Context) resource. return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).Inspector2Client(ctx) - enabledDelAdAcct := false - for _, rs := range s.RootModule().Resources { if rs.Type != "aws_inspector2_organization_configuration" { continue } - out, err := conn.DescribeOrganizationConfiguration(ctx, &inspector2.DescribeOrganizationConfigurationInput{}) - - if errs.MessageContains(err, "AccessDenied", "Invoking account does not") { - if err := testEnableDelegatedAdminAccount(ctx, conn, acctest.AccountID()); err != nil { - return err - } - - enabledDelAdAcct = true + _, err := tfinspector2.FindOrganizationConfiguration(ctx, conn) - out, err = conn.DescribeOrganizationConfiguration(ctx, &inspector2.DescribeOrganizationConfigurationInput{}) + if tfresource.NotFound(err) { + continue } if err != nil { - if enabledDelAdAcct { - if err := testDisableDelegatedAdminAccount(ctx, conn, acctest.AccountID()); err != nil { - return err - } - } - - return create.Error(names.Inspector2, create.ErrActionCheckingDestroyed, tfinspector2.ResNameOrganizationConfiguration, rs.Primary.ID, err) + return err } - if out != nil && out.AutoEnable != nil && !aws.ToBool(out.AutoEnable.Ec2) && !aws.ToBool(out.AutoEnable.Ecr) && !aws.ToBool(out.AutoEnable.Lambda) && !aws.ToBool(out.AutoEnable.LambdaCode) { - if enabledDelAdAcct { - if err := testDisableDelegatedAdminAccount(ctx, conn, acctest.AccountID()); err != nil { - return err - } - } - - return nil - } - - if enabledDelAdAcct { - if err := testDisableDelegatedAdminAccount(ctx, conn, acctest.AccountID()); err != nil { - return err - } - } - - return create.Error(names.Inspector2, create.ErrActionCheckingDestroyed, tfinspector2.ResNameOrganizationConfiguration, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("Inspector2 Organization Configuration %s still exists", rs.Primary.ID) } return nil } } -func testEnableDelegatedAdminAccount(ctx context.Context, conn *inspector2.Client, accountID string) error { - _, err := conn.EnableDelegatedAdminAccount(ctx, &inspector2.EnableDelegatedAdminAccountInput{ - DelegatedAdminAccountId: aws.String(accountID), - }) - if err != nil { - return err - } - - if err := tfinspector2.WaitDelegatedAdminAccountEnabled(ctx, conn, accountID, time.Minute*2); err != nil { - return err - } - - return nil -} - -func testDisableDelegatedAdminAccount(ctx context.Context, conn *inspector2.Client, accountID string) error { - _, err := conn.DisableDelegatedAdminAccount(ctx, &inspector2.DisableDelegatedAdminAccountInput{ - DelegatedAdminAccountId: aws.String(accountID), - }) - if err != nil { - return err - } - - if err := tfinspector2.WaitDelegatedAdminAccountDisabled(ctx, conn, accountID, time.Minute*2); err != nil { - return err - } - - return nil -} - -func testAccCheckOrganizationConfigurationExists(ctx context.Context, name string) resource.TestCheckFunc { +func testAccCheckOrganizationConfigurationExists(ctx context.Context, n string) 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.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameOrganizationConfiguration, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameOrganizationConfiguration, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).Inspector2Client(ctx) - _, err := conn.DescribeOrganizationConfiguration(ctx, &inspector2.DescribeOrganizationConfigurationInput{}) + _, err := tfinspector2.FindOrganizationConfiguration(ctx, conn) - if err != nil { - return create.Error(names.Inspector2, create.ErrActionCheckingExistence, tfinspector2.ResNameOrganizationConfiguration, rs.Primary.ID, err) - } - - return nil + return err } } diff --git a/internal/service/inspector2/service_package_gen.go b/internal/service/inspector2/service_package_gen.go index 70f40d55f18..14f29b966f9 100644 --- a/internal/service/inspector2/service_package_gen.go +++ b/internal/service/inspector2/service_package_gen.go @@ -29,20 +29,23 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePackageSDKResource { return []*types.ServicePackageSDKResource{ { - Factory: ResourceDelegatedAdminAccount, + Factory: resourceDelegatedAdminAccount, TypeName: "aws_inspector2_delegated_admin_account", + Name: "Delegated Admin Account", }, { Factory: ResourceEnabler, TypeName: "aws_inspector2_enabler", }, { - Factory: ResourceMemberAssociation, + Factory: resourceMemberAssociation, TypeName: "aws_inspector2_member_association", + Name: "Member Association", }, { - Factory: ResourceOrganizationConfiguration, + Factory: resourceOrganizationConfiguration, TypeName: "aws_inspector2_organization_configuration", + Name: "Organization Configuration", }, } }