Skip to content

Commit

Permalink
Merge pull request #38254 from omnibrian/b-aws_inspector2_enabler-reg…
Browse files Browse the repository at this point in the history
…ion-support

fix: Support inspector2 enabler in regions without LAMBDA_CODE
  • Loading branch information
ewbankkit authored Sep 25, 2024
2 parents 4fce580 + 9675144 commit 32ef5fd
Show file tree
Hide file tree
Showing 14 changed files with 367 additions and 393 deletions.
7 changes: 7 additions & 0 deletions .changelog/38254.txt
Original file line number Diff line number Diff line change
@@ -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
```
2 changes: 1 addition & 1 deletion internal/acctest/partition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down
19 changes: 0 additions & 19 deletions internal/errs/errs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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().
Expand Down Expand Up @@ -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)
Expand Down
210 changes: 110 additions & 100 deletions internal/service/inspector2/delegated_admin_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -55,148 +56,139 @@ 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)...)
}

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
Expand All @@ -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
}
Loading

0 comments on commit 32ef5fd

Please sign in to comment.