diff --git a/.changelog/45534.txt b/.changelog/45534.txt new file mode 100644 index 00000000000..336daad11ca --- /dev/null +++ b/.changelog/45534.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_cloudfront_trust_store +``` \ No newline at end of file diff --git a/internal/service/cloudfront/exports_test.go b/internal/service/cloudfront/exports_test.go index 4402c061fb9..c9556eedd1d 100644 --- a/internal/service/cloudfront/exports_test.go +++ b/internal/service/cloudfront/exports_test.go @@ -19,6 +19,7 @@ var ( ResourcePublicKey = resourcePublicKey ResourceRealtimeLogConfig = resourceRealtimeLogConfig ResourceResponseHeadersPolicy = resourceResponseHeadersPolicy + ResourceTrustStore = newTrustStoreResource ResourceVPCOrigin = newVPCOriginResource FindCachePolicyByID = findCachePolicyByID @@ -35,6 +36,8 @@ var ( FindPublicKeyByID = findPublicKeyByID FindRealtimeLogConfigByARN = findRealtimeLogConfigByARN FindResponseHeadersPolicyByID = findResponseHeadersPolicyByID + FindTrustStoreByID = findTrustStoreByID FindVPCOriginByID = findVPCOriginByID - WaitDistributionDeployed = waitDistributionDeployed + + WaitDistributionDeployed = waitDistributionDeployed ) diff --git a/internal/service/cloudfront/service_package_gen.go b/internal/service/cloudfront/service_package_gen.go index 1db1f6705ad..3b41b8688e2 100644 --- a/internal/service/cloudfront/service_package_gen.go +++ b/internal/service/cloudfront/service_package_gen.go @@ -57,6 +57,15 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.Ser WrappedImport: true, }, }, + { + Factory: newTrustStoreResource, + TypeName: "aws_cloudfront_trust_store", + Name: "Trust Store", + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrARN, + }), + Region: unique.Make(inttypes.ResourceRegionDisabled()), + }, { Factory: newVPCOriginResource, TypeName: "aws_cloudfront_vpc_origin", diff --git a/internal/service/cloudfront/sweep.go b/internal/service/cloudfront/sweep.go index a168c02ef54..6c3d9a0aa7a 100644 --- a/internal/service/cloudfront/sweep.go +++ b/internal/service/cloudfront/sweep.go @@ -4,6 +4,7 @@ package cloudfront import ( + "context" "errors" "fmt" "log" @@ -12,6 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/cloudfront" awstypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/retry" "github.com/hashicorp/terraform-provider-aws/internal/sweep" "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv2" @@ -99,6 +101,8 @@ func RegisterSweepers() { }, }) + awsv2.Register("aws_cloudfront_trust_store", sweepTrustStores, "aws_cloudfront_distribution") + resource.AddTestSweepers("aws_cloudfront_vpc_origin", &resource.Sweeper{ Name: "aws_cloudfront_vpc_origin", F: sweepVPCOrigins, @@ -770,6 +774,28 @@ func sweepOriginAccessControls(region string) error { return nil } +func sweepTrustStores(ctx context.Context, client *conns.AWSClient) ([]sweep.Sweepable, error) { + input := cloudfront.ListTrustStoresInput{} + conn := client.CloudFrontClient(ctx) + var sweepResources []sweep.Sweepable + + pages := cloudfront.NewListTrustStoresPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + return nil, err + } + + for _, v := range page.TrustStoreList { + sweepResources = append(sweepResources, framework.NewSweepResource(newTrustStoreResource, client, + framework.NewAttribute(names.AttrID, aws.ToString(v.Id)), + )) + } + } + + return sweepResources, nil +} + func sweepVPCOrigins(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(ctx, region) diff --git a/internal/service/cloudfront/trust_store.go b/internal/service/cloudfront/trust_store.go new file mode 100644 index 00000000000..9ec95f32922 --- /dev/null +++ b/internal/service/cloudfront/trust_store.go @@ -0,0 +1,400 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package cloudfront + +import ( + "context" + "errors" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/cloudfront" + awstypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + sdkretry "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/retry" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource("aws_cloudfront_trust_store", name="Trust Store") +// @Tags(identifierAttribute="arn") +// @Testing(tagsTest=false) +func newTrustStoreResource(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &trustStoreResource{} + + r.SetDefaultCreateTimeout(10 * time.Minute) + r.SetDefaultUpdateTimeout(10 * time.Minute) + r.SetDefaultDeleteTimeout(10 * time.Minute) + + return r, nil +} + +type trustStoreResource struct { + framework.ResourceWithModel[trustStoreResourceModel] + framework.WithTimeouts + framework.WithImportByID +} + +func (r *trustStoreResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrARN: framework.ARNAttributeComputedOnly(), + "etag": schema.StringAttribute{ + Computed: true, + }, + names.AttrID: framework.IDAttribute(), + names.AttrName: schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "number_of_ca_certificates": schema.Int32Attribute{ + Computed: true, + }, + names.AttrTags: tftags.TagsAttribute(), + names.AttrTagsAll: tftags.TagsAttributeComputedOnly(), + }, + Blocks: map[string]schema.Block{ + "ca_certificates_bundle_source": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[caCertificatesBundleSourceModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "ca_certificates_bundle_s3_location": schema.ListNestedBlock{ + CustomType: fwtypes.NewListNestedObjectTypeOf[caCertificatesBundleS3LocationModel](ctx), + Validators: []validator.List{ + listvalidator.SizeAtMost(1), + listvalidator.IsRequired(), + }, + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + names.AttrBucket: schema.StringAttribute{ + Required: true, + }, + names.AttrKey: schema.StringAttribute{ + Required: true, + }, + names.AttrRegion: schema.StringAttribute{ + Required: true, + }, + names.AttrVersion: schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + }, + }, + names.AttrTimeouts: timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Update: true, + Delete: true, + }), + }, + } +} + +func (r *trustStoreResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data trustStoreResourceModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &data)) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().CloudFrontClient(ctx) + + name := fwflex.StringValueFromFramework(ctx, data.Name) + var input cloudfront.CreateTrustStoreInput + smerr.AddEnrich(ctx, &resp.Diagnostics, fwflex.Expand(ctx, data, &input)) + if resp.Diagnostics.HasError() { + return + } + + // Additional fields. + if tags := getTagsIn(ctx); len(tags) > 0 { + input.Tags = &awstypes.Tags{ + Items: tags, + } + } + + outCTS, err := conn.CreateTrustStore(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, name) + return + } + + // Set values for unknowns. + data.ARN = fwflex.StringToFramework(ctx, outCTS.TrustStore.Arn) + id := aws.ToString(outCTS.TrustStore.Id) + data.ID = fwflex.StringValueToFramework(ctx, id) + + outGTS, err := waitTrustStoreActive(ctx, conn, id, r.CreateTimeout(ctx, data.Timeouts)) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } + + // Set values for unknowns. + data.Etag = fwflex.StringToFramework(ctx, outGTS.ETag) + data.NumberOfCACertificates = fwflex.Int32ToFramework(ctx, outGTS.TrustStore.NumberOfCaCertificates) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, data)) +} + +func (r *trustStoreResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state trustStoreResourceModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().CloudFrontClient(ctx) + + id := fwflex.StringValueFromFramework(ctx, state.ID) + out, err := findTrustStoreByID(ctx, conn, id) + if retry.NotFound(err) { + resp.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, fwflex.Flatten(ctx, out.TrustStore, &state)) + if resp.Diagnostics.HasError() { + return + } + + state.Etag = fwflex.StringToFramework(ctx, out.ETag) + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &state)) +} + +func (r *trustStoreResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state trustStoreResourceModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.Plan.Get(ctx, &plan)) + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().CloudFrontClient(ctx) + + diff, d := fwflex.Diff(ctx, plan, state) + smerr.AddEnrich(ctx, &resp.Diagnostics, d) + if resp.Diagnostics.HasError() { + return + } + + if diff.HasChanges() { + id, etag := fwflex.StringValueFromFramework(ctx, plan.ID), fwflex.StringValueFromFramework(ctx, state.Etag) + var input cloudfront.UpdateTrustStoreInput + smerr.AddEnrich(ctx, &resp.Diagnostics, fwflex.Expand(ctx, plan, &input)) + if resp.Diagnostics.HasError() { + return + } + + // Additional fields. + input.IfMatch = aws.String(etag) + + _, err := conn.UpdateTrustStore(ctx, &input) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } + + outGTS, err := waitTrustStoreActive(ctx, conn, id, r.UpdateTimeout(ctx, plan.Timeouts)) + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } + + // Set values for unknowns. + plan.Etag = fwflex.StringToFramework(ctx, outGTS.ETag) + plan.NumberOfCACertificates = fwflex.Int32ToFramework(ctx, outGTS.TrustStore.NumberOfCaCertificates) + } else { + plan.Etag = state.Etag + plan.NumberOfCACertificates = state.NumberOfCACertificates + } + + smerr.AddEnrich(ctx, &resp.Diagnostics, resp.State.Set(ctx, &plan)) +} + +func (r *trustStoreResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state trustStoreResourceModel + smerr.AddEnrich(ctx, &resp.Diagnostics, req.State.Get(ctx, &state)) + if resp.Diagnostics.HasError() { + return + } + + conn := r.Meta().CloudFrontClient(ctx) + + id, etag := fwflex.StringValueFromFramework(ctx, state.ID), fwflex.StringValueFromFramework(ctx, state.Etag) + input := cloudfront.DeleteTrustStoreInput{ + Id: aws.String(id), + IfMatch: aws.String(etag), + } + _, err := conn.DeleteTrustStore(ctx, &input) + if errs.IsA[*awstypes.EntityNotFound](err) { + return + } + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } + + if _, err := waitTrustStoreDeleted(ctx, conn, id, r.DeleteTimeout(ctx, state.Timeouts)); err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, id) + return + } +} + +func waitTrustStoreActive(ctx context.Context, conn *cloudfront.Client, id string, timeout time.Duration) (*cloudfront.GetTrustStoreOutput, error) { + stateConf := &sdkretry.StateChangeConf{ + Pending: []string{}, + Target: enum.Slice(awstypes.TrustStoreStatusActive), + Refresh: statusTrustStore(ctx, conn, id), + Timeout: timeout, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*cloudfront.GetTrustStoreOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(out.TrustStore.Reason))) + return out, err + } + + return nil, err +} + +func waitTrustStoreDeleted(ctx context.Context, conn *cloudfront.Client, id string, timeout time.Duration) (*cloudfront.GetTrustStoreOutput, error) { + stateConf := &sdkretry.StateChangeConf{ + Pending: enum.Slice(awstypes.TrustStoreStatusActive), + Target: []string{}, + Refresh: statusTrustStore(ctx, conn, id), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + if out, ok := outputRaw.(*cloudfront.GetTrustStoreOutput); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(out.TrustStore.Reason))) + return out, err + } + + return nil, err +} + +func statusTrustStore(ctx context.Context, conn *cloudfront.Client, id string) sdkretry.StateRefreshFunc { + return func() (any, string, error) { + out, err := findTrustStoreByID(ctx, conn, id) + if retry.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return out, string(out.TrustStore.Status), nil + } +} + +func findTrustStoreByID(ctx context.Context, conn *cloudfront.Client, id string) (*cloudfront.GetTrustStoreOutput, error) { + input := cloudfront.GetTrustStoreInput{ + Identifier: aws.String(id), + } + + return findTrustStore(ctx, conn, &input) +} + +func findTrustStore(ctx context.Context, conn *cloudfront.Client, input *cloudfront.GetTrustStoreInput) (*cloudfront.GetTrustStoreOutput, error) { + out, err := conn.GetTrustStore(ctx, input) + + if errs.IsA[*awstypes.EntityNotFound](err) { + return nil, &sdkretry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if out == nil || out.TrustStore == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return out, nil +} + +type trustStoreResourceModel struct { + ARN types.String `tfsdk:"arn"` + CACertificatesBundleSource fwtypes.ListNestedObjectValueOf[caCertificatesBundleSourceModel] `tfsdk:"ca_certificates_bundle_source"` + Etag types.String `tfsdk:"etag"` + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + NumberOfCACertificates types.Int32 `tfsdk:"number_of_ca_certificates"` + Tags tftags.Map `tfsdk:"tags"` + TagsAll tftags.Map `tfsdk:"tags_all"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +type caCertificatesBundleSourceModel struct { + CACertificatesBundleS3Location fwtypes.ListNestedObjectValueOf[caCertificatesBundleS3LocationModel] `tfsdk:"ca_certificates_bundle_s3_location"` +} + +type caCertificatesBundleS3LocationModel struct { + Bucket types.String `tfsdk:"bucket"` + Key types.String `tfsdk:"key"` + Region types.String `tfsdk:"region"` + Version types.String `tfsdk:"version"` +} + +var ( + _ fwflex.Expander = caCertificatesBundleSourceModel{} +) + +func (m caCertificatesBundleSourceModel) Expand(ctx context.Context) (any, diag.Diagnostics) { + var diags diag.Diagnostics + switch { + case !m.CACertificatesBundleS3Location.IsNull(): + data, d := m.CACertificatesBundleS3Location.ToPtr(ctx) + smerr.AddEnrich(ctx, &diags, d) + if diags.HasError() { + return nil, diags + } + var r awstypes.CaCertificatesBundleSourceMemberCaCertificatesBundleS3Location + smerr.AddEnrich(ctx, &diags, fwflex.Expand(ctx, data, &r.Value)) + if diags.HasError() { + return nil, diags + } + return &r, diags + } + return nil, diags +} diff --git a/internal/service/cloudfront/trust_store_test.go b/internal/service/cloudfront/trust_store_test.go new file mode 100644 index 00000000000..a14b2938da8 --- /dev/null +++ b/internal/service/cloudfront/trust_store_test.go @@ -0,0 +1,500 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package cloudfront_test + +import ( + "context" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/cloudfront" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfknownvalue "github.com/hashicorp/terraform-provider-aws/internal/acctest/knownvalue" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/retry" + tfcloudfront "github.com/hashicorp/terraform-provider-aws/internal/service/cloudfront" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccCloudFrontTrustStore_basic(t *testing.T) { + ctx := acctest.Context(t) + var truststore cloudfront.GetTrustStoreOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_trust_store.test" + objectKey := "ca-bundle.pem" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudFrontEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudFrontServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_basic(rName, objectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrARN), tfknownvalue.GlobalARNRegexp("cloudfront", regexache.MustCompile(`trust-store/.+`))), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("etag"), knownvalue.NotNull()), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_of_ca_certificates"), knownvalue.Int32Exact(1)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.Null()), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "ca_certificates_bundle_source", + }, + }, + }, + }) +} + +func TestAccCloudFrontTrustStore_disappears(t *testing.T) { + ctx := acctest.Context(t) + var truststore cloudfront.GetTrustStoreOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_trust_store.test" + objectKey := "ca-bundle.pem" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudFrontEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudFrontServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_basic(rName, objectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfcloudfront.ResourceTrustStore, resourceName), + ), + ExpectNonEmptyPlan: true, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + PostApplyPostRefresh: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + }, + }) +} + +func TestAccCloudFrontTrustStore_withS3ObjectVersion(t *testing.T) { + ctx := acctest.Context(t) + var truststore cloudfront.GetTrustStoreOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_trust_store.test" + objectKey := "ca-bundle.pem" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudFrontEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudFrontServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_withS3ObjectVersion(rName, objectKey), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "ca_certificates_bundle_source", + }, + }, + }, + }) +} + +func TestAccCloudFrontTrustStore_update(t *testing.T) { + ctx := acctest.Context(t) + var truststore cloudfront.GetTrustStoreOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_trust_store.test" + objectKey1 := "ca-bundle_v1.pem" + objectKey2 := "ca-bundle_v2.pem" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudFrontEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudFrontServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_basic(rName, objectKey1), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + }, + { + Config: testAccTrustStoreConfig_update(rName, objectKey2), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "ca_certificates_bundle_source", + }, + }, + }, + }) +} + +func TestAccCloudFrontTrustStore_tags(t *testing.T) { + ctx := acctest.Context(t) + var truststore cloudfront.GetTrustStoreOutput + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_cloudfront_trust_store.test" + objectKey := "ca-bundle.pem" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.CloudFrontEndpointID) + }, + ErrorCheck: acctest.ErrorCheck(t, names.CloudFrontServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_tags1(rName, objectKey, acctest.CtKey1, acctest.CtValue1), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1), + })), + }, + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "ca_certificates_bundle_source", + }, + }, + { + Config: testAccTrustStoreConfig_tags2(rName, objectKey, acctest.CtKey1, acctest.CtValue1Updated, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey1: knownvalue.StringExact(acctest.CtValue1Updated), + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + { + Config: testAccTrustStoreConfig_tags1(rName, objectKey, acctest.CtKey2, acctest.CtValue2), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &truststore), + ), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New(names.AttrTags), knownvalue.MapExact(map[string]knownvalue.Check{ + acctest.CtKey2: knownvalue.StringExact(acctest.CtValue2), + })), + }, + }, + }, + }) +} + +func testAccCheckTrustStoreDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).CloudFrontClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_cloudfront_trust_store" { + continue + } + + _, err := tfcloudfront.FindTrustStoreByID(ctx, conn, rs.Primary.ID) + + if retry.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("CloudFront Trust Store %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccCheckTrustStoreExists(ctx context.Context, n string, v *cloudfront.GetTrustStoreOutput) 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).CloudFrontClient(ctx) + + resp, err := tfcloudfront.FindTrustStoreByID(ctx, conn, rs.Primary.ID) + + if err != nil { + return err + } + + *v = *resp + + return nil + } +} + +const testAccTrustStoreCertificateContent = `-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE-----` + +func testAccTrustStoreConfig_base(rName string) string { + return fmt.Sprintf(` +data "aws_region" "current" {} + +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} +`, rName) +} + +func testAccTrustStoreConfig_basic(rName, key string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_base(rName), fmt.Sprintf(` +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.id + key = %[2]q + content = <<-EOT +%[3]s +EOT +} + +resource "aws_cloudfront_trust_store" "test" { + name = %[1]q + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + region = data.aws_region.current.name + } + } +} +`, rName, key, testAccTrustStoreCertificateContent)) +} + +func testAccTrustStoreConfig_withS3ObjectVersion(rName, key string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_base(rName), fmt.Sprintf(` +resource "aws_s3_bucket_versioning" "test" { + bucket = aws_s3_bucket.test.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.id + key = %[2]q + content = <<-EOT +%[3]s +EOT + depends_on = [aws_s3_bucket_versioning.test] +} + +resource "aws_cloudfront_trust_store" "test" { + name = %[1]q + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + region = data.aws_region.current.name + version = aws_s3_object.test.version_id + } + } +} +`, rName, key, testAccTrustStoreCertificateContent)) +} + +func testAccTrustStoreConfig_update(rName, key string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_base(rName), fmt.Sprintf(` +resource "aws_s3_bucket_versioning" "test" { + bucket = aws_s3_bucket.test.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.id + key = %[2]q + content = <<-EOT +%[3]s +EOT + depends_on = [aws_s3_bucket_versioning.test] +} + +resource "aws_cloudfront_trust_store" "test" { + name = %[1]q + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + region = data.aws_region.current.name + version = aws_s3_object.test.version_id + } + } +} +`, rName, key, testAccTrustStoreCertificateContent)) +} + +func testAccTrustStoreConfig_tags1(rName, key, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_base(rName), fmt.Sprintf(` +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.id + key = %[2]q + content = <<-EOT +%[3]s +EOT +} + +resource "aws_cloudfront_trust_store" "test" { + name = %[1]q + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + region = data.aws_region.current.name + } + } + + tags = { + %[4]q = %[5]q + } +} +`, rName, key, testAccTrustStoreCertificateContent, tagKey1, tagValue1)) +} + +func testAccTrustStoreConfig_tags2(rName, key, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_base(rName), fmt.Sprintf(` +resource "aws_s3_object" "test" { + bucket = aws_s3_bucket.test.id + key = %[2]q + content = <<-EOT +%[3]s +EOT +} + +resource "aws_cloudfront_trust_store" "test" { + name = %[1]q + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = aws_s3_bucket.test.id + key = aws_s3_object.test.key + region = data.aws_region.current.name + } + } + + tags = { + %[4]q = %[5]q + %[6]q = %[7]q + } +} +`, rName, key, testAccTrustStoreCertificateContent, tagKey1, tagValue1, tagKey2, tagValue2)) +} diff --git a/website/docs/r/cloudfront_trust_store.html.markdown b/website/docs/r/cloudfront_trust_store.html.markdown new file mode 100644 index 00000000000..ef5689045fd --- /dev/null +++ b/website/docs/r/cloudfront_trust_store.html.markdown @@ -0,0 +1,103 @@ +--- +subcategory: "CloudFront" +layout: "aws" +page_title: "AWS: aws_cloudfront_trust_store" +description: |- + Manages an AWS CloudFront Trust Store. +--- + +# Resource: aws_cloudfront_trust_store + +Manages an AWS CloudFront Trust Store. + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_cloudfront_trust_store" "example" { + name = "example-trust-store" + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = "example-bucket" + key = "ca-certificates.pem" + region = "us-east-1" + } + } +} +``` + +### With S3 Object Version + +```terraform +resource "aws_cloudfront_trust_store" "example" { + name = "example-trust-store" + + ca_certificates_bundle_source { + ca_certificates_bundle_s3_location { + bucket = "example-bucket" + key = "ca-certificates.pem" + region = "us-east-1" + version = "abc123" + } + } +} +``` + +## Argument Reference + +The following arguments are required: + +* `name` - (Required) Name of the trust store. Changing this forces a new resource to be created. +* `ca_certificates_bundle_source` - (Required) Configuration block for the CA certificates bundle source. See [`ca_certificates_bundle_source`](#ca_certificates_bundle_source) below. + +The following arguments are optional: + +* `tags` - (Optional) Key-value tags for the place index. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. + +### ca_certificates_bundle_source + +* `ca_certificates_bundle_s3_location` - (Required) Configuration block for the S3 location of the CA certificates bundle. See [`ca_certificates_bundle_s3_location`](#ca_certificates_bundle_s3_location) below. + +### ca_certificates_bundle_s3_location + +* `bucket` - (Required) S3 bucket name containing the CA certificates bundle. +* `key` - (Required) S3 object key for the CA certificates bundle. +* `region` - (Required) AWS region of the S3 bucket. +* `version` - (Optional) S3 object version ID for the CA certificates bundle. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn` - ARN of the trust store. +* `etag` - ETag of the trust store. +* `id` - ID of the trust store. +* `number_of_ca_certificates` - Number of CA certificates in the trust store. +* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +* `create` - (Default `10m`) +* `update` - (Default `10m`) +* `delete` - (Default `10m`) + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import CloudFront Trust Store using the trust store ID. For example: + +```terraform +import { + to = aws_cloudfront_trust_store.example + id = "ts_12abcXYZhA4Q6RS6tuvW5Xy0ZZZ" +} +``` + +Using `terraform import`, import CloudFront Trust Store using the trust store ID. For example: + +```console +% terraform import aws_cloudfront_trust_store.example ts_12abcXYZhA4Q6RS6tuvW5Xy0ZZZ +```