From b2ea441ff5f56af6a0294c91d233ec7a65772784 Mon Sep 17 00:00:00 2001 From: matt-mercer <449892+matt-mercer@users.noreply.github.com> Date: Mon, 27 Nov 2023 23:13:25 +0000 Subject: [PATCH 01/16] elbv2:truststore minimal aws_lb_trust_store support and listener_attributes --- .changelog/34584.txt | 11 + .github/labeler-issue-triage.yml | 2 +- .github/labeler-pr-triage.yml | 1 + .github/workflows/provider.yml | 4 +- internal/service/elbv2/listener.go | 108 +++++ .../service/elbv2/listener_data_source.go | 28 ++ internal/service/elbv2/listener_test.go | 121 ++++++ internal/service/elbv2/service_package_gen.go | 24 ++ internal/service/elbv2/trust_store.go | 402 ++++++++++++++++++ .../service/elbv2/trust_store_data_source.go | 88 ++++ .../elbv2/trust_store_data_source_test.go | 73 ++++ internal/service/elbv2/trust_store_test.go | 271 ++++++++++++ names/names_data.csv | 2 +- website/docs/d/lb_trust_store.html.markdown | 56 +++ website/docs/r/lb_listener.html.markdown | 35 ++ website/docs/r/lb_trust_store.html.markdown | 80 ++++ 16 files changed, 1302 insertions(+), 4 deletions(-) create mode 100644 .changelog/34584.txt create mode 100644 internal/service/elbv2/trust_store.go create mode 100644 internal/service/elbv2/trust_store_data_source.go create mode 100644 internal/service/elbv2/trust_store_data_source_test.go create mode 100644 internal/service/elbv2/trust_store_test.go create mode 100644 website/docs/d/lb_trust_store.html.markdown create mode 100644 website/docs/r/lb_trust_store.html.markdown diff --git a/.changelog/34584.txt b/.changelog/34584.txt new file mode 100644 index 00000000000..db8dae4257f --- /dev/null +++ b/.changelog/34584.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +aws_lb_trust_store +``` + +```release-note:new-data-source +aws_lb_trust_store +``` + +```release-note:enhancement +resource/aws_lb_listener: Add mutual_authentication configuration block +``` \ No newline at end of file diff --git a/.github/labeler-issue-triage.yml b/.github/labeler-issue-triage.yml index a88f3d70529..e6621c7c79f 100644 --- a/.github/labeler-issue-triage.yml +++ b/.github/labeler-issue-triage.yml @@ -252,7 +252,7 @@ service/elastictranscoder: service/elb: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_(app_cookie_stickiness_policy|elb|lb_cookie_stickiness_policy|lb_ssl_negotiation_policy|load_balancer_|proxy_protocol_policy)' service/elbv2: - - '((\*|-)\s*`?|(data|resource)\s+"?)aws_a?lb(\b|_listener|_target_group|s)' + - '((\*|-)\s*`?|(data|resource)\s+"?)aws_a?lb(\b|_listener|_target_group|s|_trust_store)' service/emr: - '((\*|-)\s*`?|(data|resource)\s+"?)aws_emr_' service/emrcontainers: diff --git a/.github/labeler-pr-triage.yml b/.github/labeler-pr-triage.yml index 1cf4d794c6c..443a7680b61 100644 --- a/.github/labeler-pr-triage.yml +++ b/.github/labeler-pr-triage.yml @@ -438,6 +438,7 @@ service/elbv2: - 'website/**/lb_listener*' - 'website/**/lb_target_group*' - 'website/**/lb_hosted*' + - 'website/**/lb_trust_store*' service/emr: - 'internal/service/emr/**/*' - 'website/**/emr_*' diff --git a/.github/workflows/provider.yml b/.github/workflows/provider.yml index 76c1ddc824c..7fb3ac66fac 100644 --- a/.github/workflows/provider.yml +++ b/.github/workflows/provider.yml @@ -292,8 +292,8 @@ jobs: tfproviderdocs check \ -allowed-resource-subcategories-file website/allowed-subcategories.txt \ -enable-contents-check \ - -ignore-file-missing-data-sources aws_alb,aws_alb_listener,aws_alb_target_group,aws_albs \ - -ignore-file-missing-resources aws_alb,aws_alb_listener,aws_alb_listener_certificate,aws_alb_listener_rule,aws_alb_target_group,aws_alb_target_group_attachment \ + -ignore-file-missing-data-sources aws_alb,aws_alb_listener,aws_alb_target_group,aws_alb_trust_store,aws_albs \ + -ignore-file-missing-resources aws_alb,aws_alb_listener,aws_alb_listener_certificate,aws_alb_listener_rule,aws_alb_target_group,aws_alb_target_group_attachment,aws_alb_trust_store \ -provider-source registry.terraform.io/hashicorp/aws \ -providers-schema-json terraform-providers-schema/schema.json \ -require-resource-subcategory \ diff --git a/internal/service/elbv2/listener.go b/internal/service/elbv2/listener.go index 5764f3c4b62..a9d4c2184e6 100644 --- a/internal/service/elbv2/listener.go +++ b/internal/service/elbv2/listener.go @@ -355,6 +355,32 @@ func ResourceListener() *schema.Resource { ForceNew: true, ValidateFunc: verify.ValidARN, }, + "mutual_authentication": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mode": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(MutualAuthenticationModeEnum_Values(), true), + }, + "trust_store_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, + }, + "ignore_client_certificate_expiry": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + }, + }, + }, + "port": { Type: schema.TypeInt, Optional: true, @@ -440,6 +466,10 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in input.SslPolicy = aws.String(sslPolicy.(string)) } + if v, ok := d.GetOk("mutual_authentication"); ok { + input.MutualAuthentication = expandMutualAuthenticationAttributes(v.([]interface{})) + } + output, err := retryListenerCreate(ctx, conn, input) // Some partitions (e.g. ISO) may not support tag-on-create. @@ -549,6 +579,10 @@ func resourceListenerRead(ctx context.Context, d *schema.ResourceData, meta inte return sdkdiag.AppendErrorf(diags, "setting default_action for ELBv2 listener (%s): %s", d.Id(), err) } + if err := d.Set("mutual_authentication", flattenMutualAuthenticationAttributes(listener.MutualAuthentication)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting mutual_authentication for ELBv2 listener (%s): %s", d.Id(), err) + } + return diags } @@ -595,6 +629,14 @@ func resourceListenerUpdate(ctx context.Context, d *schema.ResourceData, meta in } } + if d.HasChange("mutual_authentication") { + var err error + input.MutualAuthentication = expandMutualAuthenticationAttributes(d.Get("mutual_authentication").([]interface{})) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + } + err := retry.RetryContext(ctx, loadBalancerListenerUpdateTimeout, func() *retry.RetryError { _, err := conn.ModifyListenerWithContext(ctx, input) @@ -879,6 +921,30 @@ func expandLbListenerActionForwardConfig(l []interface{}) *elbv2.ForwardActionCo return config } +func expandMutualAuthenticationAttributes(l []interface{}) *elbv2.MutualAuthenticationAttributes { + if len(l) == 0 || l[0] == nil { + return nil + } + + tfMap, ok := l[0].(map[string]interface{}) + if !ok { + return nil + } + + mode := tfMap["mode"].(string) + if mode == MutualAuthenticationOff { + return &elbv2.MutualAuthenticationAttributes{ + Mode: aws.String(mode), + } + } + + return &elbv2.MutualAuthenticationAttributes{ + Mode: aws.String(mode), + TrustStoreArn: aws.String(tfMap["trust_store_arn"].(string)), + IgnoreClientCertificateExpiry: aws.Bool(tfMap["ignore_client_certificate_expiry"].(bool)), + } +} + func expandLbListenerActionForwardConfigTargetGroups(l []interface{}) []*elbv2.TargetGroupTuple { if len(l) == 0 { return nil @@ -966,6 +1032,29 @@ func flattenLbListenerActions(d *schema.ResourceData, Actions []*elbv2.Action) [ return vActions } +func flattenMutualAuthenticationAttributes(description *elbv2.MutualAuthenticationAttributes) []interface{} { + if description == nil { + return []interface{}{} + } + + mode := aws.StringValue(description.Mode) + if mode == MutualAuthenticationOff { + return []interface{}{ + map[string]interface{}{ + "mode": mode, + }, + } + } + + m := map[string]interface{}{ + "mode": aws.StringValue(description.Mode), + "trust_store_arn": aws.StringValue(description.TrustStoreArn), + "ignore_client_certificate_expiry": aws.BoolValue(description.IgnoreClientCertificateExpiry), + } + + return []interface{}{m} +} + func flattenAuthenticateOIDCActionConfig(config *elbv2.AuthenticateOidcActionConfig, clientSecret string) []interface{} { if config == nil { return []interface{}{} @@ -1086,3 +1175,22 @@ func flattenLbListenerActionRedirectConfig(config *elbv2.RedirectActionConfig) [ return []interface{}{m} } + +const ( + // MutualAuthenticationOff is a MutualAuthenticationModeEnum enum value + MutualAuthenticationOff = "off" + + // MutualAuthenticationVerify is a MutualAuthenticationModeEnum enum value + MutualAuthenticationVerify = "verify" + // MutualAuthenticationPassthrough is a MutualAuthenticationModeEnum enum value + MutualAuthenticationPassthrough = "passthrough" +) + +// ProtocolEnum_Values returns all elements of the ProtocolEnum enum +func MutualAuthenticationModeEnum_Values() []string { + return []string{ + MutualAuthenticationOff, + MutualAuthenticationVerify, + MutualAuthenticationPassthrough, + } +} diff --git a/internal/service/elbv2/listener_data_source.go b/internal/service/elbv2/listener_data_source.go index 2aaabd46ace..503ca75bb32 100644 --- a/internal/service/elbv2/listener_data_source.go +++ b/internal/service/elbv2/listener_data_source.go @@ -257,6 +257,30 @@ func DataSourceListener() *schema.Resource { Computed: true, ConflictsWith: []string{"arn"}, }, + "mutual_authentication": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "mode": { + Type: schema.TypeString, + Computed: true, + }, + "trust_store_arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "ignore_client_certificate_expiry": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, "port": { Type: schema.TypeInt, Optional: true, @@ -351,6 +375,10 @@ func dataSourceListenerRead(ctx context.Context, d *schema.ResourceData, meta in return sdkdiag.AppendErrorf(diags, "setting default_action: %s", err) } + if err := d.Set("mutual_authentication", flattenMutualAuthenticationAttributes(listener.MutualAuthentication)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting mutual_authentication: %s", err) + } + tags, err := listTags(ctx, conn, d.Id()) if errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { diff --git a/internal/service/elbv2/listener_test.go b/internal/service/elbv2/listener_test.go index dd476a634b7..5dd56d54d0b 100644 --- a/internal/service/elbv2/listener_test.go +++ b/internal/service/elbv2/listener_test.go @@ -302,6 +302,52 @@ func TestAccELBV2Listener_Protocol_https(t *testing.T) { }) } +func TestAccELBV2Listener_mutualAuthentication(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.Listener + key := acctest.TLSRSAPrivateKeyPEM(t, 2048) + resourceName := "aws_lb_listener.test" + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckListenerDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccListenerConfig_mutualAuthentication(rName, key, certificate), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckListenerExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "mutual_authentication.0.mode", "verify"), + resource.TestCheckResourceAttr(resourceName, "mutual_authentication.0.ignore_client_certificate_expiry", "false"), + resource.TestCheckResourceAttrPair(resourceName, "mutual_authentication.0.trust_store_arn", "aws_lb_trust_store.test", "arn"), + + resource.TestCheckResourceAttrPair(resourceName, "load_balancer_arn", "aws_lb.test", "arn"), + + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "elasticloadbalancing", regexache.MustCompile("listener/.+$")), + resource.TestCheckResourceAttr(resourceName, "protocol", "HTTPS"), + resource.TestCheckResourceAttr(resourceName, "port", "443"), + resource.TestCheckResourceAttr(resourceName, "default_action.#", "1"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.order", "1"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.type", "forward"), + resource.TestCheckResourceAttrPair(resourceName, "default_action.0.target_group_arn", "aws_lb_target_group.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.redirect.#", "0"), + resource.TestCheckResourceAttr(resourceName, "default_action.0.fixed_response.#", "0"), + resource.TestCheckResourceAttrPair(resourceName, "certificate_arn", "aws_iam_server_certificate.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "ssl_policy", "ELBSecurityPolicy-2016-08"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccELBV2Listener_LoadBalancerARN_gatewayLoadBalancer(t *testing.T) { ctx := acctest.Context(t) var conf elbv2.Listener @@ -1210,6 +1256,81 @@ resource "aws_internet_gateway" "test" { `, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key))) } +func testAccListenerConfig_mutualAuthentication(rName string, key, certificate string) string { + return acctest.ConfigCompose( + testAccListenerConfig_base(rName), + testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` +resource "aws_lb_trust_store" "test" { + name = %[1]q + ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket + ca_certificates_bundle_s3_key = aws_s3_object.test.key +} + + +resource "aws_lb_listener" "test" { + load_balancer_arn = aws_lb.test.id + protocol = "HTTPS" + port = "443" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = aws_iam_server_certificate.test.arn + + default_action { + target_group_arn = aws_lb_target_group.test.id + type = "forward" + } + + mutual_authentication { + mode = "verify" + trust_store_arn = aws_lb_trust_store.test.arn + } +} + +resource "aws_lb" "test" { + name = %[1]q + internal = true + security_groups = [aws_security_group.test.id] + subnets = aws_subnet.test[*].id + + idle_timeout = 30 + enable_deletion_protection = false + + tags = { + Name = %[1]q + } +} + +resource "aws_lb_target_group" "test" { + name = %[1]q + port = 8080 + protocol = "HTTP" + vpc_id = aws_vpc.test.id + + health_check { + path = "/health" + interval = 60 + port = 8081 + protocol = "HTTP" + timeout = 3 + healthy_threshold = 3 + unhealthy_threshold = 3 + matcher = "200-299" + } + + tags = { + Name = %[1]q + } +} + +resource "aws_iam_server_certificate" "test" { + name = %[1]q + certificate_body = "%[2]s" + private_key = "%[3]s" +} + + +`, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key))) +} + func testAccListenerConfig_arnGateway(rName string) string { return acctest.ConfigCompose( acctest.ConfigAvailableAZsNoOptIn(), diff --git a/internal/service/elbv2/service_package_gen.go b/internal/service/elbv2/service_package_gen.go index 57e5abc0a71..5032f6fdec0 100644 --- a/internal/service/elbv2/service_package_gen.go +++ b/internal/service/elbv2/service_package_gen.go @@ -37,6 +37,10 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Factory: DataSourceTargetGroup, TypeName: "aws_alb_target_group", }, + { + Factory: DataSourceTrustStore, + TypeName: "aws_alb_trust_store", + }, { Factory: DataSourceLoadBalancer, TypeName: "aws_lb", @@ -53,6 +57,10 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Factory: DataSourceTargetGroup, TypeName: "aws_lb_target_group", }, + { + Factory: DataSourceTrustStore, + TypeName: "aws_lb_trust_store", + }, { Factory: DataSourceLoadBalancers, TypeName: "aws_lbs", @@ -102,6 +110,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceTargetGroupAttachment, TypeName: "aws_alb_target_group_attachment", }, + { + Factory: ResourceTrustStore, + TypeName: "aws_alb_trust_store", + Name: "Trust Store", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "id", + }, + }, { Factory: ResourceLoadBalancer, TypeName: "aws_lb", @@ -142,6 +158,14 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceTargetGroupAttachment, TypeName: "aws_lb_target_group_attachment", }, + { + Factory: ResourceTrustStore, + TypeName: "aws_lb_trust_store", + Name: "Trust Store", + Tags: &types.ServicePackageResourceTags{ + IdentifierAttribute: "id", + }, + }, } } diff --git a/internal/service/elbv2/trust_store.go b/internal/service/elbv2/trust_store.go new file mode 100644 index 00000000000..2d6a3ea1609 --- /dev/null +++ b/internal/service/elbv2/trust_store.go @@ -0,0 +1,402 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2 + +import ( + "context" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @SDKResource("aws_lb_trust_store", name="Trust Store") +// @SDKResource("aws_alb_trust_store", name="Trust Store") +// @Tags(identifierAttribute="id") +func ResourceTrustStore() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTrustStoreCreate, + ReadWithoutTimeout: resourceTrustStoreRead, + UpdateWithoutTimeout: resourceTrustStoreUpdate, + DeleteWithoutTimeout: resourceTrustStoreDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(10 * time.Minute), + }, + + CustomizeDiff: customdiff.Sequence( + verify.SetTagsDiff, + ), + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "arn_suffix": { + Type: schema.TypeString, + Computed: true, + }, + "ca_certificates_bundle_s3_bucket": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "ca_certificates_bundle_s3_key": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.NoZeroValues, + }, + "ca_certificates_bundle_s3_object_version": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, + ValidateFunc: validName, + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + ValidateFunc: validNamePrefix, + }, + names.AttrTags: tftags.TagsSchema(), + names.AttrTagsAll: tftags.TagsSchemaComputed(), + }, + } +} + +func resourceTrustStoreCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + name := create.NewNameGenerator( + create.WithConfiguredName(d.Get("name").(string)), + create.WithConfiguredPrefix(d.Get("name_prefix").(string)), + create.WithDefaultPrefix("tf-"), + ).Generate() + exist, err := FindTrustStoreByName(ctx, conn, name) + + if err != nil && !tfresource.NotFound(err) { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store (%s): %s", name, err) + } + + if exist != nil { + return sdkdiag.AppendErrorf(diags, "ELBv2 Trust Store (%s) already exists", name) + } + + input := &elbv2.CreateTrustStoreInput{ + Name: aws.String(name), + Tags: getTagsIn(ctx), + CaCertificatesBundleS3Bucket: aws.String(d.Get("ca_certificates_bundle_s3_bucket").(string)), + CaCertificatesBundleS3Key: aws.String(d.Get("ca_certificates_bundle_s3_key").(string)), + } + + if d.Get("ca_certificates_bundle_s3_object_version").(string) != "" { + input.CaCertificatesBundleS3ObjectVersion = aws.String(d.Get("ca_certificates_bundle_s3_object_version").(string)) + } + + output, err := conn.CreateTrustStoreWithContext(ctx, input) + + // Some partitions (e.g. ISO) may not support tag-on-create. + if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + input.Tags = nil + + output, err = conn.CreateTrustStoreWithContext(ctx, input) + } + + // Tags are not supported on creation with some protocol types(i.e. GENEVE) + // Retry creation without tags + if input.Tags != nil && tfawserr.ErrMessageContains(err, ErrValidationError, TagsOnCreationErrMessage) { + input.Tags = nil + + output, err = conn.CreateTrustStoreWithContext(ctx, input) + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store (%s): %s", name, err) + } + + if len(output.TrustStores) == 0 { + return sdkdiag.AppendErrorf(diags, "creating Trust Store: no trust stores returned in response") + } + + d.SetId(aws.StringValue(output.TrustStores[0].TrustStoreArn)) + + _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { + return FindTrustStoreByARN(ctx, conn, d.Id()) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Trust Store (%s) create: %s", d.Id(), err) + } + + // For partitions not supporting tag-on-create, attempt tag after create. + if tags := getTagsIn(ctx); input.Tags == nil && len(tags) > 0 { + err := createTags(ctx, conn, d.Id(), tags) + + // If default tags only, continue. Otherwise, error. + if v, ok := d.GetOk(names.AttrTags); (!ok || len(v.(map[string]interface{})) == 0) && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { + return append(diags, resourceTrustStoreRead(ctx, d, meta)...) + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELBv2 Trust Store (%s) tags: %s", d.Id(), err) + } + } + + return append(diags, resourceTrustStoreRead(ctx, d, meta)...) +} + +func resourceTrustStoreRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + outputRaw, err := tfresource.RetryWhenNewResourceNotFound(ctx, propagationTimeout, func() (interface{}, error) { + return FindTrustStoreByARN(ctx, conn, d.Id()) + }, d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELBv2 Trust Store %s not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store (%s): %s", d.Id(), err) + } + + trustStore := outputRaw.(*elbv2.TrustStore) + + d.Set("name", trustStore.Name) + d.Set("arn", trustStore.TrustStoreArn) + + return diags +} + +func resourceTrustStoreUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + if d.HasChanges("ca_certificates_bundle_s3_bucket", "ca_certificates_bundle_s3_key", "ca_certificates_bundle_s3_object_version", "tags") { + var params = &elbv2.ModifyTrustStoreInput{ + TrustStoreArn: aws.String(d.Id()), + CaCertificatesBundleS3Bucket: aws.String(d.Get("ca_certificates_bundle_s3_bucket").(string)), + CaCertificatesBundleS3Key: aws.String(d.Get("ca_certificates_bundle_s3_key").(string)), + } + + if d.Get("ca_certificates_bundle_s3_object_version").(string) != "" { + params.CaCertificatesBundleS3ObjectVersion = aws.String(d.Get("ca_certificates_bundle_s3_object_version").(string)) + } + + _, err := conn.ModifyTrustStoreWithContext(ctx, params) + if err != nil { + return sdkdiag.AppendErrorf(diags, "modifying Trust Store: %s", err) + } + } + + return append(diags, resourceTrustStoreRead(ctx, d, meta)...) +} + +func resourceTrustStoreDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + const ( + trustStoreDeleteTimeout = 2 * time.Minute + ) + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + err := waitForNoTrustStoreAssociations(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for Trust Store Associations (%s) to be removed: %s", d.Get("name").(string), err) + } + + input := &elbv2.DeleteTrustStoreInput{ + TrustStoreArn: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting Trust Store (%s): %s", d.Id(), input) + err = retry.RetryContext(ctx, trustStoreDeleteTimeout, func() *retry.RetryError { + _, err := conn.DeleteTrustStoreWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, "TrustStoreInUse", "is currently in use by a listener") { + return retry.RetryableError(err) + } + + if err != nil { + return retry.NonRetryableError(err) + } + + return nil + }) + + if tfresource.TimedOut(err) { + _, err = conn.DeleteTrustStoreWithContext(ctx, input) + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Trust Store: %s", err) + } + + return diags +} + +func FindTrustStoreByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*elbv2.TrustStore, error) { + input := &elbv2.DescribeTrustStoresInput{ + TrustStoreArns: aws.StringSlice([]string{arn}), + } + + output, err := FindTrustStore(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.TrustStoreArn) != arn { + return nil, &retry.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTrustStoreByName(ctx context.Context, conn *elbv2.ELBV2, name string) (*elbv2.TrustStore, error) { + input := &elbv2.DescribeTrustStoresInput{ + Names: aws.StringSlice([]string{name}), + } + + output, err := FindTrustStore(ctx, conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.Name) != name { + return nil, &retry.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTrustStores(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) ([]*elbv2.TrustStore, error) { + var output []*elbv2.TrustStore + + err := conn.DescribeTrustStoresPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoresOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TrustStores { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTrustStore(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) (*elbv2.TrustStore, error) { + output, err := FindTrustStores(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func waitForNoTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, arn string, timeout time.Duration) error { + input := &elbv2.DescribeTrustStoreAssociationsInput{ + TrustStoreArn: aws.String(arn), + } + + _, err := tfresource.RetryUntilEqual(ctx, timeout, 0, func() (int, error) { + return GetRemainingTrustStoreAssociations(ctx, conn, input) + }) + + return err +} + +func GetRemainingTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoreAssociationsInput) (int, error) { + var output []*elbv2.TrustStoreAssociation + + err := conn.DescribeTrustStoreAssociationsPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoreAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TrustStoreAssociations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + return -1, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return -1, err + } + + return len(output), nil +} diff --git a/internal/service/elbv2/trust_store_data_source.go b/internal/service/elbv2/trust_store_data_source.go new file mode 100644 index 00000000000..107f5377f22 --- /dev/null +++ b/internal/service/elbv2/trust_store_data_source.go @@ -0,0 +1,88 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2 + +import ( + "context" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" +) + +// @SDKDataSource("aws_lb_trust_store") +// @SDKDataSource("aws_alb_trust_store") +func DataSourceTrustStore() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceTrustStoreRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceTrustStoreRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + input := &elbv2.DescribeTrustStoresInput{} + + if v, ok := d.GetOk("arn"); ok { + input.TrustStoreArns = aws.StringSlice([]string{v.(string)}) + } else if v, ok := d.GetOk("name"); ok { + input.Names = aws.StringSlice([]string{v.(string)}) + } + + var results []*elbv2.TrustStore + + err := conn.DescribeTrustStoresPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoresOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, l := range page.TrustStores { + if l == nil { + continue + } + + results = append(results, l) + } + + return !lastPage + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading Listener: %s", err) + } + + if len(results) != 1 { + return sdkdiag.AppendErrorf(diags, "Search returned %d results, please revise so only one is returned", len(results)) + } + + trustStore := results[0] + + d.SetId(aws.StringValue(trustStore.TrustStoreArn)) + d.Set("name", trustStore.Name) + d.Set("arn", trustStore.TrustStoreArn) + + return diags +} diff --git a/internal/service/elbv2/trust_store_data_source_test.go b/internal/service/elbv2/trust_store_data_source_test.go new file mode 100644 index 00000000000..1e515938cbf --- /dev/null +++ b/internal/service/elbv2/trust_store_data_source_test.go @@ -0,0 +1,73 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2_test + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/elbv2" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + _ "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" +) + +func TestAccELBV2TrustStoreDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + datasourceNameByName := "data.aws_lb_trust_store.named" + datasourceNameByArn := "data.aws_lb_trust_store.with_arn" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + + { + Config: testAccTrustStoreDataSourceConfig_withName(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceNameByName, "name", rName), + resource.TestCheckResourceAttrSet(datasourceNameByName, "arn"), + ), + }, + { + Config: testAccTrustStoreDataSourceConfig_withARN(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceNameByArn, "name", rName), + resource.TestCheckResourceAttrSet(datasourceNameByArn, "arn"), + ), + }, + }, + }) +} + +func testAccTrustStoreDataSourceConfig_base(rName string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` +resource "aws_lb_trust_store" "test" { + name = %[1]q + ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket + ca_certificates_bundle_s3_key = aws_s3_object.test.key +} +`, rName)) +} + +func testAccTrustStoreDataSourceConfig_withName(rName string) string { + return acctest.ConfigCompose(testAccTrustStoreDataSourceConfig_base(rName), fmt.Sprintf(` + +data "aws_lb_trust_store" "named" { + name = %[1]q + depends_on = [aws_lb_trust_store.test] +} +`, rName)) +} + +func testAccTrustStoreDataSourceConfig_withARN(rName string) string { + return acctest.ConfigCompose(testAccTrustStoreDataSourceConfig_base(rName), ` +data "aws_lb_trust_store" "with_arn" { + arn = aws_lb_trust_store.test.arn + depends_on = [aws_lb_trust_store.test] +} +`) +} diff --git a/internal/service/elbv2/trust_store_test.go b/internal/service/elbv2/trust_store_test.go new file mode 100644 index 00000000000..e9c35565c4b --- /dev/null +++ b/internal/service/elbv2/trust_store_test.go @@ -0,0 +1,271 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + 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" + tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" +) + +func TestAccELBV2TrustStore_basic(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.TrustStore + resourceName := "aws_lb_trust_store.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "elasticloadbalancing", regexache.MustCompile("truststore/.+$")), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestAccELBV2TrustStore_tags(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.TrustStore + resourceName := "aws_lb_trust_store.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + { + Config: testAccTrustStoreConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccTrustStoreConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckTrustStoreExists(ctx context.Context, n string, res *elbv2.TrustStore) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("No Trust Store ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) + + trustStore, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) + + if err != nil { + return fmt.Errorf("reading ELBv2 Trust Store (%s): %w", rs.Primary.ID, err) + } + + if trustStore == nil { + return fmt.Errorf("ELBv2 Trust Store (%s) not found", rs.Primary.ID) + } + + *res = *trustStore + return nil + } +} + +func testAccCheckTrustStoreDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lb_trust_store" { + continue + } + + trustStore, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("reading ELBv2 Trust Store (%s): %w", rs.Primary.ID, err) + } + + if trustStore == nil { + continue + } + + return fmt.Errorf("ELBv2 Trust Store %q still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccTrustStoreConfig_S3BucketCA(rName string) string { + return acctest.ConfigCompose(fmt.Sprintf(` +resource "aws_s3_bucket" "test" { + bucket = %[1]q + force_destroy = true +} + +resource "aws_s3_bucket_ownership_controls" "test" { + bucket = aws_s3_bucket.test.id + + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +resource "aws_s3_bucket_public_access_block" "test" { + bucket = aws_s3_bucket.test.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_object" "test" { + + bucket = aws_s3_bucket.test.bucket + key = "%[1]s.pem" + content = < **Note:** `aws_alb_trust_store` is known as `aws_lb_trust_store`. The functionality is identical. + +Provides information about a Load Balancer Trust Store. + +This data source can prove useful when a module accepts an LB Trust Store as an +input variable and needs to know its attributes. It can also be used to get the ARN of +an LB Trust Store for use in other resources, given LB Trust Store name. + +## Example Usage + +```terraform +variable "lb_ts_arn" { + type = string + default = "" +} + +variable "lb_ts_name" { + type = string + default = "" +} + +data "aws_lb_trust_store" "test" { + arn = var.lb_ts_arn + name = var.lb_ts_name +} +``` + +## Argument Reference + +This data source supports the following arguments: + +* `arn` - (Optional) Full ARN of the trust store. +* `name` - (Optional) Unique name of the trust store. + +~> **NOTE:** When both `arn` and `name` are specified, `arn` takes precedence. + +## Attribute Reference + +See the [LB Trust Store Resource](/docs/providers/aws/r/lb_trust_store.html) for details +on the returned attributes - they are identical. + +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +- `read` - (Default `20m`) diff --git a/website/docs/r/lb_listener.html.markdown b/website/docs/r/lb_listener.html.markdown index 34220c6c6b6..843405e7236 100644 --- a/website/docs/r/lb_listener.html.markdown +++ b/website/docs/r/lb_listener.html.markdown @@ -219,6 +219,35 @@ resource "aws_lb_listener" "example" { } ``` +### Mutual TLS Authentication + +```terraform + +resource "aws_lb" "example" { + load_balancer_type = "application" + + # ... +} + +resource "aws_lb_target_group" "example" { + # ... +} + +resource "aws_lb_listener" "example" { + load_balancer_arn = aws_lb.example.id + + default_action { + target_group_arn = aws_lb_target_group.example.id + type = "forward" + } + + mutual_authentication = { + mode = "verify" + trust_store_arn = "..." + } +} +``` + ## Argument Reference The following arguments are required: @@ -314,6 +343,12 @@ The following arguments are optional: * `stickiness` - (Optional) Configuration block for target group stickiness for the rule. Detailed below. +### `mutual_authentication` + +* `mode` - (Required) Valid values are `off`, `verify` and `passthrough`. +* `trust_store_arn` - (Required) ARN of the elbv2 Trust Store. +* `ignore_client_certificate_expiry` - (Optional) Whether client certificate expiry is ignored. Default is `false`. + ##### target_group The following arguments are required: diff --git a/website/docs/r/lb_trust_store.html.markdown b/website/docs/r/lb_trust_store.html.markdown new file mode 100644 index 00000000000..966ff84e48c --- /dev/null +++ b/website/docs/r/lb_trust_store.html.markdown @@ -0,0 +1,80 @@ +--- +subcategory: "ELB (Elastic Load Balancing)" +layout: "aws" +page_title: "AWS: aws_alb_trust_store" +description: |- + Provides a Trust Store resource for use with Load Balancers. +--- + +# Resource: aws_lb_trust_store + +Provides a ELBv2 Trust Store for use with Application Load Balancer Listener resources. + +~> **Note:** `aws_alb_trust_store` is known as `aws_lb_trust_store`. The functionality is identical. + +## Example Usage + +### Trust Store Load Balancer Listener + +```terraform +resource "aws_lb_trust_store" "test" { + name = "tf-example-lb-ts" + + ca_certificates_bundle_s3_bucket = "..." + ca_certificates_bundle_s3_key = "..." + +} + +resource "aws_lb_listener" "example" { + load_balancer_arn = aws_lb.example.id + + default_action { + target_group_arn = aws_lb_target_group.example.id + type = "forward" + } + + mutual_authentication = { + mode = "verify" + trust_store_arn = aws_lb_trust_store.test.arn + } +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `ca_certificates_bundle_s3_bucket` - (Required) S3 Bucket name holding the client certificate CA bundle. +* `ca_certificates_bundle_s3_key` - (Required) S3 Bucket name holding the client certificate CA bundle. +* `ca_certificates_bundle_s3_object_version` - (Optional) Version Id of CA bundle S3 bucket object, if versioned, defaults to latest if omitted. + +* `name_prefix` - (Optional, Forces new resource) Creates a unique name beginning with the specified prefix. Conflicts with `name`. Cannot be longer than 6 characters. +* `name` - (Optional, Forces new resource) Name of the Trust Store. If omitted, Terraform will assign a random, unique name. This name must be unique per region per account, can have a maximum of 32 characters, must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen. +* `tags` - (Optional) Map of tags to assign to the resource. 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. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `arn_suffix` - ARN suffix for use with CloudWatch Metrics. +* `arn` - ARN of the Trust Store (matches `id`). +* `id` - ARN of the Trust Store (matches `arn`). +* `name` - Name of 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). + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Trust Stores using their ARN. For example: + +```terraform +import { + to = aws_lb_trust_store.example + id = "arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314" +} +``` + +Using `terraform import`, import Target Groups using their ARN. For example: + +```console +% terraform import aws_lb_trust_store.example arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314 +``` From f17339e3cf53ac9fd6fcb4923a79b1424030e6c6 Mon Sep 17 00:00:00 2001 From: matt-mercer <449892+matt-mercer@users.noreply.github.com> Date: Wed, 29 Nov 2023 23:36:42 +0000 Subject: [PATCH 02/16] f:elbv2: new aws_lb_trust_store_revocation resource --- .changelog/34584.txt | 4 + .github/workflows/provider.yml | 4 +- internal/service/elbv2/service_package_gen.go | 10 + .../service/elbv2/trust_store_revocation.go | 296 ++++++++++++++++++ .../elbv2/trust_store_revocation_test.go | 155 +++++++++ .../r/lb_trust_store_revocation.html.markdown | 69 ++++ 6 files changed, 536 insertions(+), 2 deletions(-) create mode 100644 internal/service/elbv2/trust_store_revocation.go create mode 100644 internal/service/elbv2/trust_store_revocation_test.go create mode 100644 website/docs/r/lb_trust_store_revocation.html.markdown diff --git a/.changelog/34584.txt b/.changelog/34584.txt index db8dae4257f..28a0e49ec36 100644 --- a/.changelog/34584.txt +++ b/.changelog/34584.txt @@ -2,6 +2,10 @@ aws_lb_trust_store ``` +```release-note:new-resource +aws_lb_trust_store_revocation +``` + ```release-note:new-data-source aws_lb_trust_store ``` diff --git a/.github/workflows/provider.yml b/.github/workflows/provider.yml index 7fb3ac66fac..97f8f94fb84 100644 --- a/.github/workflows/provider.yml +++ b/.github/workflows/provider.yml @@ -292,8 +292,8 @@ jobs: tfproviderdocs check \ -allowed-resource-subcategories-file website/allowed-subcategories.txt \ -enable-contents-check \ - -ignore-file-missing-data-sources aws_alb,aws_alb_listener,aws_alb_target_group,aws_alb_trust_store,aws_albs \ - -ignore-file-missing-resources aws_alb,aws_alb_listener,aws_alb_listener_certificate,aws_alb_listener_rule,aws_alb_target_group,aws_alb_target_group_attachment,aws_alb_trust_store \ + -ignore-file-missing-data-sources aws_alb,aws_alb_listener,aws_alb_target_group,aws_alb_trust_store,aws_alb_trust_store_revocation,aws_albs \ + -ignore-file-missing-resources aws_alb,aws_alb_listener,aws_alb_listener_certificate,aws_alb_listener_rule,aws_alb_target_group,aws_alb_target_group_attachment,aws_alb_trust_store,aws_alb_trust_store_revocation \ -provider-source registry.terraform.io/hashicorp/aws \ -providers-schema-json terraform-providers-schema/schema.json \ -require-resource-subcategory \ diff --git a/internal/service/elbv2/service_package_gen.go b/internal/service/elbv2/service_package_gen.go index 5032f6fdec0..30e9e51848e 100644 --- a/internal/service/elbv2/service_package_gen.go +++ b/internal/service/elbv2/service_package_gen.go @@ -118,6 +118,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "id", }, }, + { + Factory: ResourceTrustStoreRevocation, + TypeName: "aws_alb_trust_store_revocation", + Name: "Trust Store Revocation", + }, { Factory: ResourceLoadBalancer, TypeName: "aws_lb", @@ -166,6 +171,11 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka IdentifierAttribute: "id", }, }, + { + Factory: ResourceTrustStoreRevocation, + TypeName: "aws_lb_trust_store_revocation", + Name: "Trust Store Revocation", + }, } } diff --git a/internal/service/elbv2/trust_store_revocation.go b/internal/service/elbv2/trust_store_revocation.go new file mode 100644 index 00000000000..ef9621a1beb --- /dev/null +++ b/internal/service/elbv2/trust_store_revocation.go @@ -0,0 +1,296 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2 + +import ( + "context" + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/internal/verify" +) + +// @SDKResource("aws_lb_trust_store_revocation", name="Trust Store Revocation") +// @SDKResource("aws_alb_trust_store_revocation", name="Trust Store Revocation") +func ResourceTrustStoreRevocation() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceTrustStoreRevocationCreate, + ReadWithoutTimeout: resourceTrustStoreRevocationRead, + UpdateWithoutTimeout: nil, + DeleteWithoutTimeout: resourceTrustStoreRevocationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: resourceTrustStoreRevocationImport, + }, + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "trust_store_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, + }, + "revocations_s3_bucket": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "revocations_s3_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "revocations_s3_object_version": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.NoZeroValues, + }, + "revocation_id": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func resourceTrustStoreRevocationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + trustStoreARN := d.Get("trust_store_arn").(string) + + _, err := FindTrustStoreByARN(ctx, conn, trustStoreARN) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store Revocations, Trust Store not found: %s: %s", trustStoreARN, err) + } + + input := &elbv2.AddTrustStoreRevocationsInput{ + TrustStoreArn: aws.String(trustStoreARN), + RevocationContents: make([]*elbv2.RevocationContent, 1), + } + + s3Bucket := d.Get("revocations_s3_bucket").(string) + s3Key := d.Get("revocations_s3_key").(string) + + input.RevocationContents[0] = &elbv2.RevocationContent{ + S3Bucket: aws.String(s3Bucket), + S3Key: aws.String(s3Key), + } + + if d.Get("revocations_s3_object_version").(string) != "" { + input.RevocationContents[0].S3ObjectVersion = aws.String(d.Get("revocations_s3_object_version").(string)) + } + + output, err := conn.AddTrustStoreRevocationsWithContext(ctx, input) + + if err != nil { + sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store Revocations from %s s3://%s/%s: %s", trustStoreARN, s3Bucket, s3Key, err) + } + + if len(output.TrustStoreRevocations) == 0 { + return sdkdiag.AppendErrorf(diags, "creatingTrust Store Revocations: no revocations returned in response") + } + + revocationID := aws.Int64Value(output.TrustStoreRevocations[0].RevocationId) + d.SetId(TrustStoreRevocationCreateID(aws.StringValue(output.TrustStoreRevocations[0].TrustStoreArn), revocationID)) + + d.Set("revocation_id", revocationID) + + _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { + return FindTrustStoreRevocation(ctx, conn, d.Id()) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Trust Store Revocation (%s) create: %s", d.Id(), err) + } + + return append(diags, resourceTrustStoreRevocationRead(ctx, d, meta)...) +} + +func resourceTrustStoreRevocationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + _, err := tfresource.RetryWhenNewResourceNotFound(ctx, propagationTimeout, func() (interface{}, error) { + return FindTrustStoreRevocation(ctx, conn, d.Id()) + }, d.IsNewResource()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELBv2 Trust Store Revocation %s not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store Revocation (%s): %s", d.Id(), err) + } + + return diags +} + +func resourceTrustStoreRevocationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + + const ( + trustStoreDeleteTimeout = 2 * time.Minute + ) + + parsed, err := parseTrustStoreRevocationID(d.Id()) + + if err != nil { + log.Printf("[DEBUG] error parsing Trust Store Revocation Id: %s", d.Id()) + return sdkdiag.AppendErrorf(diags, "deleting Trust Store: %s", err) + } + + input := &elbv2.RemoveTrustStoreRevocationsInput{ + TrustStoreArn: aws.String(parsed.TrustStoreARN), + RevocationIds: make([]*int64, 1), + } + input.RevocationIds[0] = aws.Int64(parsed.RevocationID) + + log.Printf("[DEBUG] Deleting Trust Store Revocation (%s): %s", d.Id(), input) + err = retry.RetryContext(ctx, trustStoreDeleteTimeout, func() *retry.RetryError { + _, err := conn.RemoveTrustStoreRevocationsWithContext(ctx, input) + + if err != nil { + return retry.NonRetryableError(err) + } + + return nil + }) + + if tfresource.TimedOut(err) { + _, err = conn.RemoveTrustStoreRevocationsWithContext(ctx, input) + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Trust Store Revocation: %s", err) + } + + return diags +} + +const trustStoreRevocationIDSeparator = "_" + +func parseTrustStoreRevocationID(id string) (*TrustStoreRevocationID, error) { + invalidIDError := func(msg string) error { + return fmt.Errorf("unexpected format for ID (%q), expected TRUSTSTOREARN_REVOCATIONID: %s", id, msg) + } + + parts := strings.Split(id, trustStoreRevocationIDSeparator) + + if len(parts) != 2 { + return nil, invalidIDError("id should have two parts") + } + + revocationID, err := strconv.ParseInt(parts[1], 10, 64) + + if err != nil { + return nil, invalidIDError("failed to parse revocationID") + } + + result := &TrustStoreRevocationID{ + TrustStoreARN: parts[0], + RevocationID: revocationID, + } + + return result, nil +} + +func resourceTrustStoreRevocationImport(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { + parsed, err := parseTrustStoreRevocationID(d.Id()) + if err != nil { + return nil, err + } + + d.Set("trust_store_arn", parsed.TrustStoreARN) + d.Set("revocation_id", parsed.RevocationID) + + return []*schema.ResourceData{d}, nil +} + +type TrustStoreRevocationID struct { + TrustStoreARN string + RevocationID int64 +} + +func FindTrustStoreRevocation(ctx context.Context, conn *elbv2.ELBV2, id string) (*elbv2.DescribeTrustStoreRevocation, error) { + parsed, err := parseTrustStoreRevocationID(id) + if err != nil { + log.Printf("[DEBUG] error parsing Trust Store Revocation Id: %s", id) + return nil, err + } + + input := &elbv2.DescribeTrustStoreRevocationsInput{ + TrustStoreArn: aws.String(parsed.TrustStoreARN), + } + var matched []*elbv2.DescribeTrustStoreRevocation + + err = conn.DescribeTrustStoreRevocationsPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoreRevocationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TrustStoreRevocations { + if v != nil && aws.Int64Value(v.RevocationId) == parsed.RevocationID { + matched = append(matched, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + count := len(matched) + + if count == 0 { + return nil, nil + } + + if count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return matched[0], nil +} + +func TrustStoreRevocationCreateID(trustStoreARN string, revocationID int64) string { + return fmt.Sprintf( + "%s%s%d", + trustStoreARN, + trustStoreRevocationIDSeparator, + revocationID, + ) +} diff --git a/internal/service/elbv2/trust_store_revocation_test.go b/internal/service/elbv2/trust_store_revocation_test.go new file mode 100644 index 00000000000..2348c6edc3f --- /dev/null +++ b/internal/service/elbv2/trust_store_revocation_test.go @@ -0,0 +1,155 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package elbv2_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + 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" + tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" +) + +func TestAccELBV2TrustStoreRevocation_basic(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.DescribeTrustStoreRevocation + resourceName := "aws_lb_trust_store_revocation.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreRevocationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreRevocationConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreRevocationExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttrSet(resourceName, "trust_store_arn"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func testAccCheckTrustStoreRevocationExists(ctx context.Context, n string, res *elbv2.DescribeTrustStoreRevocation) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return errors.New("No Trust Store Revocation ID is set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) + + revocation, err := tfelbv2.FindTrustStoreRevocation(ctx, conn, rs.Primary.ID) + + if err != nil { + return fmt.Errorf("reading ELBv2 Trust Store Revocation (%s): %w", rs.Primary.ID, err) + } + + if revocation == nil { + return fmt.Errorf("ELBv2 Trust Store Revocation (%s) not found", rs.Primary.ID) + } + + *res = *revocation + return nil + } +} + +func testAccCheckTrustStoreRevocationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lb_trust_store_revocation" { + continue + } + + revocation, err := tfelbv2.FindTrustStoreRevocation(ctx, conn, rs.Primary.ID) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + continue + } + + if err != nil { + return fmt.Errorf("reading ELBv2 Trust Store Revocation (%s): %w", rs.Primary.ID, err) + } + + if revocation == nil { + continue + } + + return fmt.Errorf("ELBv2 Trust Store Revocation %q still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccTrustStoreRevocationConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` +resource "aws_lb_trust_store" "test" { + name = %[1]q + ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket + ca_certificates_bundle_s3_key = aws_s3_object.test.key +} + + +resource "aws_s3_object" "crl" { + + bucket = aws_s3_bucket.test.bucket + key = "%[1]s-crl.pem" + content = < **Note:** `aws_alb_trust_store_revocation` is known as `aws_lb_trust_store_revocation`. The functionality is identical. + +## Example Usage + +### Trust Store With Revocations + +```terraform +resource "aws_lb_trust_store" "test" { + name = "tf-example-lb-ts" + + ca_certificates_bundle_s3_bucket = "..." + ca_certificates_bundle_s3_key = "..." + +} + +resource "aws_lb_trust_store_revocation" "test" { + trust_store_arn = aws_lb_trust_store.test.arn + + revocations_s3_bucket = "..." + revocations_s3_key = "..." + +} + +``` + +## Argument Reference + +This resource supports the following arguments: + +* `trust_store_arn` - (Required) Trust Store ARN. +* `revocations_s3_bucket` - (Required) S3 Bucket name holding the client certificate CA bundle. +* `revocations_s3_key` - (Required) S3 Bucket name holding the client certificate CA bundle. +* `revocations_s3_object_version` - (Optional) Version Id of CA bundle S3 bucket object, if versioned, defaults to latest if omitted. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `revocation_id` - AWS assigned RevocationId, (number). +* `id` - "combination of the Trust Store ARN and RevocationId `${trust_store_arn}_{revocation_id}`" + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Trust Stores using their ARN. For example: + +```terraform +import { + to = aws_lb_trust_store_revocation.example + id = "arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314_6" +} +``` + +Using `terraform import`, import Target Groups using their ARN. For example: + +```console +% terraform import aws_lb_trust_store_revocation.example arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314_6 +``` From c051e33f4dcf7a12d1b7ae460f438343b8eb5561 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 08:06:22 -0500 Subject: [PATCH 03/16] Tweak CHANGELOG entries. --- .changelog/34584.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.changelog/34584.txt b/.changelog/34584.txt index 28a0e49ec36..d64fa966924 100644 --- a/.changelog/34584.txt +++ b/.changelog/34584.txt @@ -11,5 +11,9 @@ aws_lb_trust_store ``` ```release-note:enhancement -resource/aws_lb_listener: Add mutual_authentication configuration block +resource/aws_lb_listener: Add `mutual_authentication` configuration block +``` + +```release-note:enhancement +data-source/aws_lb_listener: Add `mutual_authentication` attribute ``` \ No newline at end of file From f7de67fd5eca8dcc433551f8202f5507cf7eade3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 08:20:38 -0500 Subject: [PATCH 04/16] elbv2: Add 'findListeners'. --- internal/service/elbv2/find.go | 40 ------ internal/service/elbv2/listener.go | 170 +++++++++++++----------- internal/service/elbv2/listener_test.go | 52 ++------ 3 files changed, 101 insertions(+), 161 deletions(-) delete mode 100644 internal/service/elbv2/find.go diff --git a/internal/service/elbv2/find.go b/internal/service/elbv2/find.go deleted file mode 100644 index fa1ecb3814b..00000000000 --- a/internal/service/elbv2/find.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package elbv2 - -import ( - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/elbv2" -) - -func FindListenerByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*elbv2.Listener, error) { - input := &elbv2.DescribeListenersInput{ - ListenerArns: aws.StringSlice([]string{arn}), - } - - var result *elbv2.Listener - - err := conn.DescribeListenersPagesWithContext(ctx, input, func(page *elbv2.DescribeListenersOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, l := range page.Listeners { - if l == nil { - continue - } - - if aws.StringValue(l.ListenerArn) == arn { - result = l - return false - } - } - - return !lastPage - }) - - return result, err -} diff --git a/internal/service/elbv2/listener.go b/internal/service/elbv2/listener.go index a9d4c2184e6..55e389d2bd1 100644 --- a/internal/service/elbv2/listener.go +++ b/internal/service/elbv2/listener.go @@ -6,7 +6,6 @@ package elbv2 import ( "context" "errors" - "fmt" "log" "sort" "strconv" @@ -26,6 +25,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" @@ -431,16 +431,14 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in Tags: getTagsIn(ctx), } - if alpnPolicy, ok := d.GetOk("alpn_policy"); ok { - input.AlpnPolicy = make([]*string, 1) - input.AlpnPolicy[0] = aws.String(alpnPolicy.(string)) + if v, ok := d.GetOk("alpn_policy"); ok { + input.AlpnPolicy = aws.StringSlice([]string{v.(string)}) } - if certificateArn, ok := d.GetOk("certificate_arn"); ok { - input.Certificates = make([]*elbv2.Certificate, 1) - input.Certificates[0] = &elbv2.Certificate{ - CertificateArn: aws.String(certificateArn.(string)), - } + if v, ok := d.GetOk("certificate_arn"); ok { + input.Certificates = []*elbv2.Certificate{{ + CertificateArn: aws.String(v.(string)), + }} } if v, ok := d.GetOk("default_action"); ok && len(v.([]interface{})) > 0 { @@ -451,6 +449,10 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in } } + if v, ok := d.GetOk("mutual_authentication"); ok { + input.MutualAuthentication = expandMutualAuthenticationAttributes(v.([]interface{})) + } + if v, ok := d.GetOk("port"); ok { input.Port = aws.Int64(int64(v.(int))) } @@ -458,16 +460,12 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in if v, ok := d.GetOk("protocol"); ok { input.Protocol = aws.String(v.(string)) } else if strings.Contains(lbARN, "loadbalancer/app/") { - // Keep previous default of HTTP for Application Load Balancers + // Keep previous default of HTTP for Application Load Balancers. input.Protocol = aws.String(elbv2.ProtocolEnumHttp) } - if sslPolicy, ok := d.GetOk("ssl_policy"); ok { - input.SslPolicy = aws.String(sslPolicy.(string)) - } - - if v, ok := d.GetOk("mutual_authentication"); ok { - input.MutualAuthentication = expandMutualAuthenticationAttributes(v.([]interface{})) + if v, ok := d.GetOk("ssl_policy"); ok { + input.SslPolicy = aws.String(v.(string)) } output, err := retryListenerCreate(ctx, conn, input) @@ -493,6 +491,17 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in d.SetId(aws.StringValue(output.Listeners[0].ListenerArn)) + const ( + loadBalancerListenerReadTimeout = 2 * time.Minute + ) + _, err = tfresource.RetryWhenNotFound(ctx, loadBalancerListenerReadTimeout, func() (interface{}, error) { + return FindListenerByARN(ctx, conn, d.Id()) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Listener (%s) create: %s", d.Id(), err) + } + // For partitions not supporting tag-on-create, attempt tag after create. if tags := getTagsIn(ctx); input.Tags == nil && len(tags) > 0 { err := createTags(ctx, conn, d.Id(), tags) @@ -512,76 +521,40 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in func resourceListenerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - const ( - loadBalancerListenerReadTimeout = 2 * time.Minute - ) conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - var listener *elbv2.Listener - - err := retry.RetryContext(ctx, loadBalancerListenerReadTimeout, func() *retry.RetryError { - var err error - listener, err = FindListenerByARN(ctx, conn, d.Id()) + listener, err := FindListenerByARN(ctx, conn, d.Id()) - if d.IsNewResource() && tfawserr.ErrCodeEquals(err, elbv2.ErrCodeListenerNotFoundException) { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - listener, err = FindListenerByARN(ctx, conn, d.Id()) - } - - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elbv2.ErrCodeListenerNotFoundException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ELBv2 Listener (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "describing ELBv2 Listener (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Listener (%s): %s", d.Id(), err) } - if listener == nil { - if d.IsNewResource() { - return sdkdiag.AppendErrorf(diags, "describing ELBv2 Listener (%s): empty response", d.Id()) - } - log.Printf("[WARN] ELBv2 Listener (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags + if listener.AlpnPolicy != nil && len(listener.AlpnPolicy) == 1 && listener.AlpnPolicy[0] != nil { + d.Set("alpn_policy", listener.AlpnPolicy[0]) } - d.Set("arn", listener.ListenerArn) - d.Set("load_balancer_arn", listener.LoadBalancerArn) - d.Set("port", listener.Port) - d.Set("protocol", listener.Protocol) - d.Set("ssl_policy", listener.SslPolicy) - if listener.Certificates != nil && len(listener.Certificates) == 1 && listener.Certificates[0] != nil { d.Set("certificate_arn", listener.Certificates[0].CertificateArn) } - - if listener.AlpnPolicy != nil && len(listener.AlpnPolicy) == 1 && listener.AlpnPolicy[0] != nil { - d.Set("alpn_policy", listener.AlpnPolicy[0]) - } - sort.Slice(listener.DefaultActions, func(i, j int) bool { return aws.Int64Value(listener.DefaultActions[i].Order) < aws.Int64Value(listener.DefaultActions[j].Order) }) - if err := d.Set("default_action", flattenLbListenerActions(d, listener.DefaultActions)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting default_action for ELBv2 listener (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting default_action: %s", err) } - + d.Set("load_balancer_arn", listener.LoadBalancerArn) if err := d.Set("mutual_authentication", flattenMutualAuthenticationAttributes(listener.MutualAuthentication)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting mutual_authentication for ELBv2 listener (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting mutual_authentication: %s", err) } + d.Set("port", listener.Port) + d.Set("protocol", listener.Protocol) + d.Set("ssl_policy", listener.SslPolicy) return diags } @@ -677,40 +650,79 @@ func resourceListenerDelete(ctx context.Context, d *schema.ResourceData, meta in return diags } -func retryListenerCreate(ctx context.Context, conn *elbv2.ELBV2, params *elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) { +func retryListenerCreate(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) { const ( loadBalancerListenerCreateTimeout = 5 * time.Minute ) - var output *elbv2.CreateListenerOutput + outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, loadBalancerListenerCreateTimeout, func() (interface{}, error) { + return conn.CreateListenerWithContext(ctx, input) + }, elbv2.ErrCodeCertificateNotFoundException) - err := retry.RetryContext(ctx, loadBalancerListenerCreateTimeout, func() *retry.RetryError { - var err error + if err != nil { + return nil, err + } - output, err = conn.CreateListenerWithContext(ctx, params) + return outputRaw.(*elbv2.CreateListenerOutput), nil +} - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeCertificateNotFoundException) { - return retry.RetryableError(err) +func FindListenerByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*elbv2.Listener, error) { + input := &elbv2.DescribeListenersInput{ + ListenerArns: aws.StringSlice([]string{arn}), + } + output, err := findListener(ctx, conn, input, tfslices.PredicateTrue[*elbv2.Listener]()) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.ListenerArn) != arn { + return nil, &retry.NotFoundError{ + LastRequest: input, } + } - if err != nil { - return retry.NonRetryableError(err) + return output, nil +} + +func findListener(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeListenersInput, filter tfslices.Predicate[*elbv2.Listener]) (*elbv2.Listener, error) { + output, err := findListeners(ctx, conn, input, filter) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findListeners(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeListenersInput, filter tfslices.Predicate[*elbv2.Listener]) ([]*elbv2.Listener, error) { + var output []*elbv2.Listener + + err := conn.DescribeListenersPagesWithContext(ctx, input, func(page *elbv2.DescribeListenersOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - return nil + for _, v := range page.Listeners { + if v != nil && filter(v) { + output = append(output, v) + } + } + + return !lastPage }) - if tfresource.TimedOut(err) { - output, err = conn.CreateListenerWithContext(ctx, params) + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeListenerNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } if err != nil { return nil, err } - if output == nil || len(output.Listeners) == 0 { - return nil, fmt.Errorf("creating ELBv2 Listener: no listeners returned in response") - } - return output, nil } diff --git a/internal/service/elbv2/listener_test.go b/internal/service/elbv2/listener_test.go index 5dd56d54d0b..32419eb20de 100644 --- a/internal/service/elbv2/listener_test.go +++ b/internal/service/elbv2/listener_test.go @@ -5,20 +5,19 @@ package elbv2_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" 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" tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "golang.org/x/exp/slices" ) @@ -682,30 +681,23 @@ func testAccCheckListenerDefaultActionOrderDisappears(ctx context.Context, liste } } -func testAccCheckListenerExists(ctx context.Context, n string, res *elbv2.Listener) resource.TestCheckFunc { +func testAccCheckListenerExists(ctx context.Context, n string, v *elbv2.Listener) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No Listener ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - listener, err := tfelbv2.FindListenerByARN(ctx, conn, rs.Primary.ID) + output, err := tfelbv2.FindListenerByARN(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("reading ELBv2 Listener (%s): %w", rs.Primary.ID, err) + return err } - if listener == nil { - return fmt.Errorf("ELBv2 Listener (%s) not found", rs.Primary.ID) - } + *v = *output - *res = *listener return nil } } @@ -719,21 +711,17 @@ func testAccCheckListenerDestroy(ctx context.Context) resource.TestCheckFunc { continue } - listener, err := tfelbv2.FindListenerByARN(ctx, conn, rs.Primary.ID) + _, err := tfelbv2.FindListenerByARN(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeListenerNotFoundException) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("reading ELBv2 Listener (%s): %w", rs.Primary.ID, err) - } - - if listener == nil { - continue + return err } - return fmt.Errorf("ELBv2 Listener %q still exists", rs.Primary.ID) + return fmt.Errorf("ELBv2 Listener %s still exists", rs.Primary.ID) } return nil @@ -741,27 +729,7 @@ func testAccCheckListenerDestroy(ctx context.Context) resource.TestCheckFunc { } func testAccListenerConfig_base(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - count = 2 - - vpc_id = aws_vpc.test.id - cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 2, count.index) - availability_zone = data.aws_availability_zones.available.names[count.index] - - tags = { - Name = "%[1]s-${count.index}" - } -} - + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` resource "aws_security_group" "test" { name = %[1]q description = "Used for ALB Testing" From 89e26e3c8dc71c23f62a36058dd2b0b62466b812 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 10:50:56 -0500 Subject: [PATCH 05/16] d/aws_alb_listener: Test 'mutual_authentication'. --- .../service/elbv2/listener_data_source.go | 73 ++-- .../elbv2/listener_data_source_test.go | 343 ++---------------- 2 files changed, 45 insertions(+), 371 deletions(-) diff --git a/internal/service/elbv2/listener_data_source.go b/internal/service/elbv2/listener_data_source.go index 503ca75bb32..3b5eb18f3ab 100644 --- a/internal/service/elbv2/listener_data_source.go +++ b/internal/service/elbv2/listener_data_source.go @@ -16,7 +16,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) // @SDKDataSource("aws_alb_listener") @@ -256,12 +258,11 @@ func DataSourceListener() *schema.Resource { Optional: true, Computed: true, ConflictsWith: []string{"arn"}, + RequiredWith: []string{"port"}, }, "mutual_authentication": { Type: schema.TypeList, - Optional: true, Computed: true, - MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "mode": { @@ -270,12 +271,10 @@ func DataSourceListener() *schema.Resource { }, "trust_store_arn": { Type: schema.TypeString, - Optional: true, Computed: true, }, "ignore_client_certificate_expiry": { Type: schema.TypeBool, - Optional: true, Computed: true, }, }, @@ -286,6 +285,7 @@ func DataSourceListener() *schema.Resource { Optional: true, Computed: true, ConflictsWith: []string{"arn"}, + RequiredWith: []string{"load_balancer_arn"}, }, "protocol": { Type: schema.TypeString, @@ -309,75 +309,44 @@ func dataSourceListenerRead(ctx context.Context, d *schema.ResourceData, meta in if v, ok := d.GetOk("arn"); ok { input.ListenerArns = aws.StringSlice([]string{v.(string)}) - } else { - lbArn, lbOk := d.GetOk("load_balancer_arn") - _, portOk := d.GetOk("port") - - if !lbOk || !portOk { - return sdkdiag.AppendErrorf(diags, "both load_balancer_arn and port must be set") - } - - input.LoadBalancerArn = aws.String(lbArn.(string)) + } else if v, ok := d.GetOk("load_balancer_arn"); ok { + input.LoadBalancerArn = aws.String(v.(string)) } - var results []*elbv2.Listener - - err := conn.DescribeListenersPagesWithContext(ctx, input, func(page *elbv2.DescribeListenersOutput, lastPage bool) bool { - if page == nil { - return !lastPage + filter := tfslices.PredicateTrue[*elbv2.Listener]() + if v, ok := d.GetOk("port"); ok { + port := v.(int) + filter = func(v *elbv2.Listener) bool { + return int(aws.Int64Value(v.Port)) == port } - - for _, l := range page.Listeners { - if l == nil { - continue - } - - if v, ok := d.GetOk("port"); ok && v.(int) != int(aws.Int64Value(l.Port)) { - continue - } - - results = append(results, l) - } - - return !lastPage - }) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Listener: %s", err) } + listener, err := findListener(ctx, conn, input, filter) - if len(results) != 1 { - return sdkdiag.AppendErrorf(diags, "Search returned %d results, please revise so only one is returned", len(results)) + if err != nil { + return sdkdiag.AppendFromErr(diags, tfresource.SingularDataSourceFindError("ELBv2 Listener", err)) } - listener := results[0] - d.SetId(aws.StringValue(listener.ListenerArn)) + if listener.AlpnPolicy != nil && len(listener.AlpnPolicy) == 1 && listener.AlpnPolicy[0] != nil { + d.Set("alpn_policy", listener.AlpnPolicy[0]) + } d.Set("arn", listener.ListenerArn) - d.Set("load_balancer_arn", listener.LoadBalancerArn) - d.Set("port", listener.Port) - d.Set("protocol", listener.Protocol) - d.Set("ssl_policy", listener.SslPolicy) - if listener.Certificates != nil && len(listener.Certificates) == 1 && listener.Certificates[0] != nil { d.Set("certificate_arn", listener.Certificates[0].CertificateArn) } - - if listener.AlpnPolicy != nil && len(listener.AlpnPolicy) == 1 && listener.AlpnPolicy[0] != nil { - d.Set("alpn_policy", listener.AlpnPolicy[0]) - } - sort.Slice(listener.DefaultActions, func(i, j int) bool { return aws.Int64Value(listener.DefaultActions[i].Order) < aws.Int64Value(listener.DefaultActions[j].Order) }) - if err := d.Set("default_action", flattenLbListenerActions(d, listener.DefaultActions)); err != nil { return sdkdiag.AppendErrorf(diags, "setting default_action: %s", err) } - + d.Set("load_balancer_arn", listener.LoadBalancerArn) if err := d.Set("mutual_authentication", flattenMutualAuthenticationAttributes(listener.MutualAuthentication)); err != nil { return sdkdiag.AppendErrorf(diags, "setting mutual_authentication: %s", err) } + d.Set("port", listener.Port) + d.Set("protocol", listener.Protocol) + d.Set("ssl_policy", listener.SslPolicy) tags, err := listTags(ctx, conn, d.Id()) diff --git a/internal/service/elbv2/listener_data_source_test.go b/internal/service/elbv2/listener_data_source_test.go index d1b92809330..2e1198de534 100644 --- a/internal/service/elbv2/listener_data_source_test.go +++ b/internal/service/elbv2/listener_data_source_test.go @@ -16,43 +16,8 @@ import ( func TestAccELBV2ListenerDataSource_basic(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_listener.test" dataSourceName := "data.aws_lb_listener.test" - dataSourceName2 := "data.aws_lb_listener.from_lb_and_port" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccListenerDataSourceConfig_basic(rName), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(dataSourceName, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttr(dataSourceName, "protocol", "HTTP"), - resource.TestCheckResourceAttr(dataSourceName, "port", "80"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.0.type", "forward"), - resource.TestCheckResourceAttr(dataSourceName, "tags.%", "0"), - resource.TestCheckResourceAttrSet(dataSourceName2, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttr(dataSourceName2, "protocol", "HTTP"), - resource.TestCheckResourceAttr(dataSourceName2, "port", "80"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.0.type", "forward"), - resource.TestCheckResourceAttr(dataSourceName2, "tags.%", "0"), - ), - }, - }, - }) -} - -func TestAccELBV2ListenerDataSource_backwardsCompatibility(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - dataSourceName := "data.aws_alb_listener.test" dataSourceName2 := "data.aws_alb_listener.from_lb_and_port" resource.ParallelTest(t, resource.TestCase{ @@ -61,84 +26,28 @@ func TestAccELBV2ListenerDataSource_backwardsCompatibility(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccListenerDataSourceConfig_backwardsCompatibility(rName), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(dataSourceName, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttr(dataSourceName, "protocol", "HTTP"), - resource.TestCheckResourceAttr(dataSourceName, "port", "80"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.0.type", "forward"), - resource.TestCheckResourceAttrSet(dataSourceName2, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttr(dataSourceName2, "protocol", "HTTP"), - resource.TestCheckResourceAttr(dataSourceName2, "port", "80"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.0.type", "forward"), - ), - }, - }, - }) -} - -func TestAccELBV2ListenerDataSource_https(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - key := acctest.TLSRSAPrivateKeyPEM(t, 2048) - certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") - dataSourceName := "data.aws_lb_listener.test" - dataSourceName2 := "data.aws_lb_listener.from_lb_and_port" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccListenerDataSourceConfig_https(rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(dataSourceName, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttrSet(dataSourceName, "certificate_arn"), - resource.TestCheckResourceAttr(dataSourceName, "protocol", "HTTPS"), - resource.TestCheckResourceAttr(dataSourceName, "port", "443"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName, "default_action.0.type", "forward"), - resource.TestCheckResourceAttr(dataSourceName, "ssl_policy", "ELBSecurityPolicy-2016-08"), - resource.TestCheckResourceAttrSet(dataSourceName2, "load_balancer_arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "default_action.0.target_group_arn"), - resource.TestCheckResourceAttrSet(dataSourceName2, "certificate_arn"), - resource.TestCheckResourceAttr(dataSourceName2, "protocol", "HTTPS"), - resource.TestCheckResourceAttr(dataSourceName2, "port", "443"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.#", "1"), - resource.TestCheckResourceAttr(dataSourceName2, "default_action.0.type", "forward"), - resource.TestCheckResourceAttr(dataSourceName2, "ssl_policy", "ELBSecurityPolicy-2016-08"), - ), - }, - }, - }) -} - -func TestAccELBV2ListenerDataSource_DefaultAction_forward(t *testing.T) { - ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - dataSourceName := "data.aws_lb_listener.test" - resourceName := "aws_lb_listener.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Steps: []resource.TestStep{ - { - Config: testAccListenerDataSourceConfig_defaultActionForward(rName), + Config: testAccListenerDataSourceConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSourceName, "alpn_policy", resourceName, "alpn_policy"), + resource.TestCheckResourceAttrPair(dataSourceName, "arn", resourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "certificate_arn", resourceName, "certificate_arn"), resource.TestCheckResourceAttrPair(dataSourceName, "default_action.#", resourceName, "default_action.#"), - resource.TestCheckResourceAttrPair(dataSourceName, "default_action.0.forward.#", resourceName, "default_action.0.forward.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "load_balancer_arn", resourceName, "load_balancer_arn"), + resource.TestCheckResourceAttrPair(dataSourceName, "mutual_authentication.#", resourceName, "mutual_authentication.#"), + resource.TestCheckResourceAttrPair(dataSourceName, "port", resourceName, "port"), + resource.TestCheckResourceAttrPair(dataSourceName, "protocol", resourceName, "protocol"), + resource.TestCheckResourceAttrPair(dataSourceName, "ssl_policy", resourceName, "ssl_policy"), + resource.TestCheckResourceAttrPair(dataSourceName, "tags.%", resourceName, "tags.%"), + resource.TestCheckResourceAttrPair(dataSourceName2, "alpn_policy", dataSourceName, "alpn_policy"), + resource.TestCheckResourceAttrPair(dataSourceName2, "arn", dataSourceName, "arn"), + resource.TestCheckResourceAttrPair(dataSourceName2, "certificate_arn", dataSourceName, "certificate_arn"), + resource.TestCheckResourceAttrPair(dataSourceName2, "default_action.#", dataSourceName, "default_action.#"), + resource.TestCheckResourceAttrPair(dataSourceName2, "load_balancer_arn", dataSourceName, "load_balancer_arn"), + resource.TestCheckResourceAttrPair(dataSourceName2, "mutual_authentication.#", dataSourceName, "mutual_authentication.#"), + resource.TestCheckResourceAttrPair(dataSourceName2, "port", dataSourceName, "port"), + resource.TestCheckResourceAttrPair(dataSourceName2, "protocol", dataSourceName, "protocol"), + resource.TestCheckResourceAttrPair(dataSourceName2, "ssl_policy", dataSourceName, "ssl_policy"), + resource.TestCheckResourceAttrPair(dataSourceName2, "tags.%", dataSourceName, "tags.%"), ), }, }, @@ -156,136 +65,20 @@ resource "aws_lb_listener" "test" { target_group_arn = aws_lb_target_group.test.id type = "forward" } -} - -resource "aws_lb" "test" { - name = %[1]q - internal = true - security_groups = [aws_security_group.test.id] - subnets = aws_subnet.test[*].id - - idle_timeout = 30 - enable_deletion_protection = false tags = { - TestName = "TestAccELBV2ListenerDataSource_basic" - } -} - -resource "aws_lb_target_group" "test" { - name = %[1]q - port = 8080 - protocol = "HTTP" - vpc_id = aws_vpc.test.id - - health_check { - path = "/health" - interval = 60 - port = 8081 - protocol = "HTTP" - timeout = 3 - healthy_threshold = 3 - unhealthy_threshold = 3 - matcher = "200-299" - } -} - -data "aws_lb_listener" "test" { - arn = aws_lb_listener.test.arn -} - -data "aws_lb_listener" "from_lb_and_port" { - load_balancer_arn = aws_lb.test.arn - port = aws_lb_listener.test.port -} -`, rName)) -} - -func testAccListenerDataSourceConfig_backwardsCompatibility(rName string) string { - return acctest.ConfigCompose(testAccListenerConfig_base(rName), fmt.Sprintf(` -resource "aws_alb_listener" "test" { - load_balancer_arn = aws_alb.test.id - protocol = "HTTP" - port = "80" - - default_action { - target_group_arn = aws_alb_target_group.test.id - type = "forward" - } -} - -resource "aws_alb" "test" { - name = %[1]q - internal = true - security_groups = [aws_security_group.test.id] - subnets = aws_subnet.test[*].id - - idle_timeout = 30 - enable_deletion_protection = false - - tags = { - TestName = "TestAccELBV2ListenerDataSource_basic" - } -} - -resource "aws_alb_target_group" "test" { - name = %[1]q - port = 8080 - protocol = "HTTP" - vpc_id = aws_vpc.test.id - - health_check { - path = "/health" - interval = 60 - port = 8081 - protocol = "HTTP" - timeout = 3 - healthy_threshold = 3 - unhealthy_threshold = 3 - matcher = "200-299" - } -} - -data "aws_alb_listener" "test" { - arn = aws_alb_listener.test.arn -} - -data "aws_alb_listener" "from_lb_and_port" { - load_balancer_arn = aws_alb.test.arn - port = aws_alb_listener.test.port -} -`, rName)) -} - -func testAccListenerDataSourceConfig_https(rName, certificate, key string) string { - return acctest.ConfigCompose(testAccListenerConfig_base(rName), fmt.Sprintf(` -resource "aws_lb_listener" "test" { - load_balancer_arn = aws_lb.test.id - protocol = "HTTPS" - port = "443" - ssl_policy = "ELBSecurityPolicy-2016-08" - certificate_arn = aws_iam_server_certificate.test.arn - - default_action { - target_group_arn = aws_lb_target_group.test.id - type = "forward" + Name = %[1]q } } resource "aws_lb" "test" { name = %[1]q - internal = false + internal = true security_groups = [aws_security_group.test.id] subnets = aws_subnet.test[*].id idle_timeout = 30 enable_deletion_protection = false - - tags = { - TestName = "TestAccELBV2ListenerDataSource_basic" - } - - depends_on = [aws_internet_gateway.gw] } resource "aws_lb_target_group" "test" { @@ -306,101 +99,13 @@ resource "aws_lb_target_group" "test" { } } -resource "aws_internet_gateway" "gw" { - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - TestName = "TestAccELBV2ListenerDataSource_basic" - } -} - -resource "aws_iam_server_certificate" "test" { - name = %[1]q - certificate_body = "%[2]s" - private_key = "%[3]s" -} - data "aws_lb_listener" "test" { arn = aws_lb_listener.test.arn } -data "aws_lb_listener" "from_lb_and_port" { +data "aws_alb_listener" "from_lb_and_port" { load_balancer_arn = aws_lb.test.arn port = aws_lb_listener.test.port } -`, rName, certificate, key)) -} - -func testAccListenerDataSourceConfig_defaultActionForward(rName string) string { - return acctest.ConfigCompose( - acctest.ConfigAvailableAZsNoOptIn(), - fmt.Sprintf(` -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - count = 2 - - availability_zone = data.aws_availability_zones.available.names[count.index] - cidr_block = cidrsubnet(aws_vpc.test.cidr_block, 8, count.index) - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - } -} - -resource "aws_lb" "test" { - internal = true - name = %[1]q - - subnet_mapping { - subnet_id = aws_subnet.test[0].id - } - - subnet_mapping { - subnet_id = aws_subnet.test[1].id - } -} - -resource "aws_lb_target_group" "test" { - count = 2 - - port = 80 - protocol = "HTTP" - vpc_id = aws_vpc.test.id -} - -resource "aws_lb_listener" "test" { - load_balancer_arn = aws_lb.test.id - port = 80 - protocol = "HTTP" - - default_action { - type = "forward" - - forward { - target_group { - arn = aws_lb_target_group.test[0].arn - weight = 1 - } - - target_group { - arn = aws_lb_target_group.test[1].arn - weight = 2 - } - } - } -} - -data "aws_lb_listener" "test" { - arn = aws_lb_listener.test.arn -} `, rName)) } From a0b41d11c124f24683c251f058a5d315f2e55bc3 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 10:56:55 -0500 Subject: [PATCH 06/16] r/aws_lb_listener: Alphabetize attributes. --- internal/service/elbv2/listener.go | 114 +++++++++-------------- website/docs/r/lb_listener.html.markdown | 13 +-- 2 files changed, 49 insertions(+), 78 deletions(-) diff --git a/internal/service/elbv2/listener.go b/internal/service/elbv2/listener.go index 55e389d2bd1..a9e297a703e 100644 --- a/internal/service/elbv2/listener.go +++ b/internal/service/elbv2/listener.go @@ -47,7 +47,8 @@ func ResourceListener() *schema.Resource { }, Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(10 * time.Minute), + Create: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(5 * time.Minute), }, CustomizeDiff: customdiff.Sequence( @@ -362,21 +363,21 @@ func ResourceListener() *schema.Resource { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "ignore_client_certificate_expiry": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "mode": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice(MutualAuthenticationModeEnum_Values(), true), + ValidateFunc: validation.StringInSlice(mutualAuthenticationModeEnum_Values(), true), }, "trust_store_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "ignore_client_certificate_expiry": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, }, }, }, @@ -468,13 +469,13 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in input.SslPolicy = aws.String(v.(string)) } - output, err := retryListenerCreate(ctx, conn, input) + output, err := retryListenerCreate(ctx, conn, input, d.Timeout(schema.TimeoutCreate)) // Some partitions (e.g. ISO) may not support tag-on-create. if input.Tags != nil && errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { input.Tags = nil - output, err = retryListenerCreate(ctx, conn, input) + output, err = retryListenerCreate(ctx, conn, input, d.Timeout(schema.TimeoutCreate)) } // Tags are not supported on creation with some load balancer types (i.e. Gateway) @@ -482,7 +483,7 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in if input.Tags != nil && tfawserr.ErrMessageContains(err, ErrValidationError, TagsOnCreationErrMessage) { input.Tags = nil - output, err = retryListenerCreate(ctx, conn, input) + output, err = retryListenerCreate(ctx, conn, input, d.Timeout(schema.TimeoutCreate)) } if err != nil { @@ -491,10 +492,7 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in d.SetId(aws.StringValue(output.Listeners[0].ListenerArn)) - const ( - loadBalancerListenerReadTimeout = 2 * time.Minute - ) - _, err = tfresource.RetryWhenNotFound(ctx, loadBalancerListenerReadTimeout, func() (interface{}, error) { + _, err = tfresource.RetryWhenNotFound(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return FindListenerByARN(ctx, conn, d.Id()) }) @@ -561,9 +559,6 @@ func resourceListenerRead(ctx context.Context, d *schema.ResourceData, meta inte func resourceListenerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - const ( - loadBalancerListenerUpdateTimeout = 5 * time.Minute - ) conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) if d.HasChangesExcept("tags", "tags_all") { @@ -571,27 +566,14 @@ func resourceListenerUpdate(ctx context.Context, d *schema.ResourceData, meta in ListenerArn: aws.String(d.Id()), } - if v, ok := d.GetOk("port"); ok { - input.Port = aws.Int64(int64(v.(int))) - } - - if v, ok := d.GetOk("protocol"); ok { - input.Protocol = aws.String(v.(string)) - } - - if v, ok := d.GetOk("ssl_policy"); ok { - input.SslPolicy = aws.String(v.(string)) + if v, ok := d.GetOk("alpn_policy"); ok { + input.AlpnPolicy = aws.StringSlice([]string{v.(string)}) } if v, ok := d.GetOk("certificate_arn"); ok { - input.Certificates = make([]*elbv2.Certificate, 1) - input.Certificates[0] = &elbv2.Certificate{ + input.Certificates = []*elbv2.Certificate{{ CertificateArn: aws.String(v.(string)), - } - } - - if v, ok := d.GetOk("alpn_policy"); ok { - input.AlpnPolicy = aws.StringSlice([]string{v.(string)}) + }} } if d.HasChange("default_action") { @@ -603,31 +585,25 @@ func resourceListenerUpdate(ctx context.Context, d *schema.ResourceData, meta in } if d.HasChange("mutual_authentication") { - var err error input.MutualAuthentication = expandMutualAuthenticationAttributes(d.Get("mutual_authentication").([]interface{})) - if err != nil { - return sdkdiag.AppendFromErr(diags, err) - } } - err := retry.RetryContext(ctx, loadBalancerListenerUpdateTimeout, func() *retry.RetryError { - _, err := conn.ModifyListenerWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeCertificateNotFoundException) { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } + if v, ok := d.GetOk("port"); ok { + input.Port = aws.Int64(int64(v.(int))) + } - return nil - }) + if v, ok := d.GetOk("protocol"); ok { + input.Protocol = aws.String(v.(string)) + } - if tfresource.TimedOut(err) { - _, err = conn.ModifyListenerWithContext(ctx, input) + if v, ok := d.GetOk("ssl_policy"); ok { + input.SslPolicy = aws.String(v.(string)) } + _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, d.Timeout(schema.TimeoutUpdate), func() (interface{}, error) { + return conn.ModifyListenerWithContext(ctx, input) + }, elbv2.ErrCodeCertificateNotFoundException) + if err != nil { return sdkdiag.AppendErrorf(diags, "modifying ELBv2 Listener (%s): %s", d.Id(), err) } @@ -640,21 +616,20 @@ func resourceListenerDelete(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) + log.Printf("[INFO] Deleting ELBv2 Listener: %s", d.Id()) _, err := conn.DeleteListenerWithContext(ctx, &elbv2.DeleteListenerInput{ ListenerArn: aws.String(d.Id()), }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Listener (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting ELBv2 Listener (%s): %s", d.Id(), err) } return diags } -func retryListenerCreate(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.CreateListenerInput) (*elbv2.CreateListenerOutput, error) { - const ( - loadBalancerListenerCreateTimeout = 5 * time.Minute - ) - outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, loadBalancerListenerCreateTimeout, func() (interface{}, error) { +func retryListenerCreate(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.CreateListenerInput, timeout time.Duration) (*elbv2.CreateListenerOutput, error) { + outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, timeout, func() (interface{}, error) { return conn.CreateListenerWithContext(ctx, input) }, elbv2.ErrCodeCertificateNotFoundException) @@ -944,7 +919,7 @@ func expandMutualAuthenticationAttributes(l []interface{}) *elbv2.MutualAuthenti } mode := tfMap["mode"].(string) - if mode == MutualAuthenticationOff { + if mode == mutualAuthenticationOff { return &elbv2.MutualAuthenticationAttributes{ Mode: aws.String(mode), } @@ -1050,7 +1025,7 @@ func flattenMutualAuthenticationAttributes(description *elbv2.MutualAuthenticati } mode := aws.StringValue(description.Mode) - if mode == MutualAuthenticationOff { + if mode == mutualAuthenticationOff { return []interface{}{ map[string]interface{}{ "mode": mode, @@ -1189,20 +1164,15 @@ func flattenLbListenerActionRedirectConfig(config *elbv2.RedirectActionConfig) [ } const ( - // MutualAuthenticationOff is a MutualAuthenticationModeEnum enum value - MutualAuthenticationOff = "off" - - // MutualAuthenticationVerify is a MutualAuthenticationModeEnum enum value - MutualAuthenticationVerify = "verify" - // MutualAuthenticationPassthrough is a MutualAuthenticationModeEnum enum value - MutualAuthenticationPassthrough = "passthrough" + mutualAuthenticationOff = "off" + mutualAuthenticationVerify = "verify" + mutualAuthenticationPassthrough = "passthrough" ) -// ProtocolEnum_Values returns all elements of the ProtocolEnum enum -func MutualAuthenticationModeEnum_Values() []string { +func mutualAuthenticationModeEnum_Values() []string { return []string{ - MutualAuthenticationOff, - MutualAuthenticationVerify, - MutualAuthenticationPassthrough, + mutualAuthenticationOff, + mutualAuthenticationVerify, + mutualAuthenticationPassthrough, } } diff --git a/website/docs/r/lb_listener.html.markdown b/website/docs/r/lb_listener.html.markdown index 843405e7236..c98b41fea46 100644 --- a/website/docs/r/lb_listener.html.markdown +++ b/website/docs/r/lb_listener.html.markdown @@ -259,6 +259,7 @@ The following arguments are optional: * `alpn_policy` - (Optional) Name of the Application-Layer Protocol Negotiation (ALPN) policy. Can be set if `protocol` is `TLS`. Valid values are `HTTP1Only`, `HTTP2Only`, `HTTP2Optional`, `HTTP2Preferred`, and `None`. * `certificate_arn` - (Optional) ARN of the default SSL server certificate. Exactly one certificate is required if the protocol is HTTPS. For adding additional SSL certificates, see the [`aws_lb_listener_certificate` resource](/docs/providers/aws/r/lb_listener_certificate.html). +* `mutual_authentication` - (Optional) The mutual authentication configuration information. Detailed below. * `port` - (Optional) Port on which the load balancer is listening. Not valid for Gateway Load Balancers. * `protocol` - (Optional) Protocol for connections from clients to the load balancer. For Application Load Balancers, valid values are `HTTP` and `HTTPS`, with a default of `HTTP`. For Network Load Balancers, valid values are `TCP`, `TLS`, `UDP`, and `TCP_UDP`. Not valid to use `UDP` or `TCP_UDP` if dual-stack mode is enabled. Not valid for Gateway Load Balancers. * `ssl_policy` - (Optional) Name of the SSL Policy for the listener. Required if `protocol` is `HTTPS` or `TLS`. @@ -343,12 +344,6 @@ The following arguments are optional: * `stickiness` - (Optional) Configuration block for target group stickiness for the rule. Detailed below. -### `mutual_authentication` - -* `mode` - (Required) Valid values are `off`, `verify` and `passthrough`. -* `trust_store_arn` - (Required) ARN of the elbv2 Trust Store. -* `ignore_client_certificate_expiry` - (Optional) Whether client certificate expiry is ignored. Default is `false`. - ##### target_group The following arguments are required: @@ -385,6 +380,12 @@ The following arguments are optional: * `protocol` - (Optional) Protocol. Valid values are `HTTP`, `HTTPS`, or `#{protocol}`. Defaults to `#{protocol}`. * `query` - (Optional) Query parameters, URL-encoded when necessary, but not percent-encoded. Do not include the leading "?". Defaults to `#{query}`. +### mutual_authentication + +* `mode` - (Required) Valid values are `off`, `verify` and `passthrough`. +* `trust_store_arn` - (Required) ARN of the elbv2 Trust Store. +* `ignore_client_certificate_expiry` - (Optional) Whether client certificate expiry is ignored. Default is `false`. + ## Attribute Reference This resource exports the following attributes in addition to the arguments above: From 4ef43bcb1642957624e6b3e510075c3be76091fc Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 14:16:55 -0500 Subject: [PATCH 07/16] r/aws_lb_trust_store: Tidy up. --- internal/service/elbv2/service_package_gen.go | 8 - internal/service/elbv2/trust_store.go | 169 ++++++------------ website/docs/r/lb_trust_store.html.markdown | 4 +- 3 files changed, 56 insertions(+), 125 deletions(-) diff --git a/internal/service/elbv2/service_package_gen.go b/internal/service/elbv2/service_package_gen.go index 30e9e51848e..b4fc8ea1d9a 100644 --- a/internal/service/elbv2/service_package_gen.go +++ b/internal/service/elbv2/service_package_gen.go @@ -110,14 +110,6 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceTargetGroupAttachment, TypeName: "aws_alb_target_group_attachment", }, - { - Factory: ResourceTrustStore, - TypeName: "aws_alb_trust_store", - Name: "Trust Store", - Tags: &types.ServicePackageResourceTags{ - IdentifierAttribute: "id", - }, - }, { Factory: ResourceTrustStoreRevocation, TypeName: "aws_alb_trust_store_revocation", diff --git a/internal/service/elbv2/trust_store.go b/internal/service/elbv2/trust_store.go index 2d6a3ea1609..00176394bdf 100644 --- a/internal/service/elbv2/trust_store.go +++ b/internal/service/elbv2/trust_store.go @@ -27,7 +27,6 @@ import ( ) // @SDKResource("aws_lb_trust_store", name="Trust Store") -// @SDKResource("aws_alb_trust_store", name="Trust Store") // @Tags(identifierAttribute="id") func ResourceTrustStore() *schema.Resource { return &schema.Resource{ @@ -39,8 +38,10 @@ func ResourceTrustStore() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(10 * time.Minute), + Create: schema.DefaultTimeout(2 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), }, CustomizeDiff: customdiff.Sequence( @@ -102,25 +103,15 @@ func resourceTrustStoreCreate(ctx context.Context, d *schema.ResourceData, meta create.WithConfiguredPrefix(d.Get("name_prefix").(string)), create.WithDefaultPrefix("tf-"), ).Generate() - exist, err := FindTrustStoreByName(ctx, conn, name) - - if err != nil && !tfresource.NotFound(err) { - return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store (%s): %s", name, err) - } - - if exist != nil { - return sdkdiag.AppendErrorf(diags, "ELBv2 Trust Store (%s) already exists", name) - } - input := &elbv2.CreateTrustStoreInput{ - Name: aws.String(name), - Tags: getTagsIn(ctx), CaCertificatesBundleS3Bucket: aws.String(d.Get("ca_certificates_bundle_s3_bucket").(string)), CaCertificatesBundleS3Key: aws.String(d.Get("ca_certificates_bundle_s3_key").(string)), + Name: aws.String(name), + Tags: getTagsIn(ctx), } - if d.Get("ca_certificates_bundle_s3_object_version").(string) != "" { - input.CaCertificatesBundleS3ObjectVersion = aws.String(d.Get("ca_certificates_bundle_s3_object_version").(string)) + if v, ok := d.GetOk("ca_certificates_bundle_s3_object_version"); ok { + input.CaCertificatesBundleS3ObjectVersion = aws.String(v.(string)) } output, err := conn.CreateTrustStoreWithContext(ctx, input) @@ -144,13 +135,9 @@ func resourceTrustStoreCreate(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store (%s): %s", name, err) } - if len(output.TrustStores) == 0 { - return sdkdiag.AppendErrorf(diags, "creating Trust Store: no trust stores returned in response") - } - d.SetId(aws.StringValue(output.TrustStores[0].TrustStoreArn)) - _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { + _, err = tfresource.RetryWhenNotFound(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { return FindTrustStoreByARN(ctx, conn, d.Id()) }) @@ -179,9 +166,7 @@ func resourceTrustStoreRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - outputRaw, err := tfresource.RetryWhenNewResourceNotFound(ctx, propagationTimeout, func() (interface{}, error) { - return FindTrustStoreByARN(ctx, conn, d.Id()) - }, d.IsNewResource()) + trustStore, err := FindTrustStoreByARN(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ELBv2 Trust Store %s not found, removing from state", d.Id()) @@ -193,10 +178,9 @@ func resourceTrustStoreRead(ctx context.Context, d *schema.ResourceData, meta in return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store (%s): %s", d.Id(), err) } - trustStore := outputRaw.(*elbv2.TrustStore) - - d.Set("name", trustStore.Name) d.Set("arn", trustStore.TrustStoreArn) + d.Set("name", trustStore.Name) + d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(trustStore.Name))) return diags } @@ -205,20 +189,21 @@ func resourceTrustStoreUpdate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - if d.HasChanges("ca_certificates_bundle_s3_bucket", "ca_certificates_bundle_s3_key", "ca_certificates_bundle_s3_object_version", "tags") { - var params = &elbv2.ModifyTrustStoreInput{ - TrustStoreArn: aws.String(d.Id()), + if d.HasChangesExcept("tags", "tags_all") { + input := &elbv2.ModifyTrustStoreInput{ CaCertificatesBundleS3Bucket: aws.String(d.Get("ca_certificates_bundle_s3_bucket").(string)), CaCertificatesBundleS3Key: aws.String(d.Get("ca_certificates_bundle_s3_key").(string)), + TrustStoreArn: aws.String(d.Id()), } - if d.Get("ca_certificates_bundle_s3_object_version").(string) != "" { - params.CaCertificatesBundleS3ObjectVersion = aws.String(d.Get("ca_certificates_bundle_s3_object_version").(string)) + if v, ok := d.GetOk("ca_certificates_bundle_s3_object_version"); ok { + input.CaCertificatesBundleS3ObjectVersion = aws.String(v.(string)) } - _, err := conn.ModifyTrustStoreWithContext(ctx, params) + _, err := conn.ModifyTrustStoreWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying Trust Store: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying ELBv2 Trust Store (%s): %s", d.Id(), err) } } @@ -227,41 +212,21 @@ func resourceTrustStoreUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceTrustStoreDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - const ( - trustStoreDeleteTimeout = 2 * time.Minute - ) conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - err := waitForNoTrustStoreAssociations(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Trust Store Associations (%s) to be removed: %s", d.Get("name").(string), err) + if err := waitForNoTrustStoreAssociations(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBV2 Trust Store (%s) associations delete: %s", d.Id(), err) } - input := &elbv2.DeleteTrustStoreInput{ - TrustStoreArn: aws.String(d.Id()), - } - - log.Printf("[DEBUG] Deleting Trust Store (%s): %s", d.Id(), input) - err = retry.RetryContext(ctx, trustStoreDeleteTimeout, func() *retry.RetryError { - _, err := conn.DeleteTrustStoreWithContext(ctx, input) - - if tfawserr.ErrMessageContains(err, "TrustStoreInUse", "is currently in use by a listener") { - return retry.RetryableError(err) - } - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil - }) - - if tfresource.TimedOut(err) { - _, err = conn.DeleteTrustStoreWithContext(ctx, input) - } + log.Printf("[DEBUG] Deleting ELBv2 Trust Store: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, d.Timeout(schema.TimeoutDelete), func() (interface{}, error) { + return conn.DeleteTrustStoreWithContext(ctx, &elbv2.DeleteTrustStoreInput{ + TrustStoreArn: aws.String(d.Id()), + }) + }, elbv2.ErrCodeTrustStoreInUseException, "is currently in use by a listener") if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Trust Store: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting ELBv2 Trust Store (%s): %s", d.Id(), err) } return diags @@ -271,8 +236,7 @@ func FindTrustStoreByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*e input := &elbv2.DescribeTrustStoresInput{ TrustStoreArns: aws.StringSlice([]string{arn}), } - - output, err := FindTrustStore(ctx, conn, input) + output, err := findTrustStore(ctx, conn, input) if err != nil { return nil, err @@ -288,28 +252,17 @@ func FindTrustStoreByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*e return output, nil } -func FindTrustStoreByName(ctx context.Context, conn *elbv2.ELBV2, name string) (*elbv2.TrustStore, error) { - input := &elbv2.DescribeTrustStoresInput{ - Names: aws.StringSlice([]string{name}), - } - - output, err := FindTrustStore(ctx, conn, input) +func findTrustStore(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) (*elbv2.TrustStore, error) { + output, err := findTrustStores(ctx, conn, input) if err != nil { return nil, err } - // Eventual consistency check. - if aws.StringValue(output.Name) != name { - return nil, &retry.NotFoundError{ - LastRequest: input, - } - } - - return output, nil + return tfresource.AssertSinglePtrResult(output) } -func FindTrustStores(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) ([]*elbv2.TrustStore, error) { +func findTrustStores(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) ([]*elbv2.TrustStore, error) { var output []*elbv2.TrustStore err := conn.DescribeTrustStoresPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoresOutput, lastPage bool) bool { @@ -340,37 +293,7 @@ func FindTrustStores(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Descri return output, nil } -func FindTrustStore(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoresInput) (*elbv2.TrustStore, error) { - output, err := FindTrustStores(ctx, conn, input) - - if err != nil { - return nil, err - } - - if len(output) == 0 || output[0] == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - if count := len(output); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return output[0], nil -} - -func waitForNoTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, arn string, timeout time.Duration) error { - input := &elbv2.DescribeTrustStoreAssociationsInput{ - TrustStoreArn: aws.String(arn), - } - - _, err := tfresource.RetryUntilEqual(ctx, timeout, 0, func() (int, error) { - return GetRemainingTrustStoreAssociations(ctx, conn, input) - }) - - return err -} - -func GetRemainingTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoreAssociationsInput) (int, error) { +func findTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoreAssociationsInput) ([]*elbv2.TrustStoreAssociation, error) { var output []*elbv2.TrustStoreAssociation err := conn.DescribeTrustStoreAssociationsPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoreAssociationsOutput, lastPage bool) bool { @@ -388,15 +311,33 @@ func GetRemainingTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, }) if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { - return -1, &retry.NotFoundError{ + return nil, &retry.NotFoundError{ LastError: err, LastRequest: input, } } if err != nil { - return -1, err + return nil, err + } + + return output, nil +} + +func waitForNoTrustStoreAssociations(ctx context.Context, conn *elbv2.ELBV2, arn string, timeout time.Duration) error { + input := &elbv2.DescribeTrustStoreAssociationsInput{ + TrustStoreArn: aws.String(arn), } - return len(output), nil + _, err := tfresource.RetryUntilEqual(ctx, timeout, 0, func() (int, error) { + associations, err := findTrustStoreAssociations(ctx, conn, input) + + if err != nil { + return 0, err + } + + return len(associations), nil + }) + + return err } diff --git a/website/docs/r/lb_trust_store.html.markdown b/website/docs/r/lb_trust_store.html.markdown index 966ff84e48c..c417812d43c 100644 --- a/website/docs/r/lb_trust_store.html.markdown +++ b/website/docs/r/lb_trust_store.html.markdown @@ -1,7 +1,7 @@ --- subcategory: "ELB (Elastic Load Balancing)" layout: "aws" -page_title: "AWS: aws_alb_trust_store" +page_title: "AWS: aws_lb_trust_store" description: |- Provides a Trust Store resource for use with Load Balancers. --- @@ -10,8 +10,6 @@ description: |- Provides a ELBv2 Trust Store for use with Application Load Balancer Listener resources. -~> **Note:** `aws_alb_trust_store` is known as `aws_lb_trust_store`. The functionality is identical. - ## Example Usage ### Trust Store Load Balancer Listener From 1458be0c563b71422e8da6622d8fdf0343dd26a9 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 14:25:41 -0500 Subject: [PATCH 08/16] r/aws_lb_trust_store: Tidy up acceptance tests. --- internal/service/elbv2/listener_test.go | 2 +- .../elbv2/trust_store_data_source_test.go | 2 +- .../elbv2/trust_store_revocation_test.go | 2 +- internal/service/elbv2/trust_store_test.go | 45 ++++++------------- 4 files changed, 17 insertions(+), 34 deletions(-) diff --git a/internal/service/elbv2/listener_test.go b/internal/service/elbv2/listener_test.go index 32419eb20de..7b3d893531a 100644 --- a/internal/service/elbv2/listener_test.go +++ b/internal/service/elbv2/listener_test.go @@ -1227,7 +1227,7 @@ resource "aws_internet_gateway" "test" { func testAccListenerConfig_mutualAuthentication(rName string, key, certificate string) string { return acctest.ConfigCompose( testAccListenerConfig_base(rName), - testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` + testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` resource "aws_lb_trust_store" "test" { name = %[1]q ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket diff --git a/internal/service/elbv2/trust_store_data_source_test.go b/internal/service/elbv2/trust_store_data_source_test.go index 1e515938cbf..2853c86e4ef 100644 --- a/internal/service/elbv2/trust_store_data_source_test.go +++ b/internal/service/elbv2/trust_store_data_source_test.go @@ -44,7 +44,7 @@ func TestAccELBV2TrustStoreDataSource_basic(t *testing.T) { } func testAccTrustStoreDataSourceConfig_base(rName string) string { - return acctest.ConfigCompose(testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` resource "aws_lb_trust_store" "test" { name = %[1]q ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket diff --git a/internal/service/elbv2/trust_store_revocation_test.go b/internal/service/elbv2/trust_store_revocation_test.go index 2348c6edc3f..f4fc3806073 100644 --- a/internal/service/elbv2/trust_store_revocation_test.go +++ b/internal/service/elbv2/trust_store_revocation_test.go @@ -107,7 +107,7 @@ func testAccCheckTrustStoreRevocationDestroy(ctx context.Context) resource.TestC } func testAccTrustStoreRevocationConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccTrustStoreConfig_S3BucketCA(rName), fmt.Sprintf(` + return acctest.ConfigCompose(testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` resource "aws_lb_trust_store" "test" { name = %[1]q ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket diff --git a/internal/service/elbv2/trust_store_test.go b/internal/service/elbv2/trust_store_test.go index e9c35565c4b..3cbec8dc3e5 100644 --- a/internal/service/elbv2/trust_store_test.go +++ b/internal/service/elbv2/trust_store_test.go @@ -5,19 +5,18 @@ package elbv2_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" 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" tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBV2TrustStore_basic(t *testing.T) { @@ -38,6 +37,7 @@ func TestAccELBV2TrustStore_basic(t *testing.T) { testAccCheckTrustStoreExists(ctx, resourceName, &conf), resource.TestCheckResourceAttrSet(resourceName, "arn"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "elasticloadbalancing", regexache.MustCompile("truststore/.+$")), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), ), }, { @@ -95,30 +95,23 @@ func TestAccELBV2TrustStore_tags(t *testing.T) { }) } -func testAccCheckTrustStoreExists(ctx context.Context, n string, res *elbv2.TrustStore) resource.TestCheckFunc { +func testAccCheckTrustStoreExists(ctx context.Context, n string, v *elbv2.TrustStore) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No Trust Store ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - trustStore, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) + output, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("reading ELBv2 Trust Store (%s): %w", rs.Primary.ID, err) + return err } - if trustStore == nil { - return fmt.Errorf("ELBv2 Trust Store (%s) not found", rs.Primary.ID) - } + *v = *output - *res = *trustStore return nil } } @@ -132,28 +125,24 @@ func testAccCheckTrustStoreDestroy(ctx context.Context) resource.TestCheckFunc { continue } - trustStore, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) + _, err := tfelbv2.FindTrustStoreByARN(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("reading ELBv2 Trust Store (%s): %w", rs.Primary.ID, err) - } - - if trustStore == nil { - continue + return err } - return fmt.Errorf("ELBv2 Trust Store %q still exists", rs.Primary.ID) + return fmt.Errorf("ELBv2 Trust Store %s still exists", rs.Primary.ID) } return nil } } -func testAccTrustStoreConfig_S3BucketCA(rName string) string { +func testAccTrustStoreConfig_baseS3BucketCA(rName string) string { return acctest.ConfigCompose(fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -178,7 +167,6 @@ resource "aws_s3_bucket_public_access_block" "test" { } resource "aws_s3_object" "test" { - bucket = aws_s3_bucket.test.bucket key = "%[1]s.pem" content = < Date: Thu, 30 Nov 2023 14:25:48 -0500 Subject: [PATCH 09/16] Acceptance test output: % make testacc TESTARGS='-run=TestAccELBV2TrustStore_' PKG=elbv2 ==> Checking that code complies with gofmt requirements... TF_ACC=1 go test ./internal/service/elbv2/... -v -count 1 -parallel 20 -run=TestAccELBV2TrustStore_ -timeout 360m === RUN TestAccELBV2TrustStore_basic === PAUSE TestAccELBV2TrustStore_basic === RUN TestAccELBV2TrustStore_tags === PAUSE TestAccELBV2TrustStore_tags === CONT TestAccELBV2TrustStore_basic === CONT TestAccELBV2TrustStore_tags --- PASS: TestAccELBV2TrustStore_basic (51.94s) --- PASS: TestAccELBV2TrustStore_tags (92.76s) PASS ok github.com/hashicorp/terraform-provider-aws/internal/service/elbv2 98.276s From 06f9bbf1cc96a7e47fa942d3902138b78e2b1ffd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 14:30:53 -0500 Subject: [PATCH 10/16] r/aws_lb_trust_store: Additional acceptance tests. --- internal/service/elbv2/trust_store_test.go | 110 ++++++++++++++++++++- 1 file changed, 106 insertions(+), 4 deletions(-) diff --git a/internal/service/elbv2/trust_store_test.go b/internal/service/elbv2/trust_store_test.go index 3cbec8dc3e5..e312b4f4ba2 100644 --- a/internal/service/elbv2/trust_store_test.go +++ b/internal/service/elbv2/trust_store_test.go @@ -35,9 +35,92 @@ func TestAccELBV2TrustStore_basic(t *testing.T) { Config: testAccTrustStoreConfig_basic(rName), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckTrustStoreExists(ctx, resourceName, &conf), - resource.TestCheckResourceAttrSet(resourceName, "arn"), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "elasticloadbalancing", regexache.MustCompile("truststore/.+$")), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestAccELBV2TrustStore_disappears(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.TrustStore + resourceName := "aws_lb_trust_store.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelbv2.ResourceTrustStore(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccELBV2TrustStore_nameGenerated(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.TrustStore + resourceName := "aws_lb_trust_store.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_nameGenerated(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + acctest.CheckResourceAttrNameGeneratedWithPrefix(resourceName, "name", "tf-"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestAccELBV2TrustStore_namePrefix(t *testing.T) { + ctx := acctest.Context(t) + var conf elbv2.TrustStore + resourceName := "aws_lb_trust_store.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckTrustStoreDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccTrustStoreConfig_namePrefix(rName, "tf-px-"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckTrustStoreExists(ctx, resourceName, &conf), + acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", "tf-px-"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-px-"), ), }, { @@ -63,7 +146,7 @@ func TestAccELBV2TrustStore_tags(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccTrustStoreConfig_tags1(rName, "key1", "value1"), - Check: resource.ComposeAggregateTestCheckFunc( + Check: resource.ComposeTestCheckFunc( testAccCheckTrustStoreExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), @@ -76,7 +159,7 @@ func TestAccELBV2TrustStore_tags(t *testing.T) { }, { Config: testAccTrustStoreConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), - Check: resource.ComposeAggregateTestCheckFunc( + Check: resource.ComposeTestCheckFunc( testAccCheckTrustStoreExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), @@ -85,7 +168,7 @@ func TestAccELBV2TrustStore_tags(t *testing.T) { }, { Config: testAccTrustStoreConfig_tags1(rName, "key2", "value2"), - Check: resource.ComposeAggregateTestCheckFunc( + Check: resource.ComposeTestCheckFunc( testAccCheckTrustStoreExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -223,6 +306,25 @@ resource "aws_lb_trust_store" "test" { `, rName)) } +func testAccTrustStoreConfig_nameGenerated(rName string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_baseS3BucketCA(rName), ` +resource "aws_lb_trust_store" "test" { + ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket + ca_certificates_bundle_s3_key = aws_s3_object.test.key +} +`) +} + +func testAccTrustStoreConfig_namePrefix(rName, namePrefix string) string { + return acctest.ConfigCompose(testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` +resource "aws_lb_trust_store" "test" { + name_prefix = %[1]q + ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket + ca_certificates_bundle_s3_key = aws_s3_object.test.key +} +`, namePrefix)) +} + func testAccTrustStoreConfig_tags1(rName, tagKey1, tagValue1 string) string { return acctest.ConfigCompose(testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` resource "aws_lb_trust_store" "test" { From a9e96796d9b873706ca1b18dca9a260ce0f6eb2d Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 14:41:07 -0500 Subject: [PATCH 11/16] d/aws_lb_trust_store: Tidy up. --- internal/service/elbv2/service_package_gen.go | 5 +-- .../service/elbv2/trust_store_data_source.go | 41 ++++--------------- .../elbv2/trust_store_data_source_test.go | 2 +- 3 files changed, 9 insertions(+), 39 deletions(-) diff --git a/internal/service/elbv2/service_package_gen.go b/internal/service/elbv2/service_package_gen.go index b4fc8ea1d9a..4e44c2632eb 100644 --- a/internal/service/elbv2/service_package_gen.go +++ b/internal/service/elbv2/service_package_gen.go @@ -37,10 +37,6 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac Factory: DataSourceTargetGroup, TypeName: "aws_alb_target_group", }, - { - Factory: DataSourceTrustStore, - TypeName: "aws_alb_trust_store", - }, { Factory: DataSourceLoadBalancer, TypeName: "aws_lb", @@ -60,6 +56,7 @@ func (p *servicePackage) SDKDataSources(ctx context.Context) []*types.ServicePac { Factory: DataSourceTrustStore, TypeName: "aws_lb_trust_store", + Name: "Trust Store", }, { Factory: DataSourceLoadBalancers, diff --git a/internal/service/elbv2/trust_store_data_source.go b/internal/service/elbv2/trust_store_data_source.go index 107f5377f22..ba3b92c8237 100644 --- a/internal/service/elbv2/trust_store_data_source.go +++ b/internal/service/elbv2/trust_store_data_source.go @@ -5,7 +5,6 @@ package elbv2 import ( "context" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elbv2" @@ -13,25 +12,21 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKDataSource("aws_lb_trust_store") -// @SDKDataSource("aws_alb_trust_store") +// @SDKDataSource("aws_lb_trust_store", name="Trust Store") func DataSourceTrustStore() *schema.Resource { return &schema.Resource{ ReadWithoutTimeout: dataSourceTrustStoreRead, - Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(20 * time.Minute), - }, - Schema: map[string]*schema.Schema{ - "name": { + "arn": { Type: schema.TypeString, Optional: true, Computed: true, }, - "arn": { + "name": { Type: schema.TypeString, Optional: true, Computed: true, @@ -52,37 +47,15 @@ func dataSourceTrustStoreRead(ctx context.Context, d *schema.ResourceData, meta input.Names = aws.StringSlice([]string{v.(string)}) } - var results []*elbv2.TrustStore - - err := conn.DescribeTrustStoresPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoresOutput, lastPage bool) bool { - if page == nil { - return !lastPage - } - - for _, l := range page.TrustStores { - if l == nil { - continue - } - - results = append(results, l) - } - - return !lastPage - }) + trustStore, err := findTrustStore(ctx, conn, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Listener: %s", err) - } - - if len(results) != 1 { - return sdkdiag.AppendErrorf(diags, "Search returned %d results, please revise so only one is returned", len(results)) + return sdkdiag.AppendFromErr(diags, tfresource.SingularDataSourceFindError("ELBv2 Trust Store", err)) } - trustStore := results[0] - d.SetId(aws.StringValue(trustStore.TrustStoreArn)) - d.Set("name", trustStore.Name) d.Set("arn", trustStore.TrustStoreArn) + d.Set("name", trustStore.Name) return diags } diff --git a/internal/service/elbv2/trust_store_data_source_test.go b/internal/service/elbv2/trust_store_data_source_test.go index 2853c86e4ef..cdc0bf1e4e1 100644 --- a/internal/service/elbv2/trust_store_data_source_test.go +++ b/internal/service/elbv2/trust_store_data_source_test.go @@ -19,6 +19,7 @@ func TestAccELBV2TrustStoreDataSource_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) datasourceNameByName := "data.aws_lb_trust_store.named" datasourceNameByArn := "data.aws_lb_trust_store.with_arn" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -55,7 +56,6 @@ resource "aws_lb_trust_store" "test" { func testAccTrustStoreDataSourceConfig_withName(rName string) string { return acctest.ConfigCompose(testAccTrustStoreDataSourceConfig_base(rName), fmt.Sprintf(` - data "aws_lb_trust_store" "named" { name = %[1]q depends_on = [aws_lb_trust_store.test] From a1a569d1a1c7d48b32cd79299725268f516da9cb Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 16:57:24 -0500 Subject: [PATCH 12/16] r/aws_lb_trust_store_revocation: Tidy up. --- .../service/elbv2/trust_store_revocation.go | 204 +++++++----------- .../r/lb_trust_store_revocation.html.markdown | 14 +- 2 files changed, 79 insertions(+), 139 deletions(-) diff --git a/internal/service/elbv2/trust_store_revocation.go b/internal/service/elbv2/trust_store_revocation.go index ef9621a1beb..beea8e3e473 100644 --- a/internal/service/elbv2/trust_store_revocation.go +++ b/internal/service/elbv2/trust_store_revocation.go @@ -5,10 +5,8 @@ package elbv2 import ( "context" - "fmt" "log" "strconv" - "strings" "time" "github.com/aws/aws-sdk-go/aws" @@ -19,33 +17,32 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) // @SDKResource("aws_lb_trust_store_revocation", name="Trust Store Revocation") -// @SDKResource("aws_alb_trust_store_revocation", name="Trust Store Revocation") func ResourceTrustStoreRevocation() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceTrustStoreRevocationCreate, ReadWithoutTimeout: resourceTrustStoreRevocationRead, - UpdateWithoutTimeout: nil, DeleteWithoutTimeout: resourceTrustStoreRevocationDelete, Importer: &schema.ResourceImporter{ - StateContext: resourceTrustStoreRevocationImport, + StateContext: schema.ImportStatePassthroughContext, }, + Timeouts: &schema.ResourceTimeout{ - Read: schema.DefaultTimeout(10 * time.Minute), + Create: schema.DefaultTimeout(2 * time.Minute), }, Schema: map[string]*schema.Schema{ - "trust_store_arn": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: verify.ValidARN, + "revocation_id": { + Type: schema.TypeInt, + Computed: true, }, "revocations_s3_bucket": { Type: schema.TypeString, @@ -65,60 +62,52 @@ func ResourceTrustStoreRevocation() *schema.Resource { ForceNew: true, ValidateFunc: validation.NoZeroValues, }, - "revocation_id": { - Type: schema.TypeInt, - Computed: true, + "trust_store_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidARN, }, }, } } +const ( + trustStoreRevocationResourceIDPartCount = 2 +) + func resourceTrustStoreRevocationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - trustStoreARN := d.Get("trust_store_arn").(string) - - _, err := FindTrustStoreByARN(ctx, conn, trustStoreARN) - - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store Revocations, Trust Store not found: %s: %s", trustStoreARN, err) - } - - input := &elbv2.AddTrustStoreRevocationsInput{ - TrustStoreArn: aws.String(trustStoreARN), - RevocationContents: make([]*elbv2.RevocationContent, 1), - } - s3Bucket := d.Get("revocations_s3_bucket").(string) s3Key := d.Get("revocations_s3_key").(string) - - input.RevocationContents[0] = &elbv2.RevocationContent{ - S3Bucket: aws.String(s3Bucket), - S3Key: aws.String(s3Key), + trustStoreARN := d.Get("trust_store_arn").(string) + input := &elbv2.AddTrustStoreRevocationsInput{ + RevocationContents: []*elbv2.RevocationContent{{ + S3Bucket: aws.String(s3Bucket), + S3Key: aws.String(s3Key), + }}, + TrustStoreArn: aws.String(trustStoreARN), } - if d.Get("revocations_s3_object_version").(string) != "" { - input.RevocationContents[0].S3ObjectVersion = aws.String(d.Get("revocations_s3_object_version").(string)) + if v, ok := d.GetOk("revocations_s3_object_version"); ok { + input.RevocationContents[0].S3ObjectVersion = aws.String(v.(string)) } output, err := conn.AddTrustStoreRevocationsWithContext(ctx, input) if err != nil { - sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store Revocations from %s s3://%s/%s: %s", trustStoreARN, s3Bucket, s3Key, err) - } - - if len(output.TrustStoreRevocations) == 0 { - return sdkdiag.AppendErrorf(diags, "creatingTrust Store Revocations: no revocations returned in response") + sdkdiag.AppendErrorf(diags, "creating ELBv2 Trust Store (%s) Revocation (s3://%s/%s): %s", trustStoreARN, s3Bucket, s3Key, err) } revocationID := aws.Int64Value(output.TrustStoreRevocations[0].RevocationId) - d.SetId(TrustStoreRevocationCreateID(aws.StringValue(output.TrustStoreRevocations[0].TrustStoreArn), revocationID)) + id := errs.Must(flex.FlattenResourceId([]string{trustStoreARN, strconv.FormatInt(revocationID, 10)}, trustStoreRevocationResourceIDPartCount, false)) - d.Set("revocation_id", revocationID) + d.SetId(id) - _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { - return FindTrustStoreRevocation(ctx, conn, d.Id()) + _, err = tfresource.RetryWhenNotFound(ctx, d.Timeout(schema.TimeoutCreate), func() (interface{}, error) { + return FindTrustStoreRevocationByTwoPartKey(ctx, conn, trustStoreARN, revocationID) }) if err != nil { @@ -132,9 +121,14 @@ func resourceTrustStoreRevocationRead(ctx context.Context, d *schema.ResourceDat var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - _, err := tfresource.RetryWhenNewResourceNotFound(ctx, propagationTimeout, func() (interface{}, error) { - return FindTrustStoreRevocation(ctx, conn, d.Id()) - }, d.IsNewResource()) + parts, err := flex.ExpandResourceId(d.Id(), trustStoreRevocationResourceIDPartCount, false) + if err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + trustStoreARN := parts[0] + revocationID := errs.Must(strconv.ParseInt(parts[1], 10, 64)) + revocation, err := FindTrustStoreRevocationByTwoPartKey(ctx, conn, trustStoreARN, revocationID) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ELBv2 Trust Store Revocation %s not found, removing from state", d.Id()) @@ -146,6 +140,9 @@ func resourceTrustStoreRevocationRead(ctx context.Context, d *schema.ResourceDat return sdkdiag.AppendErrorf(diags, "reading ELBv2 Trust Store Revocation (%s): %s", d.Id(), err) } + d.Set("revocation_id", revocation.RevocationId) + d.Set("trust_store_arn", revocation.TrustStoreArn) + return diags } @@ -157,105 +154,69 @@ func resourceTrustStoreRevocationDelete(ctx context.Context, d *schema.ResourceD trustStoreDeleteTimeout = 2 * time.Minute ) - parsed, err := parseTrustStoreRevocationID(d.Id()) - + parts, err := flex.ExpandResourceId(d.Id(), trustStoreRevocationResourceIDPartCount, false) if err != nil { - log.Printf("[DEBUG] error parsing Trust Store Revocation Id: %s", d.Id()) - return sdkdiag.AppendErrorf(diags, "deleting Trust Store: %s", err) + return sdkdiag.AppendFromErr(diags, err) } - input := &elbv2.RemoveTrustStoreRevocationsInput{ - TrustStoreArn: aws.String(parsed.TrustStoreARN), - RevocationIds: make([]*int64, 1), - } - input.RevocationIds[0] = aws.Int64(parsed.RevocationID) + trustStoreARN := parts[0] + revocationID := errs.Must(strconv.ParseInt(parts[1], 10, 64)) - log.Printf("[DEBUG] Deleting Trust Store Revocation (%s): %s", d.Id(), input) - err = retry.RetryContext(ctx, trustStoreDeleteTimeout, func() *retry.RetryError { - _, err := conn.RemoveTrustStoreRevocationsWithContext(ctx, input) - - if err != nil { - return retry.NonRetryableError(err) - } - - return nil + log.Printf("[DEBUG] Deleting ELBv2 Trust Store Revocation: %s", d.Id()) + _, err = conn.RemoveTrustStoreRevocationsWithContext(ctx, &elbv2.RemoveTrustStoreRevocationsInput{ + RevocationIds: aws.Int64Slice([]int64{revocationID}), + TrustStoreArn: aws.String(trustStoreARN), }) - if tfresource.TimedOut(err) { - _, err = conn.RemoveTrustStoreRevocationsWithContext(ctx, input) - } - if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Trust Store Revocation: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting ELBv2 Trust Store Revocation (%s): %s", d.Id(), err) } return diags } -const trustStoreRevocationIDSeparator = "_" - -func parseTrustStoreRevocationID(id string) (*TrustStoreRevocationID, error) { - invalidIDError := func(msg string) error { - return fmt.Errorf("unexpected format for ID (%q), expected TRUSTSTOREARN_REVOCATIONID: %s", id, msg) - } - - parts := strings.Split(id, trustStoreRevocationIDSeparator) - - if len(parts) != 2 { - return nil, invalidIDError("id should have two parts") +func FindTrustStoreRevocationByTwoPartKey(ctx context.Context, conn *elbv2.ELBV2, trustStoreARN string, revocationID int64) (*elbv2.DescribeTrustStoreRevocation, error) { + input := &elbv2.DescribeTrustStoreRevocationsInput{ + RevocationIds: aws.Int64Slice([]int64{revocationID}), + TrustStoreArn: aws.String(trustStoreARN), } - - revocationID, err := strconv.ParseInt(parts[1], 10, 64) + output, err := findTrustStoreRevocation(ctx, conn, input) if err != nil { - return nil, invalidIDError("failed to parse revocationID") + return nil, err } - result := &TrustStoreRevocationID{ - TrustStoreARN: parts[0], - RevocationID: revocationID, + // Eventual consistency check. + if aws.StringValue(output.TrustStoreArn) != trustStoreARN || aws.Int64Value(output.RevocationId) != revocationID { + return nil, &retry.NotFoundError{ + LastRequest: input, + } } - return result, nil + return output, nil } -func resourceTrustStoreRevocationImport(_ context.Context, d *schema.ResourceData, _ interface{}) ([]*schema.ResourceData, error) { - parsed, err := parseTrustStoreRevocationID(d.Id()) +func findTrustStoreRevocation(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoreRevocationsInput) (*elbv2.DescribeTrustStoreRevocation, error) { + output, err := findTrustStoreRevocations(ctx, conn, input) + if err != nil { return nil, err } - d.Set("trust_store_arn", parsed.TrustStoreARN) - d.Set("revocation_id", parsed.RevocationID) - - return []*schema.ResourceData{d}, nil + return tfresource.AssertSinglePtrResult(output) } -type TrustStoreRevocationID struct { - TrustStoreARN string - RevocationID int64 -} - -func FindTrustStoreRevocation(ctx context.Context, conn *elbv2.ELBV2, id string) (*elbv2.DescribeTrustStoreRevocation, error) { - parsed, err := parseTrustStoreRevocationID(id) - if err != nil { - log.Printf("[DEBUG] error parsing Trust Store Revocation Id: %s", id) - return nil, err - } - - input := &elbv2.DescribeTrustStoreRevocationsInput{ - TrustStoreArn: aws.String(parsed.TrustStoreARN), - } - var matched []*elbv2.DescribeTrustStoreRevocation +func findTrustStoreRevocations(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeTrustStoreRevocationsInput) ([]*elbv2.DescribeTrustStoreRevocation, error) { + var output []*elbv2.DescribeTrustStoreRevocation - err = conn.DescribeTrustStoreRevocationsPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoreRevocationsOutput, lastPage bool) bool { + err := conn.DescribeTrustStoreRevocationsPagesWithContext(ctx, input, func(page *elbv2.DescribeTrustStoreRevocationsOutput, lastPage bool) bool { if page == nil { return !lastPage } for _, v := range page.TrustStoreRevocations { - if v != nil && aws.Int64Value(v.RevocationId) == parsed.RevocationID { - matched = append(matched, v) + if v != nil { + output = append(output, v) } } @@ -273,24 +234,5 @@ func FindTrustStoreRevocation(ctx context.Context, conn *elbv2.ELBV2, id string) return nil, err } - count := len(matched) - - if count == 0 { - return nil, nil - } - - if count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return matched[0], nil -} - -func TrustStoreRevocationCreateID(trustStoreARN string, revocationID int64) string { - return fmt.Sprintf( - "%s%s%d", - trustStoreARN, - trustStoreRevocationIDSeparator, - revocationID, - ) + return output, nil } diff --git a/website/docs/r/lb_trust_store_revocation.html.markdown b/website/docs/r/lb_trust_store_revocation.html.markdown index ee5d6893428..a4c9b387220 100644 --- a/website/docs/r/lb_trust_store_revocation.html.markdown +++ b/website/docs/r/lb_trust_store_revocation.html.markdown @@ -1,7 +1,7 @@ --- subcategory: "ELB (Elastic Load Balancing)" layout: "aws" -page_title: "AWS: aws_alb_trust_store_revocation" +page_title: "AWS: aws_lb_trust_store_revocation" description: |- Provides a Trust Store Revocation resource for use with Load Balancers. --- @@ -10,8 +10,6 @@ description: |- Provides a ELBv2 Trust Store Revocation for use with Application Load Balancer Listener resources. -~> **Note:** `aws_alb_trust_store_revocation` is known as `aws_lb_trust_store_revocation`. The functionality is identical. - ## Example Usage ### Trust Store With Revocations @@ -49,21 +47,21 @@ This resource supports the following arguments: This resource exports the following attributes in addition to the arguments above: * `revocation_id` - AWS assigned RevocationId, (number). -* `id` - "combination of the Trust Store ARN and RevocationId `${trust_store_arn}_{revocation_id}`" +* `id` - "combination of the Trust Store ARN and RevocationId `${trust_store_arn},{revocation_id}`" ## Import -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Trust Stores using their ARN. For example: +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import Trust Store Revocations using their ARN. For example: ```terraform import { to = aws_lb_trust_store_revocation.example - id = "arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314_6" + id = "arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314,6" } ``` -Using `terraform import`, import Target Groups using their ARN. For example: +Using `terraform import`, import Trust Store Revocations using their ARN. For example: ```console -% terraform import aws_lb_trust_store_revocation.example arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314_6 +% terraform import aws_lb_trust_store_revocation.example arn:aws:elasticloadbalancing:us-west-2:187416307283:truststore/my-trust-store/20cfe21448b66314,6 ``` From 5edd6f91a199aa5991fe2e479df59ab3e1f5f463 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Thu, 30 Nov 2023 17:03:07 -0500 Subject: [PATCH 13/16] r/aws_lb_trust_store_revocation: Tidy up acceptance tests. --- .../elbv2/trust_store_revocation_test.go | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/internal/service/elbv2/trust_store_revocation_test.go b/internal/service/elbv2/trust_store_revocation_test.go index f4fc3806073..132022675b6 100644 --- a/internal/service/elbv2/trust_store_revocation_test.go +++ b/internal/service/elbv2/trust_store_revocation_test.go @@ -5,18 +5,18 @@ package elbv2_test import ( "context" - "errors" "fmt" + "strconv" "testing" "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" 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" tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBV2TrustStoreRevocation_basic(t *testing.T) { @@ -48,30 +48,30 @@ func TestAccELBV2TrustStoreRevocation_basic(t *testing.T) { }) } -func testAccCheckTrustStoreRevocationExists(ctx context.Context, n string, res *elbv2.DescribeTrustStoreRevocation) resource.TestCheckFunc { +func testAccCheckTrustStoreRevocationExists(ctx context.Context, n string, v *elbv2.DescribeTrustStoreRevocation) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No Trust Store Revocation ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - revocation, err := tfelbv2.FindTrustStoreRevocation(ctx, conn, rs.Primary.ID) + trustStoreARN := rs.Primary.Attributes["trust_store_arn"] + revocationID, err := strconv.ParseInt(rs.Primary.Attributes["revocation_id"], 10, 64) if err != nil { - return fmt.Errorf("reading ELBv2 Trust Store Revocation (%s): %w", rs.Primary.ID, err) + return err } - if revocation == nil { - return fmt.Errorf("ELBv2 Trust Store Revocation (%s) not found", rs.Primary.ID) + output, err := tfelbv2.FindTrustStoreRevocationByTwoPartKey(ctx, conn, trustStoreARN, revocationID) + + if err != nil { + return err } - *res = *revocation + *v = *output + return nil } } @@ -85,21 +85,24 @@ func testAccCheckTrustStoreRevocationDestroy(ctx context.Context) resource.TestC continue } - revocation, err := tfelbv2.FindTrustStoreRevocation(ctx, conn, rs.Primary.ID) - - if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeTrustStoreNotFoundException) { - continue - } + trustStoreARN := rs.Primary.Attributes["trust_store_arn"] + revocationID, err := strconv.ParseInt(rs.Primary.Attributes["revocation_id"], 10, 64) if err != nil { - return fmt.Errorf("reading ELBv2 Trust Store Revocation (%s): %w", rs.Primary.ID, err) + return err } - if revocation == nil { + _, err = tfelbv2.FindTrustStoreRevocationByTwoPartKey(ctx, conn, trustStoreARN, revocationID) + + if tfresource.NotFound(err) { continue } - return fmt.Errorf("ELBv2 Trust Store Revocation %q still exists", rs.Primary.ID) + if err != nil { + return err + } + + return fmt.Errorf("ELBv2 Trust Store Revocation %s still exists", rs.Primary.ID) } return nil @@ -114,9 +117,7 @@ resource "aws_lb_trust_store" "test" { ca_certificates_bundle_s3_key = aws_s3_object.test.key } - resource "aws_s3_object" "crl" { - bucket = aws_s3_bucket.test.bucket key = "%[1]s-crl.pem" content = < Date: Thu, 30 Nov 2023 17:12:44 -0500 Subject: [PATCH 14/16] r/aws_lb_listener: Tweak acceptance tests. --- internal/service/elbv2/listener_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/service/elbv2/listener_test.go b/internal/service/elbv2/listener_test.go index 7b3d893531a..cc1cd4a967c 100644 --- a/internal/service/elbv2/listener_test.go +++ b/internal/service/elbv2/listener_test.go @@ -47,7 +47,8 @@ func TestAccELBV2Listener_basic(t *testing.T) { resource.TestCheckResourceAttrPair(resourceName, "default_action.0.target_group_arn", "aws_lb_target_group.test", "arn"), resource.TestCheckResourceAttr(resourceName, "default_action.0.redirect.#", "0"), resource.TestCheckResourceAttr(resourceName, "default_action.0.fixed_response.#", "0"), - resource.TestCheckResourceAttr(resourceName, "tags.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "mutual_authentication.#", "0"), ), }, { @@ -319,6 +320,7 @@ func TestAccELBV2Listener_mutualAuthentication(t *testing.T) { Config: testAccListenerConfig_mutualAuthentication(rName, key, certificate), Check: resource.ComposeAggregateTestCheckFunc( testAccCheckListenerExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "mutual_authentication.#", "1"), resource.TestCheckResourceAttr(resourceName, "mutual_authentication.0.mode", "verify"), resource.TestCheckResourceAttr(resourceName, "mutual_authentication.0.ignore_client_certificate_expiry", "false"), resource.TestCheckResourceAttrPair(resourceName, "mutual_authentication.0.trust_store_arn", "aws_lb_trust_store.test", "arn"), @@ -1227,14 +1229,14 @@ resource "aws_internet_gateway" "test" { func testAccListenerConfig_mutualAuthentication(rName string, key, certificate string) string { return acctest.ConfigCompose( testAccListenerConfig_base(rName), - testAccTrustStoreConfig_baseS3BucketCA(rName), fmt.Sprintf(` + testAccTrustStoreConfig_baseS3BucketCA(rName), + fmt.Sprintf(` resource "aws_lb_trust_store" "test" { name = %[1]q ca_certificates_bundle_s3_bucket = aws_s3_bucket.test.bucket ca_certificates_bundle_s3_key = aws_s3_object.test.key } - resource "aws_lb_listener" "test" { load_balancer_arn = aws_lb.test.id protocol = "HTTPS" @@ -1294,8 +1296,6 @@ resource "aws_iam_server_certificate" "test" { certificate_body = "%[2]s" private_key = "%[3]s" } - - `, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key))) } From 6daab53147d8480ef5e9c0ad81ce572e6a647b26 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Dec 2023 08:16:04 -0500 Subject: [PATCH 15/16] Run 'make gen'. --- internal/service/elbv2/service_package_gen.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/service/elbv2/service_package_gen.go b/internal/service/elbv2/service_package_gen.go index 4e44c2632eb..7180fb36b6f 100644 --- a/internal/service/elbv2/service_package_gen.go +++ b/internal/service/elbv2/service_package_gen.go @@ -107,11 +107,6 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*types.ServicePacka Factory: ResourceTargetGroupAttachment, TypeName: "aws_alb_target_group_attachment", }, - { - Factory: ResourceTrustStoreRevocation, - TypeName: "aws_alb_trust_store_revocation", - Name: "Trust Store Revocation", - }, { Factory: ResourceLoadBalancer, TypeName: "aws_lb", From 20d3854c8acf036a3abcaa75344aca2f563be405 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Fri, 1 Dec 2023 08:17:22 -0500 Subject: [PATCH 16/16] Fix golangci-lint 'unused'. --- internal/service/elbv2/trust_store_revocation.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/service/elbv2/trust_store_revocation.go b/internal/service/elbv2/trust_store_revocation.go index beea8e3e473..ca39bb8ee9f 100644 --- a/internal/service/elbv2/trust_store_revocation.go +++ b/internal/service/elbv2/trust_store_revocation.go @@ -150,10 +150,6 @@ func resourceTrustStoreRevocationDelete(ctx context.Context, d *schema.ResourceD var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - const ( - trustStoreDeleteTimeout = 2 * time.Minute - ) - parts, err := flex.ExpandResourceId(d.Id(), trustStoreRevocationResourceIDPartCount, false) if err != nil { return sdkdiag.AppendFromErr(diags, err)