From 9988e3363c5780d55d3fe24e21fc7474d706fb23 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Mon, 12 Dec 2022 14:13:43 -0500 Subject: [PATCH 1/5] r/aws_auditmanager_account_registration: resource implementation --- .../auditmanager/account_registration.go | 193 ++++++++++++++++++ internal/service/auditmanager/exports_test.go | 5 +- 2 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 internal/service/auditmanager/account_registration.go diff --git a/internal/service/auditmanager/account_registration.go b/internal/service/auditmanager/account_registration.go new file mode 100644 index 00000000000..0e680ccc732 --- /dev/null +++ b/internal/service/auditmanager/account_registration.go @@ -0,0 +1,193 @@ +package auditmanager + +import ( + "context" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/auditmanager" + awstypes "github.com/aws/aws-sdk-go-v2/service/auditmanager/types" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func init() { + registerFrameworkResourceFactory(newResourceAccountRegistration) +} + +func newResourceAccountRegistration(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceAccountRegistration{}, nil +} + +const ( + ResNameAccountRegistration = "AccountRegistration" +) + +type resourceAccountRegistration struct { + framework.ResourceWithConfigure +} + +func (r *resourceAccountRegistration) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_auditmanager_account_registration" +} + +func (r *resourceAccountRegistration) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "delegated_admin_account": schema.StringAttribute{ + Optional: true, + }, + "deregister_on_destroy": schema.BoolAttribute{ + Optional: true, + }, + "kms_key": schema.StringAttribute{ + Optional: true, + }, + "id": framework.IDAttribute(), + "status": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r *resourceAccountRegistration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().AuditManagerClient + accountID := r.Meta().AccountID + region := r.Meta().Region + id := strings.Join([]string{accountID, region}, ",") + + var plan resourceAccountRegistrationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + in := auditmanager.RegisterAccountInput{} + if !plan.DelegatedAdminAccount.IsNull() { + in.DelegatedAdminAccount = aws.String(plan.DelegatedAdminAccount.ValueString()) + } + if !plan.KmsKey.IsNull() { + in.KmsKey = aws.String(plan.KmsKey.ValueString()) + } + out, err := conn.RegisterAccount(ctx, &in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.AuditManager, create.ErrActionCreating, ResNameAccountRegistration, id, nil), + err.Error(), + ) + return + } + + state := plan + state.ID = types.StringValue(id) + state.Status = flex.StringValueToFramework(ctx, out.Status) + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +func (r *resourceAccountRegistration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().AuditManagerClient + + var state resourceAccountRegistrationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // There is no API to get account registration attributes like delegated admin account + // and KMS key. Read will instead call the GetAccountStatus API to confirm an active + // account status. + out, err := conn.GetAccountStatus(ctx, &auditmanager.GetAccountStatusInput{}) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.AuditManager, create.ErrActionReading, ResNameAccountRegistration, state.ID.String(), nil), + err.Error(), + ) + return + } + if out.Status == awstypes.AccountStatusInactive { + resp.State.RemoveResource(ctx) + return + } + + state.Status = flex.StringValueToFramework(ctx, out.Status) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceAccountRegistration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().AuditManagerClient + + var plan, state resourceAccountRegistrationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.DelegatedAdminAccount.Equal(state.DelegatedAdminAccount) || + !plan.KmsKey.Equal(state.KmsKey) { + in := auditmanager.RegisterAccountInput{} + if !plan.DelegatedAdminAccount.IsNull() { + in.DelegatedAdminAccount = aws.String(plan.DelegatedAdminAccount.ValueString()) + } + if !plan.KmsKey.IsNull() { + in.KmsKey = aws.String(plan.KmsKey.ValueString()) + } + out, err := conn.RegisterAccount(ctx, &in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.AuditManager, create.ErrActionUpdating, ResNameAccountRegistration, state.ID.String(), nil), + err.Error(), + ) + return + } + + state.DelegatedAdminAccount = plan.DelegatedAdminAccount + state.KmsKey = plan.KmsKey + state.Status = flex.StringValueToFramework(ctx, out.Status) + } + + if !plan.DeregisterOnDestroy.Equal(state.DeregisterOnDestroy) { + state.DeregisterOnDestroy = plan.DeregisterOnDestroy + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceAccountRegistration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().AuditManagerClient + + var state resourceAccountRegistrationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if state.DeregisterOnDestroy.ValueBool() { + _, err := conn.DeregisterAccount(ctx, &auditmanager.DeregisterAccountInput{}) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.AuditManager, create.ErrActionDeleting, ResNameAccountRegistration, state.ID.String(), nil), + err.Error(), + ) + } + } +} + +func (r *resourceAccountRegistration) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceAccountRegistrationData struct { + DelegatedAdminAccount types.String `tfsdk:"delegated_admin_account"` + DeregisterOnDestroy types.Bool `tfsdk:"deregister_on_destroy"` + KmsKey types.String `tfsdk:"kms_key"` + ID types.String `tfsdk:"id"` + Status types.String `tfsdk:"status"` +} diff --git a/internal/service/auditmanager/exports_test.go b/internal/service/auditmanager/exports_test.go index 8b2aeb1852c..a361ee8ec21 100644 --- a/internal/service/auditmanager/exports_test.go +++ b/internal/service/auditmanager/exports_test.go @@ -2,6 +2,7 @@ package auditmanager // Exports for use in tests only. var ( - ResourceControl = newResourceControl - ResourceFramework = newResourceFramework + ResourceControl = newResourceControl + ResourceFramework = newResourceFramework + ResourceAccountRegistration = newResourceAccountRegistration ) From 7ae5441f3c74885cd450fa894a59838846a3f8b6 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Mon, 12 Dec 2022 16:16:22 -0500 Subject: [PATCH 2/5] r/aws_auditmanager_account_registration: tests --- docs/acc-test-environment-variables.md | 1 + .../auditmanager/account_registration_test.go | 205 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 internal/service/auditmanager/account_registration_test.go diff --git a/docs/acc-test-environment-variables.md b/docs/acc-test-environment-variables.md index b57b2cb4caf..90542d32a5c 100644 --- a/docs/acc-test-environment-variables.md +++ b/docs/acc-test-environment-variables.md @@ -37,6 +37,7 @@ Environment variables (beyond standard AWS Go SDK ones) used by acceptance testi | `APNS_VOIP_TOKEN_KEY` | Token key file content (.p8 format) for VOIP Apple Push Notification Service in Pinpoint testing. | | `APNS_VOIP_TOKEN_KEY_ID` | Identifier for VOIP Apple Push Notification Service Token Key in Pinpoint testing. | | `APPRUNNER_CUSTOM_DOMAIN` | A custom domain endpoint (root domain, subdomain, or wildcard) for AppRunner Custom Domain Association testing. | +| `AUDITMANAGER_DEREGISTER_ACCOUNT_ON_DESTROY` | Flag to execute tests that will disable AuditManager in the account upon destruction. | | `AWS_ALTERNATE_ACCESS_KEY_ID` | AWS access key ID with access to a secondary AWS account for tests requiring multiple accounts. Requires `AWS_ALTERNATE_SECRET_ACCESS_KEY`. Conflicts with `AWS_ALTERNATE_PROFILE`. | | `AWS_ALTERNATE_SECRET_ACCESS_KEY` | AWS secret access key with access to a secondary AWS account for tests requiring multiple accounts. Requires `AWS_ALTERNATE_ACCESS_KEY_ID`. Conflicts with `AWS_ALTERNATE_PROFILE`. | | `AWS_ALTERNATE_PROFILE` | AWS profile with access to a secondary AWS account for tests requiring multiple accounts. Conflicts with `AWS_ALTERNATE_ACCESS_KEY_ID` and `AWS_ALTERNATE_SECRET_ACCESS_KEY`. | diff --git a/internal/service/auditmanager/account_registration_test.go b/internal/service/auditmanager/account_registration_test.go new file mode 100644 index 00000000000..d8e3e3d037d --- /dev/null +++ b/internal/service/auditmanager/account_registration_test.go @@ -0,0 +1,205 @@ +package auditmanager_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/auditmanager" + "github.com/aws/aws-sdk-go-v2/service/auditmanager/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + tfauditmanager "github.com/hashicorp/terraform-provider-aws/internal/service/auditmanager" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccAuditManagerAccountRegistration_serial(t *testing.T) { + testFuncs := map[string]func(t *testing.T){ + "basic": testAccAccountRegistration_basic, + "disappears": testAccAccountRegistration_disappears, + "kms key": testAccAccountRegistration_optionalKMSKey, + } + + for name, testFunc := range testFuncs { + testFunc := testFunc + + t.Run(name, func(t *testing.T) { + testFunc(t) + }) + } +} + +func testAccAccountRegistration_basic(t *testing.T) { + resourceName := "aws_auditmanager_account_registration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.AuditManagerEndpointID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AuditManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccountRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAccountRegistrationConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountRegisterationIsActive(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAccountRegistration_disappears(t *testing.T) { + if os.Getenv("AUDITMANAGER_DEREGISTER_ACCOUNT_ON_DESTROY") == "" { + t.Skip("Environment variable AUDITMANAGER_DEREGISTER_ACCOUNT_ON_DESTROY is not set") + } + + resourceName := "aws_auditmanager_account_registration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.AuditManagerEndpointID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AuditManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccountRegistrationDestroy, + Steps: []resource.TestStep{ + { + // deregister_on_destroy must be enabled for the disappears helper to disable + // audit manager on destroy and trigger the non-empty plan after state refresh + Config: testAccAccountRegistrationConfig_deregisterOnDestroy(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountRegisterationIsActive(resourceName), + acctest.CheckFrameworkResourceDisappears(acctest.Provider, tfauditmanager.ResourceAccountRegistration, resourceName), + ), + }, + { + RefreshState: true, + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccAccountRegistration_optionalKMSKey(t *testing.T) { + resourceName := "aws_auditmanager_account_registration.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(t) + acctest.PreCheckPartitionHasService(names.AuditManagerEndpointID, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.AuditManagerEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccountRegistrationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAccountRegistrationConfig_KMSKey(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountRegisterationIsActive(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "kms_key"), + ), + }, + { + Config: testAccAccountRegistrationConfig_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountRegisterationIsActive(resourceName), + resource.TestCheckNoResourceAttr(resourceName, "kms_key"), + ), + }, + { + Config: testAccAccountRegistrationConfig_KMSKey(), + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountRegisterationIsActive(resourceName), + resource.TestCheckResourceAttrSet(resourceName, "kms_key"), + ), + }, + }, + }) +} + +// testAccCheckAccountRegistrationDestroy verfies GetAccountStatus does not return an error +// +// Since this resource manages activation/deactivation of AuditManager, there is nothing +// to destroy. Additionally, because registration may remain active depending on whether +// the deactivate_on_destroy attribute was set, this function does not check that account +// registration is inactive, simply that the status check returns a valid response. +func testAccCheckAccountRegistrationDestroy(s *terraform.State) error { + ctx := context.Background() + conn := acctest.Provider.Meta().(*conns.AWSClient).AuditManagerClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_auditmanager_account_registration" { + continue + } + + _, err := conn.GetAccountStatus(ctx, &auditmanager.GetAccountStatusInput{}) + if err != nil { + return err + } + } + + return nil +} + +// testAccCheckAccountRegisterationIsActive verifies AuditManager is active in the current account/region combination +func testAccCheckAccountRegisterationIsActive(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.AuditManager, create.ErrActionCheckingExistence, tfauditmanager.ResNameAccountRegistration, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.AuditManager, create.ErrActionCheckingExistence, tfauditmanager.ResNameAccountRegistration, name, errors.New("not set")) + } + + ctx := context.Background() + conn := acctest.Provider.Meta().(*conns.AWSClient).AuditManagerClient + out, err := conn.GetAccountStatus(ctx, &auditmanager.GetAccountStatusInput{}) + if err != nil { + return create.Error(names.AuditManager, create.ErrActionCheckingExistence, tfauditmanager.ResNameAccountRegistration, rs.Primary.ID, err) + } + if out == nil || out.Status != types.AccountStatusActive { + return create.Error(names.AuditManager, create.ErrActionCheckingExistence, tfauditmanager.ResNameAccountRegistration, rs.Primary.ID, errors.New("audit manager not active")) + } + + return nil + } +} + +func testAccAccountRegistrationConfig_basic() string { + return ` +resource "aws_auditmanager_account_registration" "test" {} +` +} + +func testAccAccountRegistrationConfig_deregisterOnDestroy() string { + return ` +resource "aws_auditmanager_account_registration" "test" { + deregister_on_destroy = true +} +` +} + +func testAccAccountRegistrationConfig_KMSKey() string { + return ` +resource "aws_kms_key" "test" {} + +resource "aws_auditmanager_account_registration" "test" { + kms_key = aws_kms_key.test.arn +} +` +} From dcb2c85ac986eeda2262054ac6d60e61d1ee9474 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Mon, 12 Dec 2022 16:36:33 -0500 Subject: [PATCH 3/5] r/aws_auditmanager_account_registration: docs --- ...manager_account_registration.html.markdown | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 website/docs/r/auditmanager_account_registration.html.markdown diff --git a/website/docs/r/auditmanager_account_registration.html.markdown b/website/docs/r/auditmanager_account_registration.html.markdown new file mode 100644 index 00000000000..0969b20e0da --- /dev/null +++ b/website/docs/r/auditmanager_account_registration.html.markdown @@ -0,0 +1,50 @@ +--- +subcategory: "Audit Manager" +layout: "aws" +page_title: "AWS: aws_auditmanager_account_registration" +description: |- + Terraform resource for managing AWS Audit Manager Account Registration. +--- + +# Resource: aws_auditmanager_account_registration + +Terraform resource for managing AWS Audit Manager Account Registration. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_auditmanager_account_registration" "example" {} +``` + +### Deregister On Destroy + +```terraform +resource "aws_auditmanager_account_registration" "example" { + deregister_on_destroy = true +} +``` + +## Argument Reference + +The following arguments are optional: + +* `delegated_admin_account` - (Optional) Identifier for the delegated administrator account. +* `deregister_on_destroy` - (Optional) Flag to deregister AuditManager in the account upon destruction. Defaults to `false` (ie. AuditManager will remain active in the account, even if this resource is removed). +* `kms_key` - (Optional) KMS key identifier. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - Unique identifier for the account registration. This is a comma-separated concatenation of AWS account ID and region. +* `status` - Status of the account registration request. + +## Import + +Audit Manager Account Registration resources can be imported using the `id`, e.g., + +``` +$ terraform import aws_auditmanager_account_registration.example 012345678901,us-east-1 +``` From 833f75b51537c03fa587957afd8e395f42e47d49 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Mon, 12 Dec 2022 16:40:07 -0500 Subject: [PATCH 4/5] chore: add changelog entry --- .changelog/28314.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/28314.txt diff --git a/.changelog/28314.txt b/.changelog/28314.txt new file mode 100644 index 00000000000..bd767eb9d9f --- /dev/null +++ b/.changelog/28314.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_auditmanager_account_registration +``` From c7c2ad004a642bca22c88aab7c2ae19a36634b98 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Wed, 14 Dec 2022 08:53:54 -0500 Subject: [PATCH 5/5] r/aws_auditmanager_account_registration: use only region for id --- internal/service/auditmanager/account_registration.go | 6 ++---- .../docs/r/auditmanager_account_registration.html.markdown | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/internal/service/auditmanager/account_registration.go b/internal/service/auditmanager/account_registration.go index 0e680ccc732..4dd4735c39e 100644 --- a/internal/service/auditmanager/account_registration.go +++ b/internal/service/auditmanager/account_registration.go @@ -2,7 +2,6 @@ package auditmanager import ( "context" - "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/auditmanager" @@ -59,9 +58,8 @@ func (r *resourceAccountRegistration) Schema(ctx context.Context, req resource.S func (r *resourceAccountRegistration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { conn := r.Meta().AuditManagerClient - accountID := r.Meta().AccountID - region := r.Meta().Region - id := strings.Join([]string{accountID, region}, ",") + // Registration is applied per region, so use this as the ID + id := r.Meta().Region var plan resourceAccountRegistrationData resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) diff --git a/website/docs/r/auditmanager_account_registration.html.markdown b/website/docs/r/auditmanager_account_registration.html.markdown index 0969b20e0da..0dba9942901 100644 --- a/website/docs/r/auditmanager_account_registration.html.markdown +++ b/website/docs/r/auditmanager_account_registration.html.markdown @@ -38,7 +38,7 @@ The following arguments are optional: In addition to all arguments above, the following attributes are exported: -* `id` - Unique identifier for the account registration. This is a comma-separated concatenation of AWS account ID and region. +* `id` - Unique identifier for the account registration. Since registration is applied per AWS region, this will be the active region name (ex. `us-east-1`). * `status` - Status of the account registration request. ## Import @@ -46,5 +46,5 @@ In addition to all arguments above, the following attributes are exported: Audit Manager Account Registration resources can be imported using the `id`, e.g., ``` -$ terraform import aws_auditmanager_account_registration.example 012345678901,us-east-1 +$ terraform import aws_auditmanager_account_registration.example us-east-1 ```