From da39408d55e3a4033e88473c11352ea0e6b3eb6e Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sat, 10 Aug 2024 21:17:57 +0900 Subject: [PATCH 01/13] implement newResourceCloudTrailOrganizationAdminAccount --- .../cloudtrail_organization_admin_account.go | 183 ++++++++++++++++++ internal/service/cloudtrail/find.go | 26 +++ 2 files changed, 209 insertions(+) create mode 100644 internal/service/cloudtrail/cloudtrail_organization_admin_account.go create mode 100644 internal/service/cloudtrail/find.go diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go new file mode 100644 index 00000000000..4391012962b --- /dev/null +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -0,0 +1,183 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudtrail + +import ( + "context" + "errors" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudtrail" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudtrail/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/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "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" + fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +const ( + ResNameCloudTrailOrganizationAdminAccount = "CloudTrailOrganizationAdminAccount" +) + +// @FrameworkResource("aws_cloudtrail_organization_admin_account", name="CloudTrail Organization Admin Account") +func newResourceCloudTrailOrganizationAdminAccount(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceCloudTrailOrganizationAdminAccount{}, nil +} + +type resourceCloudTrailOrganizationAdminAccount struct { + framework.ResourceWithConfigure + framework.WithNoUpdate +} + +func (r *resourceCloudTrailOrganizationAdminAccount) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_cloudtrail_organization_admin_account" +} + +func (r *resourceCloudTrailOrganizationAdminAccount) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: schema.StringAttribute{ + Computed: true, + }, + "delegated_admin_account_id": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + fwvalidators.AWSAccountID(), + }, + }, + names.AttrEmail: schema.StringAttribute{ + Computed: true, + }, + names.AttrName: schema.StringAttribute{ + Computed: true, + }, + "service_principal": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + cloudTrailConn := r.Meta().CloudTrailClient(ctx) + organizationsConn := r.Meta().OrganizationsClient(ctx) + + var plan resourceCloudTrailOrganizationAdminAccountData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + input := &cloudtrail.RegisterOrganizationDelegatedAdminInput{ + MemberAccountId: aws.String(plan.DelegatedAdminAccountID.ValueString()), + } + + _, err := cloudTrailConn.RegisterOrganizationDelegatedAdmin(ctx, input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameCloudTrailOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), nil), + err.Error(), + ) + return + } + + // Read after create to get computed attributes + readOutput, err := FindDelegatedAccountByAccountID(ctx, organizationsConn, plan.DelegatedAdminAccountID.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameCloudTrailOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), err), + err.Error(), + ) + return + } + + state := plan + state.ID = flex.StringToFramework(ctx, plan.DelegatedAdminAccountID.ValueStringPointer()) + + resp.Diagnostics.Append(flex.Flatten(ctx, readOutput, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().OrganizationsClient(ctx) + var state resourceCloudTrailOrganizationAdminAccountData + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + out, err := FindDelegatedAccountByAccountID(ctx, conn, state.ID.ValueString()) + + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().CloudTrailClient(ctx) + + var state resourceCloudTrailOrganizationAdminAccountData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := conn.DeregisterOrganizationDelegatedAdmin(ctx, &cloudtrail.DeregisterOrganizationDelegatedAdminInput{ + DelegatedAdminAccountId: aws.String(state.ID.ValueString()), + }) + if err != nil { + var nfe *awstypes.ResourceNotFoundException + if errors.As(err, &nfe) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), nil), + err.Error(), + ) + } +} + +func (r *resourceCloudTrailOrganizationAdminAccount) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) +} + +type resourceCloudTrailOrganizationAdminAccountData struct { + ARN types.String `tfsdk:"arn"` + DelegatedAdminAccountID types.String `tfsdk:"delegated_admin_account_id"` + Email types.String `tfsdk:"email"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ServicePrincipal types.String `tfsdk:"service_principal"` +} diff --git a/internal/service/cloudtrail/find.go b/internal/service/cloudtrail/find.go new file mode 100644 index 00000000000..aab0caa07b3 --- /dev/null +++ b/internal/service/cloudtrail/find.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudtrail + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/organizations" + "github.com/aws/aws-sdk-go-v2/service/organizations/types" + tforganizations "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" +) + +const ( + cloudTrailServicePrincipal = "cloudtrail.amazonaws.com" +) + +func FindDelegatedAccountByAccountID(ctx context.Context, conn *organizations.Client, accountID string) (*types.DelegatedAdministrator, error) { + + account, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, accountID, cloudTrailServicePrincipal) + if err != nil { + return nil, err + } + + return account, nil +} From 0a27eccba106a396ebec29501842dc0f03f66501 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sat, 10 Aug 2024 21:29:48 +0900 Subject: [PATCH 02/13] make gen --- internal/service/cloudtrail/service_package_gen.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/service/cloudtrail/service_package_gen.go b/internal/service/cloudtrail/service_package_gen.go index 9d995bb1aed..5b118cd6d19 100644 --- a/internal/service/cloudtrail/service_package_gen.go +++ b/internal/service/cloudtrail/service_package_gen.go @@ -19,7 +19,12 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv } func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { - return []*types.ServicePackageFrameworkResource{} + return []*types.ServicePackageFrameworkResource{ + { + Factory: newResourceCloudTrailOrganizationAdminAccount, + Name: "CloudTrail Organization Admin Account", + }, + } } func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePackageSDKDataSource { From 5cf794070f15a395a9e99c2fb1b10b874db64514 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sat, 10 Aug 2024 23:52:40 +0900 Subject: [PATCH 03/13] fix id --- .../cloudtrail_organization_admin_account.go | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go index 4391012962b..5b453aa00f2 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -13,6 +13,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-provider-aws/internal/create" @@ -52,6 +54,9 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Schema(ctx context.Context, Validators: []validator.String{ fwvalidators.AWSAccountID(), }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, names.AttrEmail: schema.StringAttribute{ Computed: true, @@ -100,7 +105,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, } state := plan - state.ID = flex.StringToFramework(ctx, plan.DelegatedAdminAccountID.ValueStringPointer()) + state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(cloudTrailServicePrincipal)) resp.Diagnostics.Append(flex.Flatten(ctx, readOutput, &state)...) @@ -121,7 +126,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r return } - out, err := FindDelegatedAccountByAccountID(ctx, conn, state.ID.ValueString()) + out, err := FindDelegatedAccountByAccountID(ctx, conn, state.DelegatedAdminAccountID.ValueString()) if tfresource.NotFound(err) { resp.State.RemoveResource(ctx) @@ -130,12 +135,14 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), err), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.DelegatedAdminAccountID.String(), err), err.Error(), ) return } + state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(cloudTrailServicePrincipal)) + resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) if resp.Diagnostics.HasError() { @@ -155,7 +162,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, } _, err := conn.DeregisterOrganizationDelegatedAdmin(ctx, &cloudtrail.DeregisterOrganizationDelegatedAdminInput{ - DelegatedAdminAccountId: aws.String(state.ID.ValueString()), + DelegatedAdminAccountId: aws.String(state.DelegatedAdminAccountID.ValueString()), }) if err != nil { var nfe *awstypes.ResourceNotFoundException @@ -163,21 +170,20 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, return } resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), nil), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.DelegatedAdminAccountID.String(), nil), err.Error(), ) } } -func (r *resourceCloudTrailOrganizationAdminAccount) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) +func (r *resourceCloudTrailOrganizationAdminAccount) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("delegated_admin_account_id"), request, response) } type resourceCloudTrailOrganizationAdminAccountData struct { - ARN types.String `tfsdk:"arn"` + Arn types.String `tfsdk:"arn"` DelegatedAdminAccountID types.String `tfsdk:"delegated_admin_account_id"` Email types.String `tfsdk:"email"` - ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` ServicePrincipal types.String `tfsdk:"service_principal"` } From 88e91892afeb6106c7f2168415778c80bf11c655 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:05:46 +0900 Subject: [PATCH 04/13] fix id import --- .../cloudtrail_organization_admin_account.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go index 5b453aa00f2..2718edfff3c 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -10,7 +10,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" awstypes "github.com/aws/aws-sdk-go-v2/service/cloudtrail/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/resource/schema/planmodifier" @@ -37,6 +36,7 @@ func newResourceCloudTrailOrganizationAdminAccount(_ context.Context) (resource. type resourceCloudTrailOrganizationAdminAccount struct { framework.ResourceWithConfigure framework.WithNoUpdate + framework.WithImportByID } func (r *resourceCloudTrailOrganizationAdminAccount) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { @@ -61,6 +61,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Schema(ctx context.Context, names.AttrEmail: schema.StringAttribute{ Computed: true, }, + names.AttrID: framework.IDAttribute(), names.AttrName: schema.StringAttribute{ Computed: true, }, @@ -126,7 +127,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r return } - out, err := FindDelegatedAccountByAccountID(ctx, conn, state.DelegatedAdminAccountID.ValueString()) + out, err := FindDelegatedAccountByAccountID(ctx, conn, state.ID.ValueString()) if tfresource.NotFound(err) { resp.State.RemoveResource(ctx) @@ -135,13 +136,14 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.DelegatedAdminAccountID.String(), err), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), err), err.Error(), ) return } - state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(cloudTrailServicePrincipal)) + state.DelegatedAdminAccountID = flex.StringValueToFramework(ctx, state.ID.ValueString()) + state.ServicePrincipal = flex.StringValueToFramework(ctx, cloudTrailServicePrincipal) resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) @@ -162,7 +164,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, } _, err := conn.DeregisterOrganizationDelegatedAdmin(ctx, &cloudtrail.DeregisterOrganizationDelegatedAdminInput{ - DelegatedAdminAccountId: aws.String(state.DelegatedAdminAccountID.ValueString()), + DelegatedAdminAccountId: aws.String(state.ID.ValueString()), }) if err != nil { var nfe *awstypes.ResourceNotFoundException @@ -170,20 +172,17 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, return } resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.DelegatedAdminAccountID.String(), nil), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), nil), err.Error(), ) } } -func (r *resourceCloudTrailOrganizationAdminAccount) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("delegated_admin_account_id"), request, response) -} - type resourceCloudTrailOrganizationAdminAccountData struct { Arn types.String `tfsdk:"arn"` DelegatedAdminAccountID types.String `tfsdk:"delegated_admin_account_id"` Email types.String `tfsdk:"email"` + ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` ServicePrincipal types.String `tfsdk:"service_principal"` } From 58fd05fb101c0df4311a63a3e6f2b641e0c0dab8 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 11 Aug 2024 23:30:59 +0900 Subject: [PATCH 05/13] add test --- .../cloudtrail_organization_admin_account.go | 6 +- ...udtrail_organization_admin_account_test.go | 139 ++++++++++++++++++ internal/service/cloudtrail/exports_test.go | 7 +- internal/service/cloudtrail/find.go | 4 +- 4 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go index 2718edfff3c..755e01a8c80 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -106,7 +106,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, } state := plan - state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(cloudTrailServicePrincipal)) + state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(ServicePrincipal)) resp.Diagnostics.Append(flex.Flatten(ctx, readOutput, &state)...) @@ -143,7 +143,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r } state.DelegatedAdminAccountID = flex.StringValueToFramework(ctx, state.ID.ValueString()) - state.ServicePrincipal = flex.StringValueToFramework(ctx, cloudTrailServicePrincipal) + state.ServicePrincipal = flex.StringValueToFramework(ctx, ServicePrincipal) resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) @@ -167,7 +167,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, DelegatedAdminAccountId: aws.String(state.ID.ValueString()), }) if err != nil { - var nfe *awstypes.ResourceNotFoundException + var nfe *awstypes.AccountNotRegisteredException if errors.As(err, &nfe) { return } diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go new file mode 100644 index 00000000000..06eb765f846 --- /dev/null +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go @@ -0,0 +1,139 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudtrail_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + organizationstypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" + "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/service/cloudtrail" + tforganizations "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Prerequisites: +// * Organizations management account +// * Organization member account +func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { + ctx := acctest.Context(t) + var organization organizationstypes.DelegatedAdministrator + resourceName := "aws_cloudtrail_organization_admin_account.test" + organizationData := "data.aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailOrganizationAdminAccountConfig(), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckCloudTrailOrganizationAdminAccountExists(ctx, resourceName, &organization), + acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "organizations", regexache.MustCompile("account/.+")), + resource.TestCheckResourceAttrPair(resourceName, names.AttrID, organizationData, "non_master_accounts.0.id"), + resource.TestCheckResourceAttr(resourceName, "service_principal", cloudtrail.ServicePrincipal), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { + ctx := acctest.Context(t) + var organization organizationstypes.DelegatedAdministrator + resourceName := "aws_cloudtrail_organization_admin_account.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailOrganizationAdminAccountConfig(), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudTrailOrganizationAdminAccountExists(ctx, resourceName, &organization), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, cloudtrail.ResourceCloudTrailOrganizationAdminAccount, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudtrail_organization_admin_account" { + continue + } + + _, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.Attributes["delegated_admin_account_id"], rs.Primary.Attributes["service_principal"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudTrail Organization Delegated Admin Account %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckCloudTrailOrganizationAdminAccountExists(ctx context.Context, n string, v *organizationstypes.DelegatedAdministrator) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) + + output, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.Attributes["delegated_admin_account_id"], rs.Primary.Attributes["service_principal"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCloudTrailOrganizationAdminAccountConfig() string { + return fmt.Sprint(` +data "aws_organizations_organization" "test" {} + +resource "aws_cloudtrail_organization_admin_account" "test" { + delegated_admin_account_id = data.aws_organizations_organization.test.non_master_accounts[0].id +}`) +} diff --git a/internal/service/cloudtrail/exports_test.go b/internal/service/cloudtrail/exports_test.go index e7e6112e2af..a604a27b0d4 100644 --- a/internal/service/cloudtrail/exports_test.go +++ b/internal/service/cloudtrail/exports_test.go @@ -8,7 +8,8 @@ var ( ResourceEventDataStore = resourceEventDataStore ResourceTrail = resourceTrail - FindEventDataStoreByARN = findEventDataStoreByARN - FindTrailByARN = findTrailByARN - ServiceAccountPerRegionMap = serviceAccountPerRegionMap + FindEventDataStoreByARN = findEventDataStoreByARN + FindTrailByARN = findTrailByARN + ServiceAccountPerRegionMap = serviceAccountPerRegionMap + ResourceCloudTrailOrganizationAdminAccount = newResourceCloudTrailOrganizationAdminAccount ) diff --git a/internal/service/cloudtrail/find.go b/internal/service/cloudtrail/find.go index aab0caa07b3..c0f80f9b3f7 100644 --- a/internal/service/cloudtrail/find.go +++ b/internal/service/cloudtrail/find.go @@ -12,12 +12,12 @@ import ( ) const ( - cloudTrailServicePrincipal = "cloudtrail.amazonaws.com" + ServicePrincipal = "cloudtrail.amazonaws.com" ) func FindDelegatedAccountByAccountID(ctx context.Context, conn *organizations.Client, accountID string) (*types.DelegatedAdministrator, error) { - account, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, accountID, cloudTrailServicePrincipal) + account, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, accountID, ServicePrincipal) if err != nil { return nil, err } From 97c4c7d8cfec0d867504094b454e13426ff0cc8e Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 11 Aug 2024 23:41:35 +0900 Subject: [PATCH 06/13] add doc --- ...rganization_admin_account.go.html.markdown | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 website/docs/r/cloudtrail_organization_admin_account.go.html.markdown diff --git a/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown b/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown new file mode 100644 index 00000000000..664a2043b74 --- /dev/null +++ b/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown @@ -0,0 +1,64 @@ +--- +subcategory: "CloudTrail" +layout: "aws" +page_title: "AWS: aws_cloudtrail_organization_admin_account" +description: |- + Provides a resource to manage an AWS CloudTrail Delegated Administrator. +--- + +# Resource: aws_cloudtrail_organization_admin_account + +Provides a resource to manage an AWS CloudTrail Delegated Administrator. + +## Example Usage + +Basic usage: + +```terraform +resource "aws_cloudtrail_organization_admin_account" "example" { + delegated_admin_account_id = data.aws_caller_identity.delegated.account_id +} + +data "aws_caller_identity" "delegated" { + provider = aws.cloudtrail_delegate_account +} + +provider "aws" { + alias = "cloudtrail_delegate_account" + + # authentication arguments omitted +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `delegated_admin_account_id` - (Required) + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - The Organizations ARN for the delegate account. +* `id` - The Organizations member account ID that you want to enable as the CloudTrail account. +* `email` - The Organizations email for the delegate account. +* `name` - The Organizations name for the delegate account. +* `service_principal` - The AWS service principal. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import IPAMs using the delegate account `id`. For example: + +```terraform +import { + to = aws_cloudtrail_organization_admin_account.example + id = "12345678901" +} +``` + +Using `terraform import`, import IPAMs using the delegate account `id`. For example: + +```console +% terraform import aws_cloudtrail_organization_admin_account.example 12345678901 +``` From c71867587b7acfb64b443abb161e3725a17e2c18 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Sun, 11 Aug 2024 23:53:27 +0900 Subject: [PATCH 07/13] add release note --- .changelog/38817.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/38817.txt diff --git a/.changelog/38817.txt b/.changelog/38817.txt new file mode 100644 index 00000000000..6465213f991 --- /dev/null +++ b/.changelog/38817.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_cloudtrail_organization_admin_account +``` From 9cfc8708968fb0d7d5a8889d8198170f9a0e2f97 Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 12 Aug 2024 00:11:37 +0900 Subject: [PATCH 08/13] fix Service Name Scan fix Service Name Scan --- .../cloudtrail_organization_admin_account.go | 12 +++++------ ...udtrail_organization_admin_account_test.go | 20 +++++++++---------- internal/service/cloudtrail/exports_test.go | 8 ++++---- .../service/cloudtrail/service_package_gen.go | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go index 755e01a8c80..41f12e42546 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -25,11 +25,11 @@ import ( ) const ( - ResNameCloudTrailOrganizationAdminAccount = "CloudTrailOrganizationAdminAccount" + ResNameOrganizationAdminAccount = "CloudTrailOrganizationAdminAccount" ) // @FrameworkResource("aws_cloudtrail_organization_admin_account", name="CloudTrail Organization Admin Account") -func newResourceCloudTrailOrganizationAdminAccount(_ context.Context) (resource.ResourceWithConfigure, error) { +func newResourceOrganizationAdminAccount(_ context.Context) (resource.ResourceWithConfigure, error) { return &resourceCloudTrailOrganizationAdminAccount{}, nil } @@ -89,7 +89,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, _, err := cloudTrailConn.RegisterOrganizationDelegatedAdmin(ctx, input) if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameCloudTrailOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), nil), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), nil), err.Error(), ) return @@ -99,7 +99,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, readOutput, err := FindDelegatedAccountByAccountID(ctx, organizationsConn, plan.DelegatedAdminAccountID.ValueString()) if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameCloudTrailOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), err), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), err), err.Error(), ) return @@ -136,7 +136,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, r if err != nil { resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), err), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionSetting, ResNameOrganizationAdminAccount, state.ID.String(), err), err.Error(), ) return @@ -172,7 +172,7 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, return } resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameCloudTrailOrganizationAdminAccount, state.ID.String(), nil), + create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameOrganizationAdminAccount, state.ID.String(), nil), err.Error(), ) } diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go index 06eb765f846..bf9aa5769b5 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go @@ -36,12 +36,12 @@ func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx), + CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCloudTrailOrganizationAdminAccountConfig(), + Config: testAccOrganizationAdminAccountConfig(), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckCloudTrailOrganizationAdminAccountExists(ctx, resourceName, &organization), + testAccCheckOrganizationAdminAccountExists(ctx, resourceName, &organization), acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "organizations", regexache.MustCompile("account/.+")), resource.TestCheckResourceAttrPair(resourceName, names.AttrID, organizationData, "non_master_accounts.0.id"), resource.TestCheckResourceAttr(resourceName, "service_principal", cloudtrail.ServicePrincipal), @@ -68,13 +68,13 @@ func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx), + CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccCloudTrailOrganizationAdminAccountConfig(), + Config: testAccOrganizationAdminAccountConfig(), Check: resource.ComposeTestCheckFunc( - testAccCheckCloudTrailOrganizationAdminAccountExists(ctx, resourceName, &organization), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, cloudtrail.ResourceCloudTrailOrganizationAdminAccount, resourceName), + testAccCheckOrganizationAdminAccountExists(ctx, resourceName, &organization), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, cloudtrail.ResourceOrganizationAdminAccount, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -82,7 +82,7 @@ func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { }) } -func testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckOrganizationAdminAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) @@ -108,7 +108,7 @@ func testAccCheckCloudTrailOrganizationAdminAccountDestroy(ctx context.Context) } } -func testAccCheckCloudTrailOrganizationAdminAccountExists(ctx context.Context, n string, v *organizationstypes.DelegatedAdministrator) resource.TestCheckFunc { +func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string, v *organizationstypes.DelegatedAdministrator) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -129,7 +129,7 @@ func testAccCheckCloudTrailOrganizationAdminAccountExists(ctx context.Context, n } } -func testAccCloudTrailOrganizationAdminAccountConfig() string { +func testAccOrganizationAdminAccountConfig() string { return fmt.Sprint(` data "aws_organizations_organization" "test" {} diff --git a/internal/service/cloudtrail/exports_test.go b/internal/service/cloudtrail/exports_test.go index a604a27b0d4..249f293f294 100644 --- a/internal/service/cloudtrail/exports_test.go +++ b/internal/service/cloudtrail/exports_test.go @@ -8,8 +8,8 @@ var ( ResourceEventDataStore = resourceEventDataStore ResourceTrail = resourceTrail - FindEventDataStoreByARN = findEventDataStoreByARN - FindTrailByARN = findTrailByARN - ServiceAccountPerRegionMap = serviceAccountPerRegionMap - ResourceCloudTrailOrganizationAdminAccount = newResourceCloudTrailOrganizationAdminAccount + FindEventDataStoreByARN = findEventDataStoreByARN + FindTrailByARN = findTrailByARN + ServiceAccountPerRegionMap = serviceAccountPerRegionMap + ResourceOrganizationAdminAccount = newResourceOrganizationAdminAccount ) diff --git a/internal/service/cloudtrail/service_package_gen.go b/internal/service/cloudtrail/service_package_gen.go index 5b118cd6d19..cb559f76b8b 100644 --- a/internal/service/cloudtrail/service_package_gen.go +++ b/internal/service/cloudtrail/service_package_gen.go @@ -21,7 +21,7 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceCloudTrailOrganizationAdminAccount, + Factory: newResourceOrganizationAdminAccount, Name: "CloudTrail Organization Admin Account", }, } From 366c224fdcea50767fd6a9fbdd32b6965c1f23ca Mon Sep 17 00:00:00 2001 From: Yusuke Hamano <72236227+yhamano0312@users.noreply.github.com> Date: Mon, 12 Aug 2024 00:35:20 +0900 Subject: [PATCH 09/13] fix golangci-lint --- .../cloudtrail/cloudtrail_organization_admin_account_test.go | 4 ++-- internal/service/cloudtrail/find.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go index bf9aa5769b5..0971fa52541 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go @@ -130,10 +130,10 @@ func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string, v } func testAccOrganizationAdminAccountConfig() string { - return fmt.Sprint(` + return ` data "aws_organizations_organization" "test" {} resource "aws_cloudtrail_organization_admin_account" "test" { delegated_admin_account_id = data.aws_organizations_organization.test.non_master_accounts[0].id -}`) +}` } diff --git a/internal/service/cloudtrail/find.go b/internal/service/cloudtrail/find.go index c0f80f9b3f7..b90cebcd032 100644 --- a/internal/service/cloudtrail/find.go +++ b/internal/service/cloudtrail/find.go @@ -16,7 +16,6 @@ const ( ) func FindDelegatedAccountByAccountID(ctx context.Context, conn *organizations.Client, accountID string) (*types.DelegatedAdministrator, error) { - account, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, accountID, ServicePrincipal) if err != nil { return nil, err From f3d6dc580c63d4abb587db9bffe6953e04697c5e Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Aug 2024 15:06:07 -0400 Subject: [PATCH 10/13] r/aws_cloudtrail_organization_admin_account: Tidy up. --- .../cloudtrail_organization_admin_account.go | 170 ++++++++++-------- ...udtrail_organization_admin_account_test.go | 45 ++--- internal/service/cloudtrail/consts.go | 4 + internal/service/cloudtrail/exports_test.go | 13 +- internal/service/cloudtrail/find.go | 25 --- .../service/cloudtrail/service_package_gen.go | 2 +- 6 files changed, 123 insertions(+), 136 deletions(-) delete mode 100644 internal/service/cloudtrail/find.go diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go index 41f12e42546..9d73cd78549 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account.go @@ -5,7 +5,7 @@ package cloudtrail import ( "context" - "errors" + "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" @@ -16,38 +16,39 @@ import ( "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-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" fwvalidators "github.com/hashicorp/terraform-provider-aws/internal/framework/validators" + tforganizations "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -const ( - ResNameOrganizationAdminAccount = "CloudTrailOrganizationAdminAccount" -) - -// @FrameworkResource("aws_cloudtrail_organization_admin_account", name="CloudTrail Organization Admin Account") -func newResourceOrganizationAdminAccount(_ context.Context) (resource.ResourceWithConfigure, error) { - return &resourceCloudTrailOrganizationAdminAccount{}, nil +// @FrameworkResource("aws_cloudtrail_organization_admin_account", name="Organization Delegated Admin Account") +func newOrganizationAdminAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { + return &cloudTrailOrganizationAdminAccountResource{}, nil } -type resourceCloudTrailOrganizationAdminAccount struct { +type cloudTrailOrganizationAdminAccountResource struct { framework.ResourceWithConfigure framework.WithNoUpdate framework.WithImportByID } -func (r *resourceCloudTrailOrganizationAdminAccount) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { +func (*cloudTrailOrganizationAdminAccountResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { response.TypeName = "aws_cloudtrail_organization_admin_account" } -func (r *resourceCloudTrailOrganizationAdminAccount) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *cloudTrailOrganizationAdminAccountResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrARN: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "delegated_admin_account_id": schema.StringAttribute{ Required: true, @@ -60,129 +61,146 @@ func (r *resourceCloudTrailOrganizationAdminAccount) Schema(ctx context.Context, }, names.AttrEmail: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, names.AttrID: framework.IDAttribute(), names.AttrName: schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "service_principal": schema.StringAttribute{ Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, }, } } -func (r *resourceCloudTrailOrganizationAdminAccount) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - cloudTrailConn := r.Meta().CloudTrailClient(ctx) - organizationsConn := r.Meta().OrganizationsClient(ctx) - - var plan resourceCloudTrailOrganizationAdminAccountData - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { +func (r *cloudTrailOrganizationAdminAccountResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data cloudTrailOrganizationAdminAccountResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } + conn := r.Meta().CloudTrailClient(ctx) + + accountID := data.DelegatedAdminAccountID.ValueString() input := &cloudtrail.RegisterOrganizationDelegatedAdminInput{ - MemberAccountId: aws.String(plan.DelegatedAdminAccountID.ValueString()), + MemberAccountId: aws.String(accountID), } - _, err := cloudTrailConn.RegisterOrganizationDelegatedAdmin(ctx, input) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), nil), - err.Error(), - ) - return - } + _, err := conn.RegisterOrganizationDelegatedAdmin(ctx, input) - // Read after create to get computed attributes - readOutput, err := FindDelegatedAccountByAccountID(ctx, organizationsConn, plan.DelegatedAdminAccountID.ValueString()) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionCreating, ResNameOrganizationAdminAccount, plan.DelegatedAdminAccountID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("registering CloudTrail Organization Delegated Admin Account (%s)", accountID), err.Error()) + return } - state := plan - state.ServicePrincipal = flex.StringToFramework(ctx, aws.String(ServicePrincipal)) + // Set values for unknowns. + data.setID() + + delegatedAdministrator, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, r.Meta().OrganizationsClient(ctx), accountID, servicePrincipal) - resp.Diagnostics.Append(flex.Flatten(ctx, readOutput, &state)...) + if err != nil { + response.Diagnostics.AddError(fmt.Sprintf("reading CloudTrail Organization Delegated Admin Account (%s)", accountID), err.Error()) - if resp.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + data.ARN = fwflex.StringToFramework(ctx, delegatedAdministrator.Arn) + data.Email = fwflex.StringToFramework(ctx, delegatedAdministrator.Email) + data.Name = fwflex.StringToFramework(ctx, delegatedAdministrator.Name) + data.ServicePrincipal = fwflex.StringValueToFramework(ctx, servicePrincipal) + + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *resourceCloudTrailOrganizationAdminAccount) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - conn := r.Meta().OrganizationsClient(ctx) - var state resourceCloudTrailOrganizationAdminAccountData +func (r *cloudTrailOrganizationAdminAccountResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data cloudTrailOrganizationAdminAccountResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if err := data.InitFromID(); err != nil { + response.Diagnostics.AddError("parsing resource ID", err.Error()) - if resp.Diagnostics.HasError() { return } - out, err := FindDelegatedAccountByAccountID(ctx, conn, state.ID.ValueString()) + conn := r.Meta().OrganizationsClient(ctx) + + delegatedAdministrator, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, data.ID.ValueString(), servicePrincipal) 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.CloudTrail, create.ErrActionSetting, ResNameOrganizationAdminAccount, state.ID.String(), err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading CloudTrail Organization Delegated Admin Account (%s)", data.ID.ValueString()), err.Error()) + return } - state.DelegatedAdminAccountID = flex.StringValueToFramework(ctx, state.ID.ValueString()) - state.ServicePrincipal = flex.StringValueToFramework(ctx, ServicePrincipal) + // Set attributes for import. + data.ARN = fwflex.StringToFramework(ctx, delegatedAdministrator.Arn) + data.Email = fwflex.StringToFramework(ctx, delegatedAdministrator.Email) + data.Name = fwflex.StringToFramework(ctx, delegatedAdministrator.Name) + data.ServicePrincipal = fwflex.StringValueToFramework(ctx, servicePrincipal) - resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} - if resp.Diagnostics.HasError() { +func (r *cloudTrailOrganizationAdminAccountResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data cloudTrailOrganizationAdminAccountResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) -} - -func (r *resourceCloudTrailOrganizationAdminAccount) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().CloudTrailClient(ctx) - var state resourceCloudTrailOrganizationAdminAccountData - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { + _, err := conn.DeregisterOrganizationDelegatedAdmin(ctx, &cloudtrail.DeregisterOrganizationDelegatedAdminInput{ + DelegatedAdminAccountId: aws.String(data.ID.ValueString()), + }) + + if errs.IsA[*awstypes.AccountNotRegisteredException](err) { return } - _, err := conn.DeregisterOrganizationDelegatedAdmin(ctx, &cloudtrail.DeregisterOrganizationDelegatedAdminInput{ - DelegatedAdminAccountId: aws.String(state.ID.ValueString()), - }) if err != nil { - var nfe *awstypes.AccountNotRegisteredException - if errors.As(err, &nfe) { - return - } - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.CloudTrail, create.ErrActionDeleting, ResNameOrganizationAdminAccount, state.ID.String(), nil), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("deregistering CloudTrail Organization Delegated Admin Account (%s)", data.ID.ValueString()), err.Error()) + + return } } -type resourceCloudTrailOrganizationAdminAccountData struct { - Arn types.String `tfsdk:"arn"` +type cloudTrailOrganizationAdminAccountResourceModel struct { + ARN types.String `tfsdk:"arn"` DelegatedAdminAccountID types.String `tfsdk:"delegated_admin_account_id"` Email types.String `tfsdk:"email"` ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` ServicePrincipal types.String `tfsdk:"service_principal"` } + +func (model *cloudTrailOrganizationAdminAccountResourceModel) InitFromID() error { + model.DelegatedAdminAccountID = model.ID + + return nil +} + +func (model *cloudTrailOrganizationAdminAccountResourceModel) setID() { + model.ID = model.DelegatedAdminAccountID +} diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go index 0971fa52541..b1354dd5fac 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go +++ b/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go @@ -8,13 +8,11 @@ import ( "fmt" "testing" - "github.com/YakDriver/regexache" - organizationstypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" "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/service/cloudtrail" + tfcloudtrail "github.com/hashicorp/terraform-provider-aws/internal/service/cloudtrail" tforganizations "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" @@ -25,9 +23,7 @@ import ( // * Organization member account func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { ctx := acctest.Context(t) - var organization organizationstypes.DelegatedAdministrator resourceName := "aws_cloudtrail_organization_admin_account.test" - organizationData := "data.aws_organizations_organization.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -39,12 +35,13 @@ func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccOrganizationAdminAccountConfig(), + Config: testAccOrganizationAdminAccountConfig_basic, Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckOrganizationAdminAccountExists(ctx, resourceName, &organization), - acctest.MatchResourceAttrGlobalARN(resourceName, names.AttrARN, "organizations", regexache.MustCompile("account/.+")), - resource.TestCheckResourceAttrPair(resourceName, names.AttrID, organizationData, "non_master_accounts.0.id"), - resource.TestCheckResourceAttr(resourceName, "service_principal", cloudtrail.ServicePrincipal), + testAccCheckOrganizationAdminAccountExists(ctx, resourceName), + resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), + resource.TestCheckResourceAttrSet(resourceName, names.AttrEmail), + resource.TestCheckResourceAttrSet(resourceName, names.AttrName), + resource.TestCheckResourceAttr(resourceName, "service_principal", tfcloudtrail.ServicePrincipal), ), }, { @@ -58,7 +55,6 @@ func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { ctx := acctest.Context(t) - var organization organizationstypes.DelegatedAdministrator resourceName := "aws_cloudtrail_organization_admin_account.test" resource.Test(t, resource.TestCase{ @@ -71,10 +67,10 @@ func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccOrganizationAdminAccountConfig(), + Config: testAccOrganizationAdminAccountConfig_basic, Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationAdminAccountExists(ctx, resourceName, &organization), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, cloudtrail.ResourceOrganizationAdminAccount, resourceName), + testAccCheckOrganizationAdminAccountExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceOrganizationAdminAccount, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -91,7 +87,7 @@ func testAccCheckOrganizationAdminAccountDestroy(ctx context.Context) resource.T continue } - _, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.Attributes["delegated_admin_account_id"], rs.Primary.Attributes["service_principal"]) + _, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.ID, tfcloudtrail.ServicePrincipal) if tfresource.NotFound(err) { continue @@ -108,32 +104,25 @@ func testAccCheckOrganizationAdminAccountDestroy(ctx context.Context) resource.T } } -func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string, v *organizationstypes.DelegatedAdministrator) resource.TestCheckFunc { +func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("not found: %s", n) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) - output, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.Attributes["delegated_admin_account_id"], rs.Primary.Attributes["service_principal"]) + _, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, rs.Primary.ID, tfcloudtrail.ServicePrincipal) - if err != nil { - return err - } - - *v = *output - - return nil + return err } } -func testAccOrganizationAdminAccountConfig() string { - return ` +const testAccOrganizationAdminAccountConfig_basic = ` data "aws_organizations_organization" "test" {} resource "aws_cloudtrail_organization_admin_account" "test" { delegated_admin_account_id = data.aws_organizations_organization.test.non_master_accounts[0].id -}` } +` diff --git a/internal/service/cloudtrail/consts.go b/internal/service/cloudtrail/consts.go index 191b7340133..c34c0846c6e 100644 --- a/internal/service/cloudtrail/consts.go +++ b/internal/service/cloudtrail/consts.go @@ -44,3 +44,7 @@ func field_Values() []string { const ( propagationTimeout = 2 * time.Minute ) + +const ( + servicePrincipal = "cloudtrail.amazonaws.com" +) diff --git a/internal/service/cloudtrail/exports_test.go b/internal/service/cloudtrail/exports_test.go index 249f293f294..6e6ce5a86e8 100644 --- a/internal/service/cloudtrail/exports_test.go +++ b/internal/service/cloudtrail/exports_test.go @@ -5,11 +5,12 @@ package cloudtrail // Exports for use in tests only. var ( - ResourceEventDataStore = resourceEventDataStore - ResourceTrail = resourceTrail + ResourceOrganizationAdminAccount = newOrganizationAdminAccountResource + ResourceEventDataStore = resourceEventDataStore + ResourceTrail = resourceTrail - FindEventDataStoreByARN = findEventDataStoreByARN - FindTrailByARN = findTrailByARN - ServiceAccountPerRegionMap = serviceAccountPerRegionMap - ResourceOrganizationAdminAccount = newResourceOrganizationAdminAccount + FindEventDataStoreByARN = findEventDataStoreByARN + FindTrailByARN = findTrailByARN + ServiceAccountPerRegionMap = serviceAccountPerRegionMap + ServicePrincipal = servicePrincipal ) diff --git a/internal/service/cloudtrail/find.go b/internal/service/cloudtrail/find.go deleted file mode 100644 index b90cebcd032..00000000000 --- a/internal/service/cloudtrail/find.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package cloudtrail - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/service/organizations" - "github.com/aws/aws-sdk-go-v2/service/organizations/types" - tforganizations "github.com/hashicorp/terraform-provider-aws/internal/service/organizations" -) - -const ( - ServicePrincipal = "cloudtrail.amazonaws.com" -) - -func FindDelegatedAccountByAccountID(ctx context.Context, conn *organizations.Client, accountID string) (*types.DelegatedAdministrator, error) { - account, err := tforganizations.FindDelegatedAdministratorByTwoPartKey(ctx, conn, accountID, ServicePrincipal) - if err != nil { - return nil, err - } - - return account, nil -} diff --git a/internal/service/cloudtrail/service_package_gen.go b/internal/service/cloudtrail/service_package_gen.go index cb559f76b8b..09cf977246e 100644 --- a/internal/service/cloudtrail/service_package_gen.go +++ b/internal/service/cloudtrail/service_package_gen.go @@ -21,7 +21,7 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newResourceOrganizationAdminAccount, + Factory: newOrganizationAdminAccountResource, Name: "CloudTrail Organization Admin Account", }, } From d734c91bd67052b9ae9ec42128ddebe1ab2ec140 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Aug 2024 15:20:02 -0400 Subject: [PATCH 11/13] 'aws_cloudtrail_organization_admin_account' -> 'aws_cloudtrail_organization_delegated_admin_account'. --- .changelog/38817.txt | 2 +- ...l_organization_delegated_admin_account.go} | 64 +++++++++---------- ...anization_delegated_admin_account_test.go} | 34 +++++----- internal/service/cloudtrail/exports_test.go | 6 +- .../service/cloudtrail/service_package_gen.go | 4 +- ...rganization_admin_account.go.html.markdown | 64 ------------------- ...tion_delegated_admin_account.html.markdown | 63 ++++++++++++++++++ 7 files changed, 118 insertions(+), 119 deletions(-) rename internal/service/cloudtrail/{cloudtrail_organization_admin_account.go => cloudtrail_organization_delegated_admin_account.go} (71%) rename internal/service/cloudtrail/{cloudtrail_organization_admin_account_test.go => cloudtrail_organization_delegated_admin_account_test.go} (69%) delete mode 100644 website/docs/r/cloudtrail_organization_admin_account.go.html.markdown create mode 100644 website/docs/r/cloudtrail_organization_delegated_admin_account.html.markdown diff --git a/.changelog/38817.txt b/.changelog/38817.txt index 6465213f991..8b365b8ee21 100644 --- a/.changelog/38817.txt +++ b/.changelog/38817.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -aws_cloudtrail_organization_admin_account +aws_cloudtrail_organization_delegated_admin_account ``` diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go b/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account.go similarity index 71% rename from internal/service/cloudtrail/cloudtrail_organization_admin_account.go rename to internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account.go index 9d73cd78549..d0306a75ba0 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account.go +++ b/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account.go @@ -26,31 +26,25 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @FrameworkResource("aws_cloudtrail_organization_admin_account", name="Organization Delegated Admin Account") -func newOrganizationAdminAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { - return &cloudTrailOrganizationAdminAccountResource{}, nil +// @FrameworkResource("aws_cloudtrail_organization_delegated_admin_account", name="Organization Delegated Admin Account") +func newOrganizationDelegatedAdminAccountResource(_ context.Context) (resource.ResourceWithConfigure, error) { + return &organizationDelegatedAdminAccountResource{}, nil } -type cloudTrailOrganizationAdminAccountResource struct { +type organizationDelegatedAdminAccountResource struct { framework.ResourceWithConfigure framework.WithNoUpdate framework.WithImportByID } -func (*cloudTrailOrganizationAdminAccountResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { - response.TypeName = "aws_cloudtrail_organization_admin_account" +func (*organizationDelegatedAdminAccountResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_cloudtrail_organization_delegated_admin_account" } -func (r *cloudTrailOrganizationAdminAccountResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { +func (r *organizationDelegatedAdminAccountResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - names.AttrARN: schema.StringAttribute{ - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "delegated_admin_account_id": schema.StringAttribute{ + names.AttrAccountID: schema.StringAttribute{ Required: true, Validators: []validator.String{ fwvalidators.AWSAccountID(), @@ -59,6 +53,12 @@ func (r *cloudTrailOrganizationAdminAccountResource) Schema(ctx context.Context, stringplanmodifier.RequiresReplace(), }, }, + names.AttrARN: schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, names.AttrEmail: schema.StringAttribute{ Computed: true, PlanModifiers: []planmodifier.String{ @@ -82,8 +82,8 @@ func (r *cloudTrailOrganizationAdminAccountResource) Schema(ctx context.Context, } } -func (r *cloudTrailOrganizationAdminAccountResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { - var data cloudTrailOrganizationAdminAccountResourceModel +func (r *organizationDelegatedAdminAccountResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data organizationDelegatedAdminAccountResourceModel response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) if response.Diagnostics.HasError() { return @@ -91,7 +91,7 @@ func (r *cloudTrailOrganizationAdminAccountResource) Create(ctx context.Context, conn := r.Meta().CloudTrailClient(ctx) - accountID := data.DelegatedAdminAccountID.ValueString() + accountID := data.AccountID.ValueString() input := &cloudtrail.RegisterOrganizationDelegatedAdminInput{ MemberAccountId: aws.String(accountID), } @@ -123,8 +123,8 @@ func (r *cloudTrailOrganizationAdminAccountResource) Create(ctx context.Context, response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *cloudTrailOrganizationAdminAccountResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { - var data cloudTrailOrganizationAdminAccountResourceModel +func (r *organizationDelegatedAdminAccountResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data organizationDelegatedAdminAccountResourceModel response.Diagnostics.Append(request.State.Get(ctx, &data)...) if response.Diagnostics.HasError() { return @@ -162,8 +162,8 @@ func (r *cloudTrailOrganizationAdminAccountResource) Read(ctx context.Context, r response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *cloudTrailOrganizationAdminAccountResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { - var data cloudTrailOrganizationAdminAccountResourceModel +func (r *organizationDelegatedAdminAccountResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data organizationDelegatedAdminAccountResourceModel response.Diagnostics.Append(request.State.Get(ctx, &data)...) if response.Diagnostics.HasError() { return @@ -186,21 +186,21 @@ func (r *cloudTrailOrganizationAdminAccountResource) Delete(ctx context.Context, } } -type cloudTrailOrganizationAdminAccountResourceModel struct { - ARN types.String `tfsdk:"arn"` - DelegatedAdminAccountID types.String `tfsdk:"delegated_admin_account_id"` - Email types.String `tfsdk:"email"` - ID types.String `tfsdk:"id"` - Name types.String `tfsdk:"name"` - ServicePrincipal types.String `tfsdk:"service_principal"` +type organizationDelegatedAdminAccountResourceModel struct { + AccountID types.String `tfsdk:"account_id"` + ARN types.String `tfsdk:"arn"` + Email types.String `tfsdk:"email"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + ServicePrincipal types.String `tfsdk:"service_principal"` } -func (model *cloudTrailOrganizationAdminAccountResourceModel) InitFromID() error { - model.DelegatedAdminAccountID = model.ID +func (model *organizationDelegatedAdminAccountResourceModel) InitFromID() error { + model.AccountID = model.ID return nil } -func (model *cloudTrailOrganizationAdminAccountResourceModel) setID() { - model.ID = model.DelegatedAdminAccountID +func (model *organizationDelegatedAdminAccountResourceModel) setID() { + model.ID = model.AccountID } diff --git a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go b/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account_test.go similarity index 69% rename from internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go rename to internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account_test.go index b1354dd5fac..d92fa3a3896 100644 --- a/internal/service/cloudtrail/cloudtrail_organization_admin_account_test.go +++ b/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account_test.go @@ -21,9 +21,9 @@ import ( // Prerequisites: // * Organizations management account // * Organization member account -func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { +func TestAccCloudTrailOrganizationDelegatedAdminAccount_basic(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_cloudtrail_organization_admin_account.test" + resourceName := "aws_cloudtrail_organization_delegated_admin_account.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -32,12 +32,12 @@ func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), + CheckDestroy: testAccCheckOrganizationDelegatedAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccOrganizationAdminAccountConfig_basic, + Config: testAccOrganizationDelegatedAdminAccountConfig_basic, Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckOrganizationAdminAccountExists(ctx, resourceName), + testAccCheckOrganizationDelegatedAdminAccountExists(ctx, resourceName), resource.TestCheckResourceAttrSet(resourceName, names.AttrARN), resource.TestCheckResourceAttrSet(resourceName, names.AttrEmail), resource.TestCheckResourceAttrSet(resourceName, names.AttrName), @@ -53,9 +53,9 @@ func TestAccCloudTrailOrganizationAdminAccount_basic(t *testing.T) { }) } -func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { +func TestAccCloudTrailOrganizationDelegatedAdminAccount_disappears(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_cloudtrail_organization_admin_account.test" + resourceName := "aws_cloudtrail_organization_delegated_admin_account.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -64,13 +64,13 @@ func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.OrganizationsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationAdminAccountDestroy(ctx), + CheckDestroy: testAccCheckOrganizationDelegatedAdminAccountDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccOrganizationAdminAccountConfig_basic, + Config: testAccOrganizationDelegatedAdminAccountConfig_basic, Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationAdminAccountExists(ctx, resourceName), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceOrganizationAdminAccount, resourceName), + testAccCheckOrganizationDelegatedAdminAccountExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceOrganizationDelegatedAdminAccount, resourceName), ), ExpectNonEmptyPlan: true, }, @@ -78,12 +78,12 @@ func TestAccCloudTrailOrganizationAdminAccount_disappears(t *testing.T) { }) } -func testAccCheckOrganizationAdminAccountDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckOrganizationDelegatedAdminAccountDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_cloudtrail_organization_admin_account" { + if rs.Type != "aws_cloudtrail_organization_delegated_admin_account" { continue } @@ -104,7 +104,7 @@ func testAccCheckOrganizationAdminAccountDestroy(ctx context.Context) resource.T } } -func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string) resource.TestCheckFunc { +func testAccCheckOrganizationDelegatedAdminAccountExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -119,10 +119,10 @@ func testAccCheckOrganizationAdminAccountExists(ctx context.Context, n string) r } } -const testAccOrganizationAdminAccountConfig_basic = ` +const testAccOrganizationDelegatedAdminAccountConfig_basic = ` data "aws_organizations_organization" "test" {} -resource "aws_cloudtrail_organization_admin_account" "test" { - delegated_admin_account_id = data.aws_organizations_organization.test.non_master_accounts[0].id +resource "aws_cloudtrail_organization_delegated_admin_account" "test" { + account_id = data.aws_organizations_organization.test.non_master_accounts[0].id } ` diff --git a/internal/service/cloudtrail/exports_test.go b/internal/service/cloudtrail/exports_test.go index 6e6ce5a86e8..9bdcb9c1ec5 100644 --- a/internal/service/cloudtrail/exports_test.go +++ b/internal/service/cloudtrail/exports_test.go @@ -5,9 +5,9 @@ package cloudtrail // Exports for use in tests only. var ( - ResourceOrganizationAdminAccount = newOrganizationAdminAccountResource - ResourceEventDataStore = resourceEventDataStore - ResourceTrail = resourceTrail + ResourceEventDataStore = resourceEventDataStore + ResourceOrganizationDelegatedAdminAccount = newOrganizationDelegatedAdminAccountResource + ResourceTrail = resourceTrail FindEventDataStoreByARN = findEventDataStoreByARN FindTrailByARN = findTrailByARN diff --git a/internal/service/cloudtrail/service_package_gen.go b/internal/service/cloudtrail/service_package_gen.go index 09cf977246e..cf15b49f77a 100644 --- a/internal/service/cloudtrail/service_package_gen.go +++ b/internal/service/cloudtrail/service_package_gen.go @@ -21,8 +21,8 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ { - Factory: newOrganizationAdminAccountResource, - Name: "CloudTrail Organization Admin Account", + Factory: newOrganizationDelegatedAdminAccountResource, + Name: "Organization Delegated Admin Account", }, } } diff --git a/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown b/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown deleted file mode 100644 index 664a2043b74..00000000000 --- a/website/docs/r/cloudtrail_organization_admin_account.go.html.markdown +++ /dev/null @@ -1,64 +0,0 @@ ---- -subcategory: "CloudTrail" -layout: "aws" -page_title: "AWS: aws_cloudtrail_organization_admin_account" -description: |- - Provides a resource to manage an AWS CloudTrail Delegated Administrator. ---- - -# Resource: aws_cloudtrail_organization_admin_account - -Provides a resource to manage an AWS CloudTrail Delegated Administrator. - -## Example Usage - -Basic usage: - -```terraform -resource "aws_cloudtrail_organization_admin_account" "example" { - delegated_admin_account_id = data.aws_caller_identity.delegated.account_id -} - -data "aws_caller_identity" "delegated" { - provider = aws.cloudtrail_delegate_account -} - -provider "aws" { - alias = "cloudtrail_delegate_account" - - # authentication arguments omitted -} -``` - -## Argument Reference - -This resource supports the following arguments: - -* `delegated_admin_account_id` - (Required) - -## Attribute Reference - -This resource exports the following attributes in addition to the arguments above: - -* `arn` - The Organizations ARN for the delegate account. -* `id` - The Organizations member account ID that you want to enable as the CloudTrail account. -* `email` - The Organizations email for the delegate account. -* `name` - The Organizations name for the delegate account. -* `service_principal` - The AWS service principal. - -## Import - -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import IPAMs using the delegate account `id`. For example: - -```terraform -import { - to = aws_cloudtrail_organization_admin_account.example - id = "12345678901" -} -``` - -Using `terraform import`, import IPAMs using the delegate account `id`. For example: - -```console -% terraform import aws_cloudtrail_organization_admin_account.example 12345678901 -``` diff --git a/website/docs/r/cloudtrail_organization_delegated_admin_account.html.markdown b/website/docs/r/cloudtrail_organization_delegated_admin_account.html.markdown new file mode 100644 index 00000000000..d4168786808 --- /dev/null +++ b/website/docs/r/cloudtrail_organization_delegated_admin_account.html.markdown @@ -0,0 +1,63 @@ +--- +subcategory: "CloudTrail" +layout: "aws" +page_title: "AWS: aws_cloudtrail_organization_delegated_admin_account" +description: |- + Provides a resource to manage an AWS CloudTrail Delegated Administrator. +--- + +# Resource: aws_cloudtrail_organization_delegated_admin_account + +Provides a resource to manage an AWS CloudTrail Delegated Administrator. + +## Example Usage + +Basic usage: + +```terraform +resource "aws_cloudtrail_organization_delegated_admin_account" "example" { + account_id = data.aws_caller_identity.delegated.account_id +} + +data "aws_caller_identity" "delegated" { + provider = aws.cloudtrail_delegate_account +} + +provider "aws" { + alias = "cloudtrail_delegate_account" + + # authentication arguments omitted +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `account_id` - (Required) An organization member account ID that you want to designate as a delegated administrator. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - The Amazon Resource Name (ARN) of the delegated administrator's account. +* `email` - The email address that is associated with the delegated administrator's AWS account. +* `name` - The friendly name of the delegated administrator's account. +* `service_principal` - The AWS CloudTrail service principal name. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import delegated administrators using the delegate account `id`. For example: + +```terraform +import { + to = aws_cloudtrail_organization_delegated_admin_account.example + id = "12345678901" +} +``` + +Using `terraform import`, import delegated administrators using the delegate account `id`. For example: + +```console +% terraform import aws_cloudtrail_organization_delegated_admin_account.example 12345678901 +``` From 2525b61fc32533e7727d060665e0397d4e18ce48 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Aug 2024 15:22:37 -0400 Subject: [PATCH 12/13] cloudtrail: Correct source file names. --- ...d_admin_account.go => organization_delegated_admin_account.go} | 0 ...count_test.go => organization_delegated_admin_account_test.go} | 0 internal/service/cloudtrail/{cloudtrail.go => trail.go} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename internal/service/cloudtrail/{cloudtrail_organization_delegated_admin_account.go => organization_delegated_admin_account.go} (100%) rename internal/service/cloudtrail/{cloudtrail_organization_delegated_admin_account_test.go => organization_delegated_admin_account_test.go} (100%) rename internal/service/cloudtrail/{cloudtrail.go => trail.go} (100%) diff --git a/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account.go b/internal/service/cloudtrail/organization_delegated_admin_account.go similarity index 100% rename from internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account.go rename to internal/service/cloudtrail/organization_delegated_admin_account.go diff --git a/internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account_test.go b/internal/service/cloudtrail/organization_delegated_admin_account_test.go similarity index 100% rename from internal/service/cloudtrail/cloudtrail_organization_delegated_admin_account_test.go rename to internal/service/cloudtrail/organization_delegated_admin_account_test.go diff --git a/internal/service/cloudtrail/cloudtrail.go b/internal/service/cloudtrail/trail.go similarity index 100% rename from internal/service/cloudtrail/cloudtrail.go rename to internal/service/cloudtrail/trail.go From eb2b4a91560568eecdfefa1f70641d02272fb98d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 13 Aug 2024 15:26:42 -0400 Subject: [PATCH 13/13] r/aws_cloudtrail_organization_delegated_admin_account: Serialize acceptance tests. --- .../service/cloudtrail/cloudtrail_test.go | 1565 +--------------- ...ganization_delegated_admin_account_test.go | 4 +- internal/service/cloudtrail/trail_test.go | 1573 +++++++++++++++++ 3 files changed, 1579 insertions(+), 1563 deletions(-) create mode 100644 internal/service/cloudtrail/trail_test.go diff --git a/internal/service/cloudtrail/cloudtrail_test.go b/internal/service/cloudtrail/cloudtrail_test.go index 2bdab88128b..ef35184a478 100644 --- a/internal/service/cloudtrail/cloudtrail_test.go +++ b/internal/service/cloudtrail/cloudtrail_test.go @@ -4,21 +4,10 @@ package cloudtrail_test import ( - "context" - "fmt" "testing" - "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/cloudtrail" - "github.com/aws/aws-sdk-go-v2/service/cloudtrail/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" - tfcloudtrail "github.com/hashicorp/terraform-provider-aws/internal/service/cloudtrail" - "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -37,6 +26,10 @@ func TestAccCloudTrail_serial(t *testing.T) { t.Parallel() testCases := map[string]map[string]func(t *testing.T){ + "OrganizationDelegatedAdminAccount": { + acctest.CtBasic: testAccOrganizationDelegatedAdminAccount_basic, + acctest.CtDisappears: testAccOrganizationDelegatedAdminAccount_disappears, + }, "Trail": { acctest.CtBasic: testAccTrail_basic, "cloudwatch": testAccTrail_cloudWatch, @@ -59,1553 +52,3 @@ func TestAccCloudTrail_serial(t *testing.T) { acctest.RunSerialTests2Levels(t, testCases, 0) } - -func testAccTrail_basic(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtFalse), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_modified(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, names.AttrPrefix), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtFalse), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccTrail_cloudWatch(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_cloudWatch(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_group_arn"), - resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_role_arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_cloudWatchModified(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_group_arn"), - resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_role_arn"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccTrail_enableLogging(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_enableLogging(rName, true), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - // AWS will create the trail with logging turned off. - // Test that "enable_logging" default works. - testAccCheckLoggingEnabled(ctx, resourceName, true), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_enableLogging(rName, false), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - testAccCheckLoggingEnabled(ctx, resourceName, false), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - testAccCheckLoggingEnabled(ctx, resourceName, true), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - }, - }) -} - -func testAccTrail_multiRegion(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtFalse), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - Config: testAccCloudTrailConfig_multiRegion(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtTrue), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtFalse), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - }, - }) -} - -func testAccTrail_organization(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_organization(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtTrue), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtFalse), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - }, - }) -} - -func testAccTrail_logValidation(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_logValidation(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), - testAccCheckLogValidationEnabled(resourceName, true, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_logValidationModified(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), - ), - }, - }, - }) -} - -func testAccTrail_kmsKey(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resourceName := "aws_cloudtrail.test" - kmsResourceName := "aws_kms_key.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_kmsKey(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), - testAccCheckLogValidationEnabled(resourceName, false, &trail), - resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, kmsResourceName, names.AttrARN), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccTrail_tags(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), - ), - }, - { - Config: testAccCloudTrailConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), - ), - }, - }, - }) -} - -func testAccTrail_globalServiceEvents(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_globalServiceEvents(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtFalse), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccTrail_eventSelector(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_eventSelector(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::S3::Object"), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct2), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/isen", rName)), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/ko", rName)), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtFalse), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "ReadOnly"), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct0), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_eventSelectorReadWriteType(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "WriteOnly"), - ), - }, - { - Config: testAccCloudTrailConfig_eventSelectorModified(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::S3::Object"), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct2), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/isen", rName)), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/ko", rName)), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "ReadOnly"), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.#", acctest.Ct2), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.0.type", "AWS::S3::Object"), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.0.values.#", acctest.Ct2), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.1.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/tf1", rName)), - acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.1.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/tf2", rName)), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.1.type", "AWS::Lambda::Function"), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.1.values.#", acctest.Ct1), - acctest.CheckResourceAttrRegionalARN(resourceName, "event_selector.1.data_resource.1.values.0", "lambda", fmt.Sprintf("function:%s", rName)), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct0), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.include_management_events", acctest.CtFalse), - resource.TestCheckResourceAttr(resourceName, "event_selector.1.read_write_type", "All"), - ), - }, - { - Config: testAccCloudTrailConfig_eventSelectorNone(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct0), - ), - }, - }, - }) -} - -func testAccTrail_eventSelectorDynamoDB(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_eventSelectorDynamoDB(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::DynamoDB::Table"), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct1), - acctest.MatchResourceAttrRegionalARN(resourceName, "event_selector.0.data_resource.0.values.0", "dynamodb", regexache.MustCompile(`table/tf-acc-test-.+`)), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "All"), - ), - }, - }, - }) -} - -func testAccTrail_eventSelectorExclude(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_eventSelectorExcludeKMS(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct1), - resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "kms.amazonaws.com"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_eventSelectorExcludeKMSAndRDSData(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), - resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct2), - resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "kms.amazonaws.com"), - resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "rdsdata.amazonaws.com"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_eventSelectorNone(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct0), - ), - }, - }, - }) -} - -func testAccTrail_insightSelector(t *testing.T) { - ctx := acctest.Context(t) - resourceName := "aws_cloudtrail.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_insightSelector(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "insight_selector.0.insight_type", "ApiCallRateInsight"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - { - Config: testAccCloudTrailConfig_insightSelectorMulti(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct2), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "insight_selector.*", map[string]string{ - "insight_type": "ApiCallRateInsight", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "insight_selector.*", map[string]string{ - "insight_type": "ApiErrorRateInsight", - }), - ), - }, - { - Config: testAccCloudTrailConfig_insightSelector(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct1), - resource.TestCheckResourceAttr(resourceName, "insight_selector.0.insight_type", "ApiCallRateInsight"), - ), - }, - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct0), - ), - }, - }, - }) -} - -func testAccTrail_advancedEventSelector(t *testing.T) { - ctx := acctest.Context(t) - resourceName := "aws_cloudtrail.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_advancedEventSelector(rName), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.#", "5"), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.0.name", "s3Custom"), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.0.field_selector.#", "5"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ - names.AttrField: "eventCategory", - "equals.#": acctest.Ct1, - "equals.0": "Data", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ - names.AttrField: "eventName", - "equals.#": acctest.Ct1, - "equals.0": "DeleteObject", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ - names.AttrField: "resources.ARN", - "equals.#": acctest.Ct2, - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ - names.AttrField: "readOnly", - "equals.#": acctest.Ct1, - "equals.0": acctest.CtFalse, - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ - names.AttrField: "resources.type", - "equals.#": acctest.Ct1, - "equals.0": "AWS::S3::Object", - }), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.1.name", "lambdaLogAllEvents"), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.1.field_selector.#", acctest.Ct2), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.1.field_selector.*", map[string]string{ - names.AttrField: "eventCategory", - "equals.#": acctest.Ct1, - "equals.0": "Data", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.1.field_selector.*", map[string]string{ - names.AttrField: "resources.type", - "equals.#": acctest.Ct1, - "equals.0": "AWS::Lambda::Function", - }), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.2.name", "dynamoDbReadOnlyEvents"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.2.field_selector.*", map[string]string{ - names.AttrField: "readOnly", - "equals.#": acctest.Ct1, - "equals.0": acctest.CtTrue, - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.2.field_selector.*", map[string]string{ - names.AttrField: "resources.type", - "equals.#": acctest.Ct1, - "equals.0": "AWS::DynamoDB::Table", - }), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.3.name", "s3OutpostsWriteOnlyEvents"), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.3.field_selector.#", acctest.Ct3), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ - names.AttrField: "eventCategory", - "equals.#": acctest.Ct1, - "equals.0": "Data", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ - names.AttrField: "readOnly", - "equals.#": acctest.Ct1, - "equals.0": acctest.CtFalse, - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ - names.AttrField: "resources.type", - "equals.#": acctest.Ct1, - "equals.0": "AWS::S3Outposts::Object", - }), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.4.name", "managementEventsSelector"), - resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.4.field_selector.#", acctest.Ct1), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.4.field_selector.*", map[string]string{ - names.AttrField: "eventCategory", - "equals.#": acctest.Ct1, - "equals.0": "Management", - }), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccTrail_disappears(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceTrail(), resourceName), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceTrail(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func testAccTrail_migrateV0(t *testing.T) { - ctx := acctest.Context(t) - var trail types.Trail - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_cloudtrail.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), - CheckDestroy: testAccCheckTrailDestroy(ctx), - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "aws": { - Source: "hashicorp/aws", - VersionConstraint: "5.24.0", - }, - }, - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttrPair(resourceName, names.AttrID, resourceName, names.AttrName), - ), - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Config: testAccCloudTrailConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckTrailExists(ctx, resourceName, &trail), - acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), - resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), - resource.TestCheckResourceAttrPair(resourceName, names.AttrID, resourceName, names.AttrARN), - ), - }, - }, - }) -} - -func testAccCheckTrailExists(ctx context.Context, n string, v *types.Trail) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) - - output, err := tfcloudtrail.FindTrailByARN(ctx, conn, rs.Primary.ID) - - if err != nil { - return err - } - - *v = *output - - return nil - } -} - -func testAccCheckLoggingEnabled(ctx context.Context, n string, want bool) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) - - output, err := conn.GetTrailStatus(ctx, &cloudtrail.GetTrailStatusInput{ - Name: aws.String(rs.Primary.ID), - }) - - if err != nil { - return err - } - - if got := aws.ToBool(output.IsLogging); got != want { - return fmt.Errorf("Expected logging status %t, given %t", want, got) - } - - return nil - } -} - -func testAccCheckLogValidationEnabled(n string, desired bool, trail *types.Trail) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if trail.LogFileValidationEnabled == nil { - return fmt.Errorf("No LogFileValidationEnabled attribute present") - } - - logValid := aws.ToBool(trail.LogFileValidationEnabled) - if logValid != desired { - return fmt.Errorf("Expected log validation status %t, given %t", desired, logValid) - } - - // local state comparison - enabled, ok := rs.Primary.Attributes["enable_log_file_validation"] - if !ok { - return fmt.Errorf("No enable_log_file_validation attribute defined for %s, expected %t", - n, desired) - } - desiredInString := fmt.Sprintf("%t", desired) - if enabled != desiredInString { - return fmt.Errorf("Expected log validation status %s, saved %s", desiredInString, enabled) - } - - return nil - } -} - -func testAccCheckTrailDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_cloudtrail" { - continue - } - - _, err := tfcloudtrail.FindTrailByARN(ctx, conn, rs.Primary.ID) - - if tfresource.NotFound(err) { - continue - } - - if err != nil { - return err - } - - return fmt.Errorf("CloudTrail Trail (%s) still exists", rs.Primary.ID) - } - - return nil - } -} - -func testAccCloudTrailConfig_base(rName string) string { - return fmt.Sprintf(` -data "aws_caller_identity" "current" {} - -data "aws_partition" "current" {} - -data "aws_region" "current" {} - -resource "aws_s3_bucket" "test" { - bucket = %[1]q - force_destroy = true -} - -resource "aws_s3_bucket_policy" "test" { - bucket = aws_s3_bucket.test.id - policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Sid = "AWSCloudTrailAclCheck" - Effect = "Allow" - Principal = { - Service = "cloudtrail.amazonaws.com" - } - Action = "s3:GetBucketAcl" - Resource = "arn:${data.aws_partition.current.partition}:s3:::%[1]s" - Condition = { - StringEquals = { - "aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/%[1]s" - } - } - }, - { - Sid = "AWSCloudTrailWrite" - Effect = "Allow" - Principal = { - Service = "cloudtrail.amazonaws.com" - } - Action = "s3:PutObject" - Resource = "arn:${data.aws_partition.current.partition}:s3:::%[1]s/*" - Condition = { - StringEquals = { - "s3:x-amz-acl" = "bucket-owner-full-control" - "aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/%[1]s" - } - } - } - ] - }) -} -`, rName) -} - -func testAccCloudTrailConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id -} -`, rName)) -} - -func testAccCloudTrailConfig_modified(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - s3_key_prefix = "prefix" - include_global_service_events = false -} -`, rName)) -} - -func testAccCloudTrailConfig_enableLogging(rName string, enableLogging bool) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - s3_key_prefix = "prefix" - include_global_service_events = false - enable_logging = %[2]t -} -`, rName, enableLogging)) -} - -func testAccCloudTrailConfig_cloudWatch(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.test.arn}:*" - cloud_watch_logs_role_arn = aws_iam_role.test.arn -} - -resource "aws_cloudwatch_log_group" "test" { - name = %[1]q -} - -resource "aws_iam_role" "test" { - name = %[1]q - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - - Statement = [{ - Sid = "" - Effect = "Allow" - Action = "sts:AssumeRole" - - Principal = { - Service = "cloudtrail.${data.aws_partition.current.dns_suffix}" - } - }] - }) -} - -resource "aws_iam_role_policy" "test" { - name = %[1]q - role = aws_iam_role.test.id - - policy = jsonencode({ - Version = "2012-10-17" - - Statement = [{ - Sid = "AWSCloudTrailCreateLogStream" - Effect = "Allow" - Resource = "${aws_cloudwatch_log_group.test.arn}:*" - - Action = [ - "logs:CreateLogStream", - "logs:PutLogEvents", - ] - }] - }) -} -`, rName)) -} - -func testAccCloudTrailConfig_cloudWatchModified(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.test2.arn}:*" - cloud_watch_logs_role_arn = aws_iam_role.test.arn -} - -resource "aws_cloudwatch_log_group" "test" { - name = %[1]q -} - -resource "aws_cloudwatch_log_group" "test2" { - name = "%[1]s-2" -} - -resource "aws_iam_role" "test" { - name = %[1]q - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - - Statement = [{ - Sid = "" - Effect = "Allow" - Action = "sts:AssumeRole" - - Principal = { - Service = "cloudtrail.${data.aws_partition.current.dns_suffix}" - } - }] - }) -} - -resource "aws_iam_role_policy" "test" { - name = %[1]q - role = aws_iam_role.test.id - - policy = jsonencode({ - Version = "2012-10-17" - - Statement = [{ - Sid = "AWSCloudTrailCreateLogStream" - Effect = "Allow" - Resource = "${aws_cloudwatch_log_group.test2.arn}:*" - - Action = [ - "logs:CreateLogStream", - "logs:PutLogEvents", - ] - }] - }) -} -`, rName)) -} - -func testAccCloudTrailConfig_multiRegion(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - is_multi_region_trail = true -} -`, rName)) -} - -func testAccCloudTrailConfig_organization(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - is_organization_trail = true - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id -} -`, rName)) -} - -func testAccCloudTrailConfig_logValidation(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - is_multi_region_trail = true - include_global_service_events = true - enable_log_file_validation = true -} -`, rName)) -} - -func testAccCloudTrailConfig_logValidationModified(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - include_global_service_events = true -} -`, rName)) -} - -func testAccCloudTrailConfig_kmsKey(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_kms_key" "test" { - description = %[1]q - - policy = jsonencode({ - Version = "2012-10-17" - Id = %[1]q - - Statement = [{ - Sid = "Enable IAM User Permissions" - Effect = "Allow" - Action = "kms:*" - Resource = "*" - - Principal = { - AWS = "*" - } - }] - }) -} - -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - include_global_service_events = true - kms_key_id = aws_kms_key.test.arn -} -`, rName)) -} - -func testAccCloudTrailConfig_globalServiceEvents(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - include_global_service_events = false -} -`, rName)) -} - -func testAccCloudTrailConfig_tags1(rName, tagKey1, tagValue1 string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - tags = { - %[2]q = %[3]q - } -} -`, rName, tagKey1, tagValue1)) -} - -func testAccCloudTrailConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - tags = { - %[2]q = %[3]q - %[4]q = %[5]q - } -} -`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) -} - -func testAccCloudTrailConfig_eventSelector(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - read_write_type = "ReadOnly" - include_management_events = false - - data_resource { - type = "AWS::S3::Object" - - values = [ - "${aws_s3_bucket.test2.arn}/isen", - "${aws_s3_bucket.test2.arn}/ko", - ] - } - } -} - -resource "aws_s3_bucket" "test2" { - bucket = "%[1]s-2" - force_destroy = true -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorReadWriteType(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - read_write_type = "WriteOnly" - include_management_events = true - } -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorModified(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - read_write_type = "ReadOnly" - include_management_events = true - - data_resource { - type = "AWS::S3::Object" - - values = [ - "${aws_s3_bucket.test2.arn}/isen", - "${aws_s3_bucket.test2.arn}/ko", - ] - } - } - - event_selector { - read_write_type = "All" - include_management_events = false - - data_resource { - type = "AWS::S3::Object" - - values = [ - "${aws_s3_bucket.test2.arn}/tf1", - "${aws_s3_bucket.test2.arn}/tf2", - ] - } - - data_resource { - type = "AWS::Lambda::Function" - - values = [ - aws_lambda_function.test.arn, - ] - } - } -} - -resource "aws_s3_bucket" "test2" { - bucket = "%[1]s-2" - force_destroy = true -} - -resource "aws_iam_role" "test" { - name = %[1]q - - assume_role_policy = jsonencode({ - Version = "2012-10-17" - - Statement = [{ - Sid = "" - Effect = "Allow" - Action = "sts:AssumeRole" - - Principal = { - Service = "lambda.${data.aws_partition.current.dns_suffix}" - } - }] - }) -} - -resource "aws_lambda_function" "test" { - filename = "test-fixtures/lambdatest.zip" - function_name = %[1]q - role = aws_iam_role.test.arn - handler = "exports.example" - runtime = "nodejs16.x" -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorNone(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorDynamoDB(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - read_write_type = "All" - include_management_events = true - - data_resource { - type = "AWS::DynamoDB::Table" - - values = [ - aws_dynamodb_table.test.arn, - ] - } - } -} - -resource "aws_dynamodb_table" "test" { - name = %[1]q - read_capacity = 1 - write_capacity = 1 - hash_key = %[1]q - - attribute { - name = %[1]q - type = "S" - } -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorExcludeKMS(rName string) string { - return acctest.ConfigCompose( - testAccCloudTrailConfig_base(rName), - fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - exclude_management_event_sources = ["kms.${data.aws_partition.current.dns_suffix}"] - } -} -`, rName)) -} - -func testAccCloudTrailConfig_eventSelectorExcludeKMSAndRDSData(rName string) string { - return acctest.ConfigCompose( - testAccCloudTrailConfig_base(rName), - fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - event_selector { - exclude_management_event_sources = [ - "kms.${data.aws_partition.current.dns_suffix}", - "rdsdata.${data.aws_partition.current.dns_suffix}" - ] - } -} -`, rName)) -} - -func testAccCloudTrailConfig_insightSelector(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - - insight_selector { - insight_type = "ApiCallRateInsight" - } -} -`, rName)) -} - -func testAccCloudTrailConfig_insightSelectorMulti(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - - insight_selector { - insight_type = "ApiCallRateInsight" - } - - insight_selector { - insight_type = "ApiErrorRateInsight" - } -} -`, rName)) -} - -func testAccCloudTrailConfig_advancedEventSelector(rName string) string { - return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` -resource "aws_cloudtrail" "test" { - # Must have bucket policy attached first - depends_on = [aws_s3_bucket_policy.test] - - name = %[1]q - s3_bucket_name = aws_s3_bucket.test.id - - advanced_event_selector { - name = "s3Custom" - field_selector { - field = "eventCategory" - equals = ["Data"] - } - - field_selector { - field = "eventName" - equals = ["DeleteObject"] - } - - field_selector { - field = "resources.ARN" - equals = [ - "${aws_s3_bucket.test2.arn}/foobar", - "${aws_s3_bucket.test2.arn}/bar", - ] - } - - field_selector { - field = "readOnly" - equals = ["false"] - } - - field_selector { - field = "resources.type" - equals = ["AWS::S3::Object"] - } - } - advanced_event_selector { - name = "lambdaLogAllEvents" - field_selector { - field = "eventCategory" - equals = ["Data"] - } - - field_selector { - field = "resources.type" - equals = ["AWS::Lambda::Function"] - } - } - - advanced_event_selector { - name = "dynamoDbReadOnlyEvents" - field_selector { - field = "eventCategory" - equals = ["Data"] - } - - field_selector { - field = "readOnly" - equals = ["true"] - } - - field_selector { - field = "resources.type" - equals = ["AWS::DynamoDB::Table"] - } - } - - advanced_event_selector { - name = "s3OutpostsWriteOnlyEvents" - field_selector { - field = "eventCategory" - equals = ["Data"] - } - - field_selector { - field = "readOnly" - equals = ["false"] - } - - field_selector { - field = "resources.type" - equals = ["AWS::S3Outposts::Object"] - } - } - - advanced_event_selector { - name = "managementEventsSelector" - field_selector { - field = "eventCategory" - equals = ["Management"] - } - } -} - -resource "aws_s3_bucket" "test2" { - bucket = "%[1]s-2" - force_destroy = true -} -`, rName)) -} diff --git a/internal/service/cloudtrail/organization_delegated_admin_account_test.go b/internal/service/cloudtrail/organization_delegated_admin_account_test.go index d92fa3a3896..e5417e3cee3 100644 --- a/internal/service/cloudtrail/organization_delegated_admin_account_test.go +++ b/internal/service/cloudtrail/organization_delegated_admin_account_test.go @@ -21,7 +21,7 @@ import ( // Prerequisites: // * Organizations management account // * Organization member account -func TestAccCloudTrailOrganizationDelegatedAdminAccount_basic(t *testing.T) { +func testAccOrganizationDelegatedAdminAccount_basic(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_cloudtrail_organization_delegated_admin_account.test" @@ -53,7 +53,7 @@ func TestAccCloudTrailOrganizationDelegatedAdminAccount_basic(t *testing.T) { }) } -func TestAccCloudTrailOrganizationDelegatedAdminAccount_disappears(t *testing.T) { +func testAccOrganizationDelegatedAdminAccount_disappears(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_cloudtrail_organization_delegated_admin_account.test" diff --git a/internal/service/cloudtrail/trail_test.go b/internal/service/cloudtrail/trail_test.go new file mode 100644 index 00000000000..78bd5c101d2 --- /dev/null +++ b/internal/service/cloudtrail/trail_test.go @@ -0,0 +1,1573 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cloudtrail_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudtrail" + "github.com/aws/aws-sdk-go-v2/service/cloudtrail/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" + tfcloudtrail "github.com/hashicorp/terraform-provider-aws/internal/service/cloudtrail" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func testAccTrail_basic(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtFalse), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_modified(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, names.AttrPrefix), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtFalse), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTrail_cloudWatch(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_cloudWatch(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_group_arn"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_role_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_cloudWatchModified(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_group_arn"), + resource.TestCheckResourceAttrSet(resourceName, "cloud_watch_logs_role_arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTrail_enableLogging(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_enableLogging(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + // AWS will create the trail with logging turned off. + // Test that "enable_logging" default works. + testAccCheckLoggingEnabled(ctx, resourceName, true), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_enableLogging(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + testAccCheckLoggingEnabled(ctx, resourceName, false), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + testAccCheckLoggingEnabled(ctx, resourceName, true), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + }, + }) +} + +func testAccTrail_multiRegion(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtFalse), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + Config: testAccCloudTrailConfig_multiRegion(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtTrue), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "is_multi_region_trail", acctest.CtFalse), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + }, + }) +} + +func testAccTrail_organization(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckOrganizationManagementAccount(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_organization(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtTrue), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "is_organization_trail", acctest.CtFalse), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + }, + }) +} + +func testAccTrail_logValidation(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_logValidation(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), + testAccCheckLogValidationEnabled(resourceName, true, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_logValidationModified(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrKMSKeyID, ""), + ), + }, + }, + }) +} + +func testAccTrail_kmsKey(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resourceName := "aws_cloudtrail.test" + kmsResourceName := "aws_kms_key.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_kmsKey(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, names.AttrS3KeyPrefix, ""), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtTrue), + testAccCheckLogValidationEnabled(resourceName, false, &trail), + resource.TestCheckResourceAttrPair(resourceName, names.AttrKMSKeyID, kmsResourceName, names.AttrARN), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTrail_tags(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_tags1(rName, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_tags2(rName, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey1, acctest.CtValue1Updated), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + { + Config: testAccCloudTrailConfig_tags1(rName, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsKey2, acctest.CtValue2), + ), + }, + }, + }) +} + +func testAccTrail_globalServiceEvents(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_globalServiceEvents(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + resource.TestCheckResourceAttr(resourceName, "include_global_service_events", acctest.CtFalse), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTrail_eventSelector(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_eventSelector(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct2), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/isen", rName)), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/ko", rName)), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtFalse), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "ReadOnly"), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct0), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_eventSelectorReadWriteType(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "WriteOnly"), + ), + }, + { + Config: testAccCloudTrailConfig_eventSelectorModified(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct2), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/isen", rName)), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.0.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/ko", rName)), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "ReadOnly"), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.#", acctest.Ct2), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.0.type", "AWS::S3::Object"), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.0.values.#", acctest.Ct2), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.1.data_resource.0.values.0", "s3", fmt.Sprintf("%s-2/tf1", rName)), + acctest.CheckResourceAttrGlobalARNNoAccount(resourceName, "event_selector.1.data_resource.0.values.1", "s3", fmt.Sprintf("%s-2/tf2", rName)), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.1.type", "AWS::Lambda::Function"), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.data_resource.1.values.#", acctest.Ct1), + acctest.CheckResourceAttrRegionalARN(resourceName, "event_selector.1.data_resource.1.values.0", "lambda", fmt.Sprintf("function:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct0), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.include_management_events", acctest.CtFalse), + resource.TestCheckResourceAttr(resourceName, "event_selector.1.read_write_type", "All"), + ), + }, + { + Config: testAccCloudTrailConfig_eventSelectorNone(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct0), + ), + }, + }, + }) +} + +func testAccTrail_eventSelectorDynamoDB(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_eventSelectorDynamoDB(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.type", "AWS::DynamoDB::Table"), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.data_resource.0.values.#", acctest.Ct1), + acctest.MatchResourceAttrRegionalARN(resourceName, "event_selector.0.data_resource.0.values.0", "dynamodb", regexache.MustCompile(`table/tf-acc-test-.+`)), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.read_write_type", "All"), + ), + }, + }, + }) +} + +func testAccTrail_eventSelectorExclude(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_eventSelectorExcludeKMS(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct1), + resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "kms.amazonaws.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_eventSelectorExcludeKMSAndRDSData(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.include_management_events", acctest.CtTrue), + resource.TestCheckResourceAttr(resourceName, "event_selector.0.exclude_management_event_sources.#", acctest.Ct2), + resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "kms.amazonaws.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "event_selector.0.exclude_management_event_sources.*", "rdsdata.amazonaws.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_eventSelectorNone(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "event_selector.#", acctest.Ct0), + ), + }, + }, + }) +} + +func testAccTrail_insightSelector(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_cloudtrail.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_insightSelector(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "insight_selector.0.insight_type", "ApiCallRateInsight"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccCloudTrailConfig_insightSelectorMulti(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct2), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "insight_selector.*", map[string]string{ + "insight_type": "ApiCallRateInsight", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "insight_selector.*", map[string]string{ + "insight_type": "ApiErrorRateInsight", + }), + ), + }, + { + Config: testAccCloudTrailConfig_insightSelector(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct1), + resource.TestCheckResourceAttr(resourceName, "insight_selector.0.insight_type", "ApiCallRateInsight"), + ), + }, + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "insight_selector.#", acctest.Ct0), + ), + }, + }, + }) +} + +func testAccTrail_advancedEventSelector(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_cloudtrail.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_advancedEventSelector(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.#", "5"), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.0.name", "s3Custom"), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.0.field_selector.#", "5"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ + names.AttrField: "eventCategory", + "equals.#": acctest.Ct1, + "equals.0": "Data", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ + names.AttrField: "eventName", + "equals.#": acctest.Ct1, + "equals.0": "DeleteObject", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ + names.AttrField: "resources.ARN", + "equals.#": acctest.Ct2, + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ + names.AttrField: "readOnly", + "equals.#": acctest.Ct1, + "equals.0": acctest.CtFalse, + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.0.field_selector.*", map[string]string{ + names.AttrField: "resources.type", + "equals.#": acctest.Ct1, + "equals.0": "AWS::S3::Object", + }), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.1.name", "lambdaLogAllEvents"), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.1.field_selector.#", acctest.Ct2), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.1.field_selector.*", map[string]string{ + names.AttrField: "eventCategory", + "equals.#": acctest.Ct1, + "equals.0": "Data", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.1.field_selector.*", map[string]string{ + names.AttrField: "resources.type", + "equals.#": acctest.Ct1, + "equals.0": "AWS::Lambda::Function", + }), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.2.name", "dynamoDbReadOnlyEvents"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.2.field_selector.*", map[string]string{ + names.AttrField: "readOnly", + "equals.#": acctest.Ct1, + "equals.0": acctest.CtTrue, + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.2.field_selector.*", map[string]string{ + names.AttrField: "resources.type", + "equals.#": acctest.Ct1, + "equals.0": "AWS::DynamoDB::Table", + }), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.3.name", "s3OutpostsWriteOnlyEvents"), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.3.field_selector.#", acctest.Ct3), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ + names.AttrField: "eventCategory", + "equals.#": acctest.Ct1, + "equals.0": "Data", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ + names.AttrField: "readOnly", + "equals.#": acctest.Ct1, + "equals.0": acctest.CtFalse, + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.3.field_selector.*", map[string]string{ + names.AttrField: "resources.type", + "equals.#": acctest.Ct1, + "equals.0": "AWS::S3Outposts::Object", + }), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.4.name", "managementEventsSelector"), + resource.TestCheckResourceAttr(resourceName, "advanced_event_selector.4.field_selector.#", acctest.Ct1), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "advanced_event_selector.4.field_selector.*", map[string]string{ + names.AttrField: "eventCategory", + "equals.#": acctest.Ct1, + "equals.0": "Management", + }), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccTrail_disappears(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceTrail(), resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfcloudtrail.ResourceTrail(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccTrail_migrateV0(t *testing.T) { + ctx := acctest.Context(t) + var trail types.Trail + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudtrail.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudTrailServiceID), + CheckDestroy: testAccCheckTrailDestroy(ctx), + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "aws": { + Source: "hashicorp/aws", + VersionConstraint: "5.24.0", + }, + }, + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttrPair(resourceName, names.AttrID, resourceName, names.AttrName), + ), + }, + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Config: testAccCloudTrailConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrailExists(ctx, resourceName, &trail), + acctest.CheckResourceAttrRegionalARN(resourceName, names.AttrARN, "cloudtrail", fmt.Sprintf("trail/%s", rName)), + resource.TestCheckResourceAttr(resourceName, names.AttrName, rName), + resource.TestCheckResourceAttrPair(resourceName, names.AttrID, resourceName, names.AttrARN), + ), + }, + }, + }) +} + +func testAccCheckTrailExists(ctx context.Context, n string, v *types.Trail) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) + + output, err := tfcloudtrail.FindTrailByARN(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckLoggingEnabled(ctx context.Context, n string, want bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) + + output, err := conn.GetTrailStatus(ctx, &cloudtrail.GetTrailStatusInput{ + Name: aws.String(rs.Primary.ID), + }) + + if err != nil { + return err + } + + if got := aws.ToBool(output.IsLogging); got != want { + return fmt.Errorf("Expected logging status %t, given %t", want, got) + } + + return nil + } +} + +func testAccCheckLogValidationEnabled(n string, desired bool, trail *types.Trail) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if trail.LogFileValidationEnabled == nil { + return fmt.Errorf("No LogFileValidationEnabled attribute present") + } + + logValid := aws.ToBool(trail.LogFileValidationEnabled) + if logValid != desired { + return fmt.Errorf("Expected log validation status %t, given %t", desired, logValid) + } + + // local state comparison + enabled, ok := rs.Primary.Attributes["enable_log_file_validation"] + if !ok { + return fmt.Errorf("No enable_log_file_validation attribute defined for %s, expected %t", + n, desired) + } + desiredInString := fmt.Sprintf("%t", desired) + if enabled != desiredInString { + return fmt.Errorf("Expected log validation status %s, saved %s", desiredInString, enabled) + } + + return nil + } +} + +func testAccCheckTrailDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudTrailClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudtrail" { + continue + } + + _, err := tfcloudtrail.FindTrailByARN(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudTrail Trail (%s) still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCloudTrailConfig_base(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +data "aws_partition" "current" {} + +data "aws_region" "current" {} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_s3_bucket_policy" "test" { + bucket = aws_s3_bucket.test.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "AWSCloudTrailAclCheck" + Effect = "Allow" + Principal = { + Service = "cloudtrail.amazonaws.com" + } + Action = "s3:GetBucketAcl" + Resource = "arn:${data.aws_partition.current.partition}:s3:::%[1]s" + Condition = { + StringEquals = { + "aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/%[1]s" + } + } + }, + { + Sid = "AWSCloudTrailWrite" + Effect = "Allow" + Principal = { + Service = "cloudtrail.amazonaws.com" + } + Action = "s3:PutObject" + Resource = "arn:${data.aws_partition.current.partition}:s3:::%[1]s/*" + Condition = { + StringEquals = { + "s3:x-amz-acl" = "bucket-owner-full-control" + "aws:SourceArn" = "arn:${data.aws_partition.current.partition}:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/%[1]s" + } + } + } + ] + }) +} +`, rName) +} + +func testAccCloudTrailConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id +} +`, rName)) +} + +func testAccCloudTrailConfig_modified(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + s3_key_prefix = "prefix" + include_global_service_events = false +} +`, rName)) +} + +func testAccCloudTrailConfig_enableLogging(rName string, enableLogging bool) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + s3_key_prefix = "prefix" + include_global_service_events = false + enable_logging = %[2]t +} +`, rName, enableLogging)) +} + +func testAccCloudTrailConfig_cloudWatch(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.test.arn}:*" + cloud_watch_logs_role_arn = aws_iam_role.test.arn +} + +resource "aws_cloudwatch_log_group" "test" { + name = %[1]q +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + + Statement = [{ + Sid = "" + Effect = "Allow" + Action = "sts:AssumeRole" + + Principal = { + Service = "cloudtrail.${data.aws_partition.current.dns_suffix}" + } + }] + }) +} + +resource "aws_iam_role_policy" "test" { + name = %[1]q + role = aws_iam_role.test.id + + policy = jsonencode({ + Version = "2012-10-17" + + Statement = [{ + Sid = "AWSCloudTrailCreateLogStream" + Effect = "Allow" + Resource = "${aws_cloudwatch_log_group.test.arn}:*" + + Action = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + }] + }) +} +`, rName)) +} + +func testAccCloudTrailConfig_cloudWatchModified(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.test2.arn}:*" + cloud_watch_logs_role_arn = aws_iam_role.test.arn +} + +resource "aws_cloudwatch_log_group" "test" { + name = %[1]q +} + +resource "aws_cloudwatch_log_group" "test2" { + name = "%[1]s-2" +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + + Statement = [{ + Sid = "" + Effect = "Allow" + Action = "sts:AssumeRole" + + Principal = { + Service = "cloudtrail.${data.aws_partition.current.dns_suffix}" + } + }] + }) +} + +resource "aws_iam_role_policy" "test" { + name = %[1]q + role = aws_iam_role.test.id + + policy = jsonencode({ + Version = "2012-10-17" + + Statement = [{ + Sid = "AWSCloudTrailCreateLogStream" + Effect = "Allow" + Resource = "${aws_cloudwatch_log_group.test2.arn}:*" + + Action = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + }] + }) +} +`, rName)) +} + +func testAccCloudTrailConfig_multiRegion(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + is_multi_region_trail = true +} +`, rName)) +} + +func testAccCloudTrailConfig_organization(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + is_organization_trail = true + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id +} +`, rName)) +} + +func testAccCloudTrailConfig_logValidation(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + is_multi_region_trail = true + include_global_service_events = true + enable_log_file_validation = true +} +`, rName)) +} + +func testAccCloudTrailConfig_logValidationModified(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + include_global_service_events = true +} +`, rName)) +} + +func testAccCloudTrailConfig_kmsKey(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_kms_key" "test" { + description = %[1]q + + policy = jsonencode({ + Version = "2012-10-17" + Id = %[1]q + + Statement = [{ + Sid = "Enable IAM User Permissions" + Effect = "Allow" + Action = "kms:*" + Resource = "*" + + Principal = { + AWS = "*" + } + }] + }) +} + +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + include_global_service_events = true + kms_key_id = aws_kms_key.test.arn +} +`, rName)) +} + +func testAccCloudTrailConfig_globalServiceEvents(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + include_global_service_events = false +} +`, rName)) +} + +func testAccCloudTrailConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1)) +} + +func testAccCloudTrailConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) +} + +func testAccCloudTrailConfig_eventSelector(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + read_write_type = "ReadOnly" + include_management_events = false + + data_resource { + type = "AWS::S3::Object" + + values = [ + "${aws_s3_bucket.test2.arn}/isen", + "${aws_s3_bucket.test2.arn}/ko", + ] + } + } +} + +resource "aws_s3_bucket" "test2" { + bucket = "%[1]s-2" + force_destroy = true +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorReadWriteType(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + read_write_type = "WriteOnly" + include_management_events = true + } +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorModified(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + read_write_type = "ReadOnly" + include_management_events = true + + data_resource { + type = "AWS::S3::Object" + + values = [ + "${aws_s3_bucket.test2.arn}/isen", + "${aws_s3_bucket.test2.arn}/ko", + ] + } + } + + event_selector { + read_write_type = "All" + include_management_events = false + + data_resource { + type = "AWS::S3::Object" + + values = [ + "${aws_s3_bucket.test2.arn}/tf1", + "${aws_s3_bucket.test2.arn}/tf2", + ] + } + + data_resource { + type = "AWS::Lambda::Function" + + values = [ + aws_lambda_function.test.arn, + ] + } + } +} + +resource "aws_s3_bucket" "test2" { + bucket = "%[1]s-2" + force_destroy = true +} + +resource "aws_iam_role" "test" { + name = %[1]q + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + + Statement = [{ + Sid = "" + Effect = "Allow" + Action = "sts:AssumeRole" + + Principal = { + Service = "lambda.${data.aws_partition.current.dns_suffix}" + } + }] + }) +} + +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q + role = aws_iam_role.test.arn + handler = "exports.example" + runtime = "nodejs16.x" +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorNone(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorDynamoDB(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + read_write_type = "All" + include_management_events = true + + data_resource { + type = "AWS::DynamoDB::Table" + + values = [ + aws_dynamodb_table.test.arn, + ] + } + } +} + +resource "aws_dynamodb_table" "test" { + name = %[1]q + read_capacity = 1 + write_capacity = 1 + hash_key = %[1]q + + attribute { + name = %[1]q + type = "S" + } +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorExcludeKMS(rName string) string { + return acctest.ConfigCompose( + testAccCloudTrailConfig_base(rName), + fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + exclude_management_event_sources = ["kms.${data.aws_partition.current.dns_suffix}"] + } +} +`, rName)) +} + +func testAccCloudTrailConfig_eventSelectorExcludeKMSAndRDSData(rName string) string { + return acctest.ConfigCompose( + testAccCloudTrailConfig_base(rName), + fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + event_selector { + exclude_management_event_sources = [ + "kms.${data.aws_partition.current.dns_suffix}", + "rdsdata.${data.aws_partition.current.dns_suffix}" + ] + } +} +`, rName)) +} + +func testAccCloudTrailConfig_insightSelector(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + + insight_selector { + insight_type = "ApiCallRateInsight" + } +} +`, rName)) +} + +func testAccCloudTrailConfig_insightSelectorMulti(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + + insight_selector { + insight_type = "ApiCallRateInsight" + } + + insight_selector { + insight_type = "ApiErrorRateInsight" + } +} +`, rName)) +} + +func testAccCloudTrailConfig_advancedEventSelector(rName string) string { + return acctest.ConfigCompose(testAccCloudTrailConfig_base(rName), fmt.Sprintf(` +resource "aws_cloudtrail" "test" { + # Must have bucket policy attached first + depends_on = [aws_s3_bucket_policy.test] + + name = %[1]q + s3_bucket_name = aws_s3_bucket.test.id + + advanced_event_selector { + name = "s3Custom" + field_selector { + field = "eventCategory" + equals = ["Data"] + } + + field_selector { + field = "eventName" + equals = ["DeleteObject"] + } + + field_selector { + field = "resources.ARN" + equals = [ + "${aws_s3_bucket.test2.arn}/foobar", + "${aws_s3_bucket.test2.arn}/bar", + ] + } + + field_selector { + field = "readOnly" + equals = ["false"] + } + + field_selector { + field = "resources.type" + equals = ["AWS::S3::Object"] + } + } + advanced_event_selector { + name = "lambdaLogAllEvents" + field_selector { + field = "eventCategory" + equals = ["Data"] + } + + field_selector { + field = "resources.type" + equals = ["AWS::Lambda::Function"] + } + } + + advanced_event_selector { + name = "dynamoDbReadOnlyEvents" + field_selector { + field = "eventCategory" + equals = ["Data"] + } + + field_selector { + field = "readOnly" + equals = ["true"] + } + + field_selector { + field = "resources.type" + equals = ["AWS::DynamoDB::Table"] + } + } + + advanced_event_selector { + name = "s3OutpostsWriteOnlyEvents" + field_selector { + field = "eventCategory" + equals = ["Data"] + } + + field_selector { + field = "readOnly" + equals = ["false"] + } + + field_selector { + field = "resources.type" + equals = ["AWS::S3Outposts::Object"] + } + } + + advanced_event_selector { + name = "managementEventsSelector" + field_selector { + field = "eventCategory" + equals = ["Management"] + } + } +} + +resource "aws_s3_bucket" "test2" { + bucket = "%[1]s-2" + force_destroy = true +} +`, rName)) +}