diff --git a/.changelog/34135.txt b/.changelog/34135.txt new file mode 100644 index 00000000000..649c4a1ec86 --- /dev/null +++ b/.changelog/34135.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_lb: Fix `InvalidConfigurationRequest: Load balancer attribute key 'dns_record.client_routing_policy' is not supported on load balancers with type 'network'` errors on resource Create in AWS GovCloud (US) +``` + +```release-note:enhancement +data-source/aws_lb: Add `dns_record_client_routing_policy` attribute +``` \ No newline at end of file diff --git a/internal/flex/flex.go b/internal/flex/flex.go index b020e32ba73..34c417583b1 100644 --- a/internal/flex/flex.go +++ b/internal/flex/flex.go @@ -289,12 +289,29 @@ func FlattenResourceId(idParts []string, partCount int, allowEmptyPart bool) (st return strings.Join(idParts, ResourceIdSeparator), nil } +// BoolValueToString converts a Go bool value to a string pointer. +func BoolValueToString(v bool) *string { + return aws.String(strconv.FormatBool(v)) +} + // StringToBoolValue converts a string pointer to a Go bool value. // Only the string "true" is converted to true, all other values return false. func StringToBoolValue(v *string) bool { return aws.StringValue(v) == strconv.FormatBool(true) } +// IntValueToString converts a Go int value to a string pointer. +func IntValueToString(v int) *string { + return aws.String(strconv.Itoa(v)) +} + +// StringToIntValue converts a string pointer to a Go int value. +// Invalid integer strings are converted to 0. +func StringToIntValue(v *string) int { + i, _ := strconv.Atoi(aws.StringValue(v)) + return i +} + // Takes a string of resource attributes separated by the ResourceIdSeparator constant // returns the number of parts func ResourceIdPartCount(id string) int { diff --git a/internal/service/elbv2/const.go b/internal/service/elbv2/const.go index 1a0c5b661ce..ebd7a7df1d8 100644 --- a/internal/service/elbv2/const.go +++ b/internal/service/elbv2/const.go @@ -4,7 +4,79 @@ package elbv2 const ( - ErrValidationError = "ValidationError" + errCodeValidationError = "ValidationError" - TagsOnCreationErrMessage = "cannot specify tags on creation" + tagsOnCreationErrMessage = "cannot specify tags on creation" ) + +// See https://docs.aws.amazon.com/elasticloadbalancing/latest/APIReference/API_LoadBalancerAttribute.html#API_LoadBalancerAttribute_Contents. +const ( + // The following attributes are supported by all load balancers: + loadBalancerAttributeDeletionProtectionEnabled = "deletion_protection.enabled" + loadBalancerAttributeLoadBalancingCrossZoneEnabled = "load_balancing.cross_zone.enabled" + + // The following attributes are supported by both Application Load Balancers and Network Load Balancers: + loadBalancerAttributeAccessLogsS3Enabled = "access_logs.s3.enabled" + loadBalancerAttributeAccessLogsS3Bucket = "access_logs.s3.bucket" + loadBalancerAttributeAccessLogsS3Prefix = "access_logs.s3.prefix" + loadBalancerAttributeIPv6DenyAllIGWTraffic = "ipv6.deny_all_igw_traffic" + + // The following attributes are supported by only Application Load Balancers: + loadBalancerAttributeIdleTimeoutTimeoutSeconds = "idle_timeout.timeout_seconds" + loadBalancerAttributeConnectionLogsS3Enabled = "connection_logs.s3.enabled" + loadBalancerAttributeConnectionLogsS3Bucket = "connection_logs.s3.bucket" + loadBalancerAttributeConnectionLogsS3Prefix = "connection_logs.s3.prefix" + loadBalancerAttributeRoutingHTTPDesyncMitigationMode = "routing.http.desync_mitigation_mode" + loadBalancerAttributeRoutingHTTPDropInvalidHeaderFieldsEnabled = "routing.http.drop_invalid_header_fields.enabled" + loadBalancerAttributeRoutingHTTPPreserveHostHeaderEnabled = "routing.http.preserve_host_header.enabled" + loadBalancerAttributeRoutingHTTPXAmznTLSVersionAndCipherSuiteEnabled = "routing.http.x_amzn_tls_version_and_cipher_suite.enabled" + loadBalancerAttributeRoutingHTTPXFFClientPortEnabled = "routing.http.xff_client_port.enabled" + loadBalancerAttributeRoutingHTTPXFFHeaderProcessingMode = "routing.http.xff_header_processing.mode" + loadBalancerAttributeRoutingHTTP2Enabled = "routing.http2.enabled" + loadBalancerAttributeWAFFailOpenEnabled = "waf.fail_open.enabled" + + // The following attributes are supported by only Network Load Balancers: + loadBalancerAttributeDNSRecordClientRoutingPolicy = "dns_record.client_routing_policy" +) + +const ( + httpDesyncMitigationModeMonitor = "monitor" + httpDesyncMitigationModeDefensive = "defensive" + httpDesyncMitigationModeStrictest = "strictest" +) + +func httpDesyncMitigationMode_Values() []string { + return []string{ + httpDesyncMitigationModeMonitor, + httpDesyncMitigationModeDefensive, + httpDesyncMitigationModeStrictest, + } +} + +const ( + dnsRecordClientRoutingPolicyAvailabilityZoneAffinity = "availability_zone_affinity" + dnsRecordClientRoutingPolicyPartialAvailabilityZoneAffinity = "partial_availability_zone_affinity" + dnsRecordClientRoutingPolicyAnyAvailabilityZone = "any_availability_zone" +) + +func dnsRecordClientRoutingPolicy_Values() []string { + return []string{ + dnsRecordClientRoutingPolicyAvailabilityZoneAffinity, + dnsRecordClientRoutingPolicyPartialAvailabilityZoneAffinity, + dnsRecordClientRoutingPolicyAnyAvailabilityZone, + } +} + +const ( + httpXFFHeaderProcessingModeAppend = "append" + httpXFFHeaderProcessingModePreserve = "preserve" + httpXFFHeaderProcessingModeRemove = "remove" +) + +func httpXFFHeaderProcessingMode_Values() []string { + return []string{ + httpXFFHeaderProcessingModeAppend, + httpXFFHeaderProcessingModePreserve, + httpXFFHeaderProcessingModeRemove, + } +} diff --git a/internal/service/elbv2/listener.go b/internal/service/elbv2/listener.go index a9e297a703e..2ced20b09f3 100644 --- a/internal/service/elbv2/listener.go +++ b/internal/service/elbv2/listener.go @@ -480,7 +480,7 @@ func resourceListenerCreate(ctx context.Context, d *schema.ResourceData, meta in // Tags are not supported on creation with some load balancer types (i.e. Gateway) // Retry creation without tags - if input.Tags != nil && tfawserr.ErrMessageContains(err, ErrValidationError, TagsOnCreationErrMessage) { + if input.Tags != nil && tfawserr.ErrMessageContains(err, errCodeValidationError, tagsOnCreationErrMessage) { input.Tags = nil output, err = retryListenerCreate(ctx, conn, input, d.Timeout(schema.TimeoutCreate)) diff --git a/internal/service/elbv2/load_balancer.go b/internal/service/elbv2/load_balancer.go index 1416fb267e0..5ed5b623a11 100644 --- a/internal/service/elbv2/load_balancer.go +++ b/internal/service/elbv2/load_balancer.go @@ -4,20 +4,19 @@ package elbv2 import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports - "bytes" + "context" "errors" "fmt" "log" - "strconv" "time" "github.com/YakDriver/regexache" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/elbv2" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" - multierror "github.com/hashicorp/go-multierror" "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" @@ -34,6 +33,7 @@ import ( // nosemgrep:ci.semgrep.aws.multiple-service-imports "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" + "golang.org/x/exp/slices" ) // @SDKResource("aws_alb", name="Load Balancer") @@ -105,14 +105,10 @@ func ResourceLoadBalancer() *schema.Resource { ForceNew: true, }, "desync_mitigation_mode": { - Type: schema.TypeString, - Optional: true, - Default: "defensive", - ValidateFunc: validation.StringInSlice([]string{ - "monitor", - "defensive", - "strictest", - }, false), + Type: schema.TypeString, + Optional: true, + Default: httpDesyncMitigationModeDefensive, + ValidateFunc: validation.StringInSlice(httpDesyncMitigationMode_Values(), false), DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, "dns_name": { @@ -122,19 +118,15 @@ func ResourceLoadBalancer() *schema.Resource { "dns_record_client_routing_policy": { Type: schema.TypeString, Optional: true, - Default: "any_availability_zone", + Default: dnsRecordClientRoutingPolicyAnyAvailabilityZone, DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumNetwork), - ValidateFunc: validation.StringInSlice([]string{ - "availability_zone_affinity", - "partial_availability_zone_affinity", - "any_availability_zone", - }, false), + ValidateFunc: validation.StringInSlice(dnsRecordClientRoutingPolicy_Values(), false), }, "drop_invalid_header_fields": { Type: schema.TypeBool, Optional: true, Default: false, - DiffSuppressFunc: suppressIfLBType("network"), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, "enable_cross_zone_load_balancing": { Type: schema.TypeBool, @@ -151,7 +143,7 @@ func ResourceLoadBalancer() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: true, - DiffSuppressFunc: suppressIfLBType(elbv2.LoadBalancerTypeEnumNetwork), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, "enable_tls_version_and_cipher_suite_headers": { Type: schema.TypeBool, @@ -163,7 +155,7 @@ func ResourceLoadBalancer() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - DiffSuppressFunc: suppressIfLBType(elbv2.LoadBalancerTypeEnumNetwork), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, "enable_xff_client_port": { Type: schema.TypeBool, @@ -182,7 +174,7 @@ func ResourceLoadBalancer() *schema.Resource { Type: schema.TypeInt, Optional: true, Default: 60, - DiffSuppressFunc: suppressIfLBType(elbv2.LoadBalancerTypeEnumNetwork), + DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), }, "internal": { Type: schema.TypeBool, @@ -266,24 +258,12 @@ func ResourceLoadBalancer() *schema.Resource { }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string))) - if m["allocation_id"] != "" { - buf.WriteString(fmt.Sprintf("%s-", m["allocation_id"].(string))) - } - if m["private_ipv4_address"] != "" { - buf.WriteString(fmt.Sprintf("%s-", m["private_ipv4_address"].(string))) - } - return create.StringHashcode(buf.String()) - }, }, "subnets": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, names.AttrTags: tftags.TagsSchema(), names.AttrTagsAll: tftags.TagsSchemaComputed(), @@ -294,13 +274,9 @@ func ResourceLoadBalancer() *schema.Resource { "xff_header_processing_mode": { Type: schema.TypeString, Optional: true, - Default: "append", + Default: httpXFFHeaderProcessingModeAppend, DiffSuppressFunc: suppressIfLBTypeNot(elbv2.LoadBalancerTypeEnumApplication), - ValidateFunc: validation.StringInSlice([]string{ - "append", - "preserve", - "remove", - }, false), + ValidateFunc: validation.StringInSlice(httpXFFHeaderProcessingMode_Values(), false), }, "zone_id": { Type: schema.TypeString, @@ -310,15 +286,15 @@ func ResourceLoadBalancer() *schema.Resource { } } -func suppressIfLBType(t string) schema.SchemaDiffSuppressFunc { +func suppressIfLBType(types ...string) schema.SchemaDiffSuppressFunc { return func(k string, old string, new string, d *schema.ResourceData) bool { - return d.Get("load_balancer_type").(string) == t + return slices.Contains(types, d.Get("load_balancer_type").(string)) } } -func suppressIfLBTypeNot(t string) schema.SchemaDiffSuppressFunc { +func suppressIfLBTypeNot(types ...string) schema.SchemaDiffSuppressFunc { return func(k string, old string, new string, d *schema.ResourceData) bool { - return d.Get("load_balancer_type").(string) != t + return !slices.Contains(types, d.Get("load_balancer_type").(string)) } } @@ -331,7 +307,7 @@ func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, met create.WithConfiguredPrefix(d.Get("name_prefix").(string)), create.WithDefaultPrefix("tf-lb-"), ).Generate() - exist, err := FindLoadBalancer(ctx, conn, &elbv2.DescribeLoadBalancersInput{ + exist, err := findLoadBalancer(ctx, conn, &elbv2.DescribeLoadBalancersInput{ Names: aws.StringSlice([]string{name}), }) @@ -409,7 +385,56 @@ func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, met } } - return append(diags, resourceLoadBalancerUpdate(ctx, d, meta)...) + var attributes []*elbv2.LoadBalancerAttribute + + if lbType == elbv2.LoadBalancerTypeEnumApplication || lbType == elbv2.LoadBalancerTypeEnumNetwork { + if v, ok := d.GetOk("access_logs"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandLoadBalancerAccessLogsAttributes(v.([]interface{})[0].(map[string]interface{}), false)...) + } else { + attributes = append(attributes, &elbv2.LoadBalancerAttribute{ + Key: aws.String(loadBalancerAttributeAccessLogsS3Enabled), + Value: flex.BoolValueToString(false), + }) + } + } + + attributes = append(attributes, loadBalancerAttributes.expand(d, false)...) + + wait := false + if len(attributes) > 0 { + if err := modifyLoadBalancerAttributes(ctx, conn, d.Id(), attributes); err != nil { + return sdkdiag.AppendFromErr(diags, err) + } + + wait = true + } + + if v, ok := d.GetOk("enforce_security_group_inbound_rules_on_private_link_traffic"); ok && lbType == elbv2.LoadBalancerTypeEnumNetwork { + input := &elbv2.SetSecurityGroupsInput{ + EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic: aws.String(v.(string)), + LoadBalancerArn: aws.String(d.Id()), + } + + if v, ok := d.GetOk("security_groups"); ok { + input.SecurityGroups = flex.ExpandStringSet(v.(*schema.Set)) + } + + _, err := conn.SetSecurityGroupsWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELBv2 Load Balancer (%s) security groups: %s", d.Id(), err) + } + + wait = true + } + + if wait { + if _, err := waitLoadBalancerActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Load Balancer (%s) create: %s", d.Id(), err) + } + } + + return append(diags, resourceLoadBalancerRead(ctx, d, meta)...) } func resourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { @@ -428,9 +453,38 @@ func resourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "reading ELBv2 Load Balancer (%s): %s", d.Id(), err) } - if err := flattenResource(ctx, d, meta, lb); err != nil { - return sdkdiag.AppendFromErr(diags, err) + d.Set("arn", lb.LoadBalancerArn) + d.Set("arn_suffix", SuffixFromARN(lb.LoadBalancerArn)) + d.Set("customer_owned_ipv4_pool", lb.CustomerOwnedIpv4Pool) + d.Set("dns_name", lb.DNSName) + d.Set("enforce_security_group_inbound_rules_on_private_link_traffic", lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) + d.Set("internal", aws.StringValue(lb.Scheme) == elbv2.LoadBalancerSchemeEnumInternal) + d.Set("ip_address_type", lb.IpAddressType) + d.Set("load_balancer_type", lb.Type) + d.Set("name", lb.LoadBalancerName) + d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(lb.LoadBalancerName))) + d.Set("security_groups", aws.StringValueSlice(lb.SecurityGroups)) + if err := d.Set("subnet_mapping", flattenSubnetMappingsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting subnet_mapping: %s", err) + } + if err := d.Set("subnets", flattenSubnetsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting subnets: %s", err) + } + d.Set("vpc_id", lb.VpcId) + d.Set("zone_id", lb.CanonicalHostedZoneId) + + attributes, err := FindLoadBalancerAttributesByARN(ctx, conn, d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Load Balancer (%s) attributes: %s", d.Id(), err) + } + + if err := d.Set("access_logs", []interface{}{flattenLoadBalancerAccessLogsAttributes(attributes)}); err != nil { + return sdkdiag.AppendErrorf(diags, "setting access_logs: %s", err) } + + loadBalancerAttributes.flatten(d, attributes) + return diags } @@ -438,224 +492,74 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - attributes := make([]*elbv2.LoadBalancerAttribute, 0) + var attributes []*elbv2.LoadBalancerAttribute if d.HasChange("access_logs") { - logs := d.Get("access_logs").([]interface{}) - - if len(logs) == 1 && logs[0] != nil { - log := logs[0].(map[string]interface{}) - - enabled := log["enabled"].(bool) - - attributes = append(attributes, - &elbv2.LoadBalancerAttribute{ - Key: aws.String("access_logs.s3.enabled"), - Value: aws.String(strconv.FormatBool(enabled)), - }) - if enabled { - attributes = append(attributes, - &elbv2.LoadBalancerAttribute{ - Key: aws.String("access_logs.s3.bucket"), - Value: aws.String(log["bucket"].(string)), - }, - &elbv2.LoadBalancerAttribute{ - Key: aws.String("access_logs.s3.prefix"), - Value: aws.String(log["prefix"].(string)), - }) - } + if v, ok := d.GetOk("access_logs"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + attributes = append(attributes, expandLoadBalancerAccessLogsAttributes(v.([]interface{})[0].(map[string]interface{}), true)...) } else { attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("access_logs.s3.enabled"), - Value: aws.String("false"), - }) - } - } - - switch d.Get("load_balancer_type").(string) { - case elbv2.LoadBalancerTypeEnumApplication: - if d.HasChange("idle_timeout") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("idle_timeout.timeout_seconds"), - Value: aws.String(fmt.Sprintf("%d", d.Get("idle_timeout").(int))), - }) - } - - if d.HasChange("enable_http2") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http2.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("enable_http2").(bool))), - }) - } - - // The "waf.fail_open.enabled" attribute is not available in all AWS regions - // e.g. us-gov-east-1; thus, we can instead only modify the attribute as a result of d.HasChange() - // to avoid "ValidationError: Load balancer attribute key 'waf.fail_open.enabled' is not recognized" - // when modifying the attribute right after resource creation. - // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/22037 - if d.HasChange("enable_waf_fail_open") { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("waf.fail_open.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("enable_waf_fail_open").(bool))), - }) - } - - if d.HasChange("drop_invalid_header_fields") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.drop_invalid_header_fields.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("drop_invalid_header_fields").(bool))), - }) - } - - if d.HasChange("preserve_host_header") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.preserve_host_header.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("preserve_host_header").(bool))), - }) - } - - if d.HasChange("desync_mitigation_mode") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.desync_mitigation_mode"), - Value: aws.String(d.Get("desync_mitigation_mode").(string)), - }) - } - - if d.HasChange("enable_tls_version_and_cipher_suite_headers") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.x_amzn_tls_version_and_cipher_suite.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("enable_tls_version_and_cipher_suite_headers").(bool))), - }) - } - - if d.HasChange("enable_xff_client_port") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.xff_client_port.enabled"), - Value: aws.String(strconv.FormatBool(d.Get("enable_xff_client_port").(bool))), - }) - } - - if d.HasChange("xff_header_processing_mode") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("routing.http.xff_header_processing.mode"), - Value: aws.String(d.Get("xff_header_processing_mode").(string)), - }) - } - - case elbv2.LoadBalancerTypeEnumGateway: - if d.HasChange("enable_cross_zone_load_balancing") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("load_balancing.cross_zone.enabled"), - Value: aws.String(fmt.Sprintf("%t", d.Get("enable_cross_zone_load_balancing").(bool))), - }) - } - - case elbv2.LoadBalancerTypeEnumNetwork: - if d.HasChange("dns_record_client_routing_policy") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("dns_record.client_routing_policy"), - Value: aws.String(d.Get("dns_record_client_routing_policy").(string)), - }) - } - if d.HasChange("enable_cross_zone_load_balancing") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("load_balancing.cross_zone.enabled"), - Value: aws.String(fmt.Sprintf("%t", d.Get("enable_cross_zone_load_balancing").(bool))), + Key: aws.String(loadBalancerAttributeAccessLogsS3Enabled), + Value: flex.BoolValueToString(false), }) } } - if d.HasChange("enable_deletion_protection") || d.IsNewResource() { - attributes = append(attributes, &elbv2.LoadBalancerAttribute{ - Key: aws.String("deletion_protection.enabled"), - Value: aws.String(fmt.Sprintf("%t", d.Get("enable_deletion_protection").(bool))), - }) - } + attributes = append(attributes, loadBalancerAttributes.expand(d, true)...) - if len(attributes) != 0 { - input := &elbv2.ModifyLoadBalancerAttributesInput{ - LoadBalancerArn: aws.String(d.Id()), - Attributes: attributes, - } - - log.Printf("[DEBUG] ALB Modify Load Balancer Attributes Request: %#v", input) - - // Not all attributes are supported in all partitions (e.g., ISO) - var err error - for { - _, err = conn.ModifyLoadBalancerAttributesWithContext(ctx, input) - if err == nil { - break - } - - re := regexache.MustCompile(`attribute key ('|")?([^'" ]+)('|")? is not recognized`) - if sm := re.FindStringSubmatch(err.Error()); len(sm) > 1 { - log.Printf("[WARN] failed to modify Load Balancer (%s), unsupported attribute (%s): %s", d.Id(), sm[2], err) - input.Attributes = removeAttribute(input.Attributes, sm[2]) - continue - } - - break - } - - if err != nil { - return sdkdiag.AppendErrorf(diags, "failure configuring LB attributes: %s", err) + if len(attributes) > 0 { + if err := modifyLoadBalancerAttributes(ctx, conn, d.Id(), attributes); err != nil { + return sdkdiag.AppendFromErr(diags, err) } } if d.HasChanges("enforce_security_group_inbound_rules_on_private_link_traffic", "security_groups") { - sgs := flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)) - - params := &elbv2.SetSecurityGroupsInput{ + input := &elbv2.SetSecurityGroupsInput{ LoadBalancerArn: aws.String(d.Id()), - SecurityGroups: sgs, + SecurityGroups: flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)), } if v := d.Get("load_balancer_type"); v == elbv2.LoadBalancerTypeEnumNetwork { if v, ok := d.GetOk("enforce_security_group_inbound_rules_on_private_link_traffic"); ok { - params.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = aws.String(v.(string)) + input.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic = aws.String(v.(string)) } } - _, err := conn.SetSecurityGroupsWithContext(ctx, params) + _, err := conn.SetSecurityGroupsWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "failure Setting LB Security Groups: %s", err) + return sdkdiag.AppendErrorf(diags, "setting ELBv2 Load Balancer (%s) security groups: %s", d.Id(), err) } } - // subnets are assigned at Create; the 'change' here is an empty map for old - // and current subnets for new, so this change is redundant when the - // resource is just created, so we don't attempt if it is a newly created - // resource. - if d.HasChange("subnets") && !d.IsNewResource() { - subnets := flex.ExpandStringSet(d.Get("subnets").(*schema.Set)) - - params := &elbv2.SetSubnetsInput{ + if d.HasChange("subnets") { + input := &elbv2.SetSubnetsInput{ LoadBalancerArn: aws.String(d.Id()), - Subnets: subnets, + Subnets: flex.ExpandStringSet(d.Get("subnets").(*schema.Set)), } - _, err := conn.SetSubnetsWithContext(ctx, params) + _, err := conn.SetSubnetsWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "failure Setting LB Subnets: %s", err) + return sdkdiag.AppendErrorf(diags, "setting ELBv2 Load Balancer (%s) subnets: %s", d.Id(), err) } } if d.HasChange("ip_address_type") { - params := &elbv2.SetIpAddressTypeInput{ - LoadBalancerArn: aws.String(d.Id()), + input := &elbv2.SetIpAddressTypeInput{ IpAddressType: aws.String(d.Get("ip_address_type").(string)), + LoadBalancerArn: aws.String(d.Id()), } - _, err := conn.SetIpAddressTypeWithContext(ctx, params) + _, err := conn.SetIpAddressTypeWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "failure Setting LB IP Address Type: %s", err) + return sdkdiag.AppendErrorf(diags, "setting ELBv2 Load Balancer (%s) address type: %s", d.Id(), err) } } - _, err := waitLoadBalancerActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Load Balancer (%s) to be active: %s", d.Get("name").(string), err) + if _, err := waitLoadBalancerActive(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ELBv2 Load Balancer (%s) update: %s", d.Id(), err) } return append(diags, resourceLoadBalancerRead(ctx, d, meta)...) @@ -665,37 +569,197 @@ func resourceLoadBalancerDelete(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - log.Printf("[INFO] Deleting LB: %s", d.Id()) - - // Destroy the load balancer - deleteElbOpts := elbv2.DeleteLoadBalancerInput{ + log.Printf("[INFO] Deleting ELBv2 Load Balancer: %s", d.Id()) + _, err := conn.DeleteLoadBalancerWithContext(ctx, &elbv2.DeleteLoadBalancerInput{ LoadBalancerArn: aws.String(d.Id()), - } - if _, err := conn.DeleteLoadBalancerWithContext(ctx, &deleteElbOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting LB: %s", err) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ELBv2 Load Balancer (%s): %s", d.Id(), err) } ec2conn := meta.(*conns.AWSClient).EC2Conn(ctx) - err := cleanupALBNetworkInterfaces(ctx, ec2conn, d.Id()) - if err != nil { - log.Printf("[WARN] Failed to cleanup ENIs for ALB %q: %#v", d.Id(), err) + if err := cleanupALBNetworkInterfaces(ctx, ec2conn, d.Id()); err != nil { + log.Printf("[WARN] Failed to cleanup ENIs for ALB (%s): %s", d.Id(), err) } - err = waitForNLBNetworkInterfacesToDetach(ctx, ec2conn, d.Id()) - if err != nil { - log.Printf("[WARN] Failed to wait for ENIs to disappear for NLB %q: %#v", d.Id(), err) + if err := waitForNLBNetworkInterfacesToDetach(ctx, ec2conn, d.Id()); err != nil { + log.Printf("[WARN] Failed to wait for ENIs to disappear for NLB (%s): %s", d.Id(), err) } return diags } +func modifyLoadBalancerAttributes(ctx context.Context, conn *elbv2.ELBV2, arn string, attributes []*elbv2.LoadBalancerAttribute) error { + input := &elbv2.ModifyLoadBalancerAttributesInput{ + Attributes: attributes, + LoadBalancerArn: aws.String(arn), + } + + // Not all attributes are supported in all partitions. + for { + _, err := conn.ModifyLoadBalancerAttributesWithContext(ctx, input) + + if err != nil { + // "Validation error: Load balancer attribute key 'routing.http.desync_mitigation_mode' is not recognized" + // "InvalidConfigurationRequest: Load balancer attribute key 'dns_record.client_routing_policy' is not supported on load balancers with type 'network'" + re := regexache.MustCompile(`attribute key ('|")?([^'" ]+)('|")? is not (recognized|supported)`) + if sm := re.FindStringSubmatch(err.Error()); len(sm) > 1 { + key := sm[2] + input.Attributes = slices.DeleteFunc(input.Attributes, func(v *elbv2.LoadBalancerAttribute) bool { + return aws.StringValue(v.Key) == key + }) + + continue + } + + return fmt.Errorf("modifying ELBv2 Load Balancer (%s) attributes: %w", arn, err) + } + + return nil + } +} + +type loadBalancerAttributeInfo struct { + apiAttributeKey string + tfType schema.ValueType + loadBalancerTypesSupported []string +} + +type loadBalancerAttributeMap map[string]loadBalancerAttributeInfo + +var loadBalancerAttributes = loadBalancerAttributeMap(map[string]loadBalancerAttributeInfo{ + "desync_mitigation_mode": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPDesyncMitigationMode, + tfType: schema.TypeString, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "dns_record_client_routing_policy": { + apiAttributeKey: loadBalancerAttributeDNSRecordClientRoutingPolicy, + tfType: schema.TypeString, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumNetwork}, + }, + "drop_invalid_header_fields": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPDropInvalidHeaderFieldsEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "enable_cross_zone_load_balancing": { + apiAttributeKey: loadBalancerAttributeLoadBalancingCrossZoneEnabled, + tfType: schema.TypeBool, + // Although this attribute is supported for ALBs, it must always be true. + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumNetwork, elbv2.LoadBalancerTypeEnumGateway}, + }, + "enable_deletion_protection": { + apiAttributeKey: loadBalancerAttributeDeletionProtectionEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication, elbv2.LoadBalancerTypeEnumNetwork, elbv2.LoadBalancerTypeEnumGateway}, + }, + "enable_http2": { + apiAttributeKey: loadBalancerAttributeRoutingHTTP2Enabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "enable_tls_version_and_cipher_suite_headers": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPXAmznTLSVersionAndCipherSuiteEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "enable_waf_fail_open": { + apiAttributeKey: loadBalancerAttributeWAFFailOpenEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "enable_xff_client_port": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPXFFClientPortEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "idle_timeout": { + apiAttributeKey: loadBalancerAttributeIdleTimeoutTimeoutSeconds, + tfType: schema.TypeInt, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "preserve_host_header": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPPreserveHostHeaderEnabled, + tfType: schema.TypeBool, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, + "xff_header_processing_mode": { + apiAttributeKey: loadBalancerAttributeRoutingHTTPXFFHeaderProcessingMode, + tfType: schema.TypeString, + loadBalancerTypesSupported: []string{elbv2.LoadBalancerTypeEnumApplication}, + }, +}) + +func (m loadBalancerAttributeMap) expand(d *schema.ResourceData, update bool) []*elbv2.LoadBalancerAttribute { + var apiObjects []*elbv2.LoadBalancerAttribute + + loadBalancerType := d.Get("load_balancer_type").(string) + for tfAttributeName, attributeInfo := range m { + if update && !d.HasChange(tfAttributeName) { + continue + } + + if !slices.Contains(attributeInfo.loadBalancerTypesSupported, loadBalancerType) { + continue + } + + switch v, t, k := d.Get(tfAttributeName), attributeInfo.tfType, aws.String(attributeInfo.apiAttributeKey); t { + case schema.TypeBool: + v := v.(bool) + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: k, + Value: flex.BoolValueToString(v), + }) + case schema.TypeInt: + v := v.(int) + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: k, + Value: flex.IntValueToString(v), + }) + case schema.TypeString: + if v := v.(string); v != "" { + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: k, + Value: aws.String(v), + }) + } + } + } + + return apiObjects +} + +func (m loadBalancerAttributeMap) flatten(d *schema.ResourceData, apiObjects []*elbv2.LoadBalancerAttribute) { + for tfAttributeName, attributeInfo := range m { + k := attributeInfo.apiAttributeKey + i := slices.IndexFunc(apiObjects, func(v *elbv2.LoadBalancerAttribute) bool { + return aws.StringValue(v.Key) == k + }) + + if i == -1 { + continue + } + + switch v, t := apiObjects[i].Value, attributeInfo.tfType; t { + case schema.TypeBool: + d.Set(tfAttributeName, flex.StringToBoolValue(v)) + case schema.TypeInt: + d.Set(tfAttributeName, flex.StringToIntValue(v)) + case schema.TypeString: + d.Set(tfAttributeName, v) + } + } +} + func FindLoadBalancerByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) (*elbv2.LoadBalancer, error) { input := &elbv2.DescribeLoadBalancersInput{ LoadBalancerArns: aws.StringSlice([]string{arn}), } - output, err := FindLoadBalancer(ctx, conn, input) + output, err := findLoadBalancer(ctx, conn, input) if err != nil { return nil, err @@ -711,7 +775,17 @@ func FindLoadBalancerByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) ( return output, nil } -func FindLoadBalancers(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeLoadBalancersInput) ([]*elbv2.LoadBalancer, error) { +func findLoadBalancer(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeLoadBalancersInput) (*elbv2.LoadBalancer, error) { + output, err := findLoadBalancers(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSinglePtrResult(output) +} + +func findLoadBalancers(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeLoadBalancersInput) ([]*elbv2.LoadBalancer, error) { var output []*elbv2.LoadBalancer err := conn.DescribeLoadBalancersPagesWithContext(ctx, input, func(page *elbv2.DescribeLoadBalancersOutput, lastPage bool) bool { @@ -720,7 +794,7 @@ func FindLoadBalancers(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Desc } for _, v := range page.LoadBalancers { - if v != nil { + if v != nil && v.State != nil { output = append(output, v) } } @@ -742,25 +816,32 @@ func FindLoadBalancers(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.Desc return output, nil } -func FindLoadBalancer(ctx context.Context, conn *elbv2.ELBV2, input *elbv2.DescribeLoadBalancersInput) (*elbv2.LoadBalancer, error) { - output, err := FindLoadBalancers(ctx, conn, input) +func FindLoadBalancerAttributesByARN(ctx context.Context, conn *elbv2.ELBV2, arn string) ([]*elbv2.LoadBalancerAttribute, error) { + input := &elbv2.DescribeLoadBalancerAttributesInput{ + LoadBalancerArn: aws.String(arn), + } + + output, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeLoadBalancerNotFoundException) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } if err != nil { return nil, err } - if len(output) == 0 || output[0] == nil || output[0].State == nil { + if output == nil { return nil, tfresource.NewEmptyResultError(input) } - if count := len(output); count > 1 { - return nil, tfresource.NewTooManyResultsError(count, input) - } - - return output[0], nil + return output.Attributes, nil } -func statusLoadBalancerState(ctx context.Context, conn *elbv2.ELBV2, arn string) retry.StateRefreshFunc { +func statusLoadBalancer(ctx context.Context, conn *elbv2.ELBV2, arn string) retry.StateRefreshFunc { return func() (interface{}, string, error) { output, err := FindLoadBalancerByARN(ctx, conn, arn) @@ -780,10 +861,10 @@ func waitLoadBalancerActive(ctx context.Context, conn *elbv2.ELBV2, arn string, stateConf := &retry.StateChangeConf{ Pending: []string{elbv2.LoadBalancerStateEnumProvisioning, elbv2.LoadBalancerStateEnumFailed}, Target: []string{elbv2.LoadBalancerStateEnumActive}, - Refresh: statusLoadBalancerState(ctx, conn, arn), + Refresh: statusLoadBalancer(ctx, conn, arn), Timeout: timeout, MinTimeout: 10 * time.Second, - Delay: 30 * time.Second, // Wait 30 secs before starting + Delay: 30 * time.Second, } outputRaw, err := stateConf.WaitForStateContext(ctx) @@ -797,107 +878,75 @@ func waitLoadBalancerActive(ctx context.Context, conn *elbv2.ELBV2, arn string, return nil, err } -func removeAttribute(attributes []*elbv2.LoadBalancerAttribute, key string) []*elbv2.LoadBalancerAttribute { - for i, a := range attributes { - if aws.StringValue(a.Key) == key { - return append(attributes[:i], attributes[i+1:]...) - } - } - - log.Printf("[WARN] Unable to remove attribute %s from Load Balancer attributes: not found", key) - return attributes -} - // ALB automatically creates ENI(s) on creation // but the cleanup is asynchronous and may take time // which then blocks IGW, SG or VPC on deletion // So we make the cleanup "synchronous" here -func cleanupALBNetworkInterfaces(ctx context.Context, conn *ec2.EC2, lbArn string) error { - name, err := getLBNameFromARN(lbArn) - +func cleanupALBNetworkInterfaces(ctx context.Context, conn *ec2.EC2, arn string) error { + name, err := loadBalancerNameFromARN(arn) if err != nil { return err } networkInterfaces, err := tfec2.FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(ctx, conn, "amazon-elb", "ELB "+name) - if err != nil { return err } - var errs *multierror.Error + var errs []error - for _, networkInterface := range networkInterfaces { - if networkInterface.Attachment == nil { + for _, v := range networkInterfaces { + if v.Attachment == nil { continue } - attachmentID := aws.StringValue(networkInterface.Attachment.AttachmentId) - networkInterfaceID := aws.StringValue(networkInterface.NetworkInterfaceId) - - err = tfec2.DetachNetworkInterface(ctx, conn, networkInterfaceID, attachmentID, tfec2.NetworkInterfaceDetachedTimeout) - - if err != nil { - errs = multierror.Append(errs, err) + attachmentID := aws.StringValue(v.Attachment.AttachmentId) + networkInterfaceID := aws.StringValue(v.NetworkInterfaceId) + if err := tfec2.DetachNetworkInterface(ctx, conn, networkInterfaceID, attachmentID, tfec2.NetworkInterfaceDetachedTimeout); err != nil { + errs = append(errs, err) continue } - err = tfec2.DeleteNetworkInterface(ctx, conn, networkInterfaceID) - - if err != nil { - errs = multierror.Append(errs, err) - + if err := tfec2.DeleteNetworkInterface(ctx, conn, networkInterfaceID); err != nil { + errs = append(errs, err) continue } } - return errs.ErrorOrNil() + return errors.Join(errs...) } func waitForNLBNetworkInterfacesToDetach(ctx context.Context, conn *ec2.EC2, lbArn string) error { - const ( - loadBalancerNetworkInterfaceDetachTimeout = 5 * time.Minute - ) - name, err := getLBNameFromARN(lbArn) - + name, err := loadBalancerNameFromARN(lbArn) if err != nil { return err } - errAttached := errors.New("attached") - - _, err = tfresource.RetryWhen(ctx, loadBalancerNetworkInterfaceDetachTimeout, - func() (interface{}, error) { - networkInterfaces, err := tfec2.FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(ctx, conn, "amazon-aws", "ELB "+name) - - if err != nil { - return nil, err - } - - if len(networkInterfaces) > 0 { - return networkInterfaces, errAttached - } - - return networkInterfaces, nil - }, - func(err error) (bool, error) { - if errors.Is(err, errAttached) { - return true, err - } - - return false, err - }, + const ( + timeout = 5 * time.Minute ) + _, err = tfresource.RetryUntilEqual(ctx, timeout, 0, func() (int, error) { + networkInterfaces, err := tfec2.FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(ctx, conn, "amazon-aws", "ELB "+name) + if err != nil { + return 0, err + } + + return len(networkInterfaces), nil + }) return err } -func getLBNameFromARN(arn string) (string, error) { - re := regexache.MustCompile("([^/]+/[^/]+/[^/]+)$") - matches := re.FindStringSubmatch(arn) +func loadBalancerNameFromARN(s string) (string, error) { + v, err := arn.Parse(s) + if err != nil { + return "", err + } + + matches := regexache.MustCompile("([^/]+/[^/]+/[^/]+)$").FindStringSubmatch(v.Resource) if len(matches) != 2 { - return "", fmt.Errorf("unexpected ARN format: %q", arn) + return "", fmt.Errorf("unexpected ELBv2 Load Balancer ARN format: %q", s) } // e.g. app/example-alb/b26e625cdde161e6 @@ -942,114 +991,6 @@ func SuffixFromARN(arn *string) string { return "" } -// flattenResource takes a *elbv2.LoadBalancer and populates all respective resource fields. -func flattenResource(ctx context.Context, d *schema.ResourceData, meta interface{}, lb *elbv2.LoadBalancer) error { - conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) - - d.Set("arn", lb.LoadBalancerArn) - d.Set("arn_suffix", SuffixFromARN(lb.LoadBalancerArn)) - d.Set("customer_owned_ipv4_pool", lb.CustomerOwnedIpv4Pool) - d.Set("dns_name", lb.DNSName) - d.Set("enforce_security_group_inbound_rules_on_private_link_traffic", lb.EnforceSecurityGroupInboundRulesOnPrivateLinkTraffic) - d.Set("internal", aws.StringValue(lb.Scheme) == elbv2.LoadBalancerSchemeEnumInternal) - d.Set("ip_address_type", lb.IpAddressType) - d.Set("load_balancer_type", lb.Type) - d.Set("name", lb.LoadBalancerName) - d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(lb.LoadBalancerName))) - d.Set("security_groups", aws.StringValueSlice(lb.SecurityGroups)) - d.Set("vpc_id", lb.VpcId) - d.Set("zone_id", lb.CanonicalHostedZoneId) - - if err := d.Set("subnets", flattenSubnetsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { - return fmt.Errorf("setting subnets: %w", err) - } - - if err := d.Set("subnet_mapping", flattenSubnetMappingsFromAvailabilityZones(lb.AvailabilityZones)); err != nil { - return fmt.Errorf("setting subnet_mapping: %w", err) - } - - attributesResp, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, &elbv2.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: aws.String(d.Id()), - }) - if err != nil { - return fmt.Errorf("retrieving LB Attributes: %w", err) - } - - accessLogMap := map[string]interface{}{ - "bucket": "", - "enabled": false, - "prefix": "", - } - - for _, attr := range attributesResp.Attributes { - switch aws.StringValue(attr.Key) { - case "access_logs.s3.enabled": - accessLogMap["enabled"] = flex.StringToBoolValue(attr.Value) - case "access_logs.s3.bucket": - accessLogMap["bucket"] = aws.StringValue(attr.Value) - case "access_logs.s3.prefix": - accessLogMap["prefix"] = aws.StringValue(attr.Value) - case "dns_record.client_routing_policy": - dnsClientRoutingPolicy := aws.StringValue(attr.Value) - log.Printf("[DEBUG] Setting NLB DNS Record Client Routing Policy: %s", dnsClientRoutingPolicy) - d.Set("dns_record_client_routing_policy", dnsClientRoutingPolicy) - case "idle_timeout.timeout_seconds": - timeout, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return fmt.Errorf("parsing ALB timeout: %w", err) - } - log.Printf("[DEBUG] Setting ALB Timeout Seconds: %d", timeout) - d.Set("idle_timeout", timeout) - case "routing.http.drop_invalid_header_fields.enabled": - dropInvalidHeaderFieldsEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting LB Invalid Header Fields Enabled: %t", dropInvalidHeaderFieldsEnabled) - d.Set("drop_invalid_header_fields", dropInvalidHeaderFieldsEnabled) - case "routing.http.preserve_host_header.enabled": - preserveHostHeaderEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting LB Preserve Host Header Enabled: %t", preserveHostHeaderEnabled) - d.Set("preserve_host_header", preserveHostHeaderEnabled) - case "deletion_protection.enabled": - protectionEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting LB Deletion Protection Enabled: %t", protectionEnabled) - d.Set("enable_deletion_protection", protectionEnabled) - case "routing.http2.enabled": - http2Enabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting ALB HTTP/2 Enabled: %t", http2Enabled) - d.Set("enable_http2", http2Enabled) - case "waf.fail_open.enabled": - wafFailOpenEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting ALB WAF fail open Enabled: %t", wafFailOpenEnabled) - d.Set("enable_waf_fail_open", wafFailOpenEnabled) - case "load_balancing.cross_zone.enabled": - crossZoneLbEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting NLB Cross Zone Load Balancing Enabled: %t", crossZoneLbEnabled) - d.Set("enable_cross_zone_load_balancing", crossZoneLbEnabled) - case "routing.http.desync_mitigation_mode": - desyncMitigationMode := aws.StringValue(attr.Value) - log.Printf("[DEBUG] Setting ALB Desync Mitigation Mode: %s", desyncMitigationMode) - d.Set("desync_mitigation_mode", desyncMitigationMode) - case "routing.http.x_amzn_tls_version_and_cipher_suite.enabled": - tlsVersionAndCipherEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting ALB TLS Version And Cipher Suite Headers Enabled: %t", tlsVersionAndCipherEnabled) - d.Set("enable_tls_version_and_cipher_suite_headers", tlsVersionAndCipherEnabled) - case "routing.http.xff_client_port.enabled": - xffClientPortEnabled := flex.StringToBoolValue(attr.Value) - log.Printf("[DEBUG] Setting ALB Xff Client Port Enabled: %t", xffClientPortEnabled) - d.Set("enable_xff_client_port", xffClientPortEnabled) - case "routing.http.xff_header_processing.mode": - xffHeaderProcMode := aws.StringValue(attr.Value) - log.Printf("[DEBUG] Setting ALB Xff Header Processing Mode: %s", xffHeaderProcMode) - d.Set("xff_header_processing_mode", xffHeaderProcMode) - } - } - - if err := d.Set("access_logs", []interface{}{accessLogMap}); err != nil { - return fmt.Errorf("setting access_logs: %w", err) - } - - return nil -} - // Load balancers of type 'network' cannot have their subnets updated, // cannot have security groups added if none are present, and cannot have // all security groups removed. If the type is 'network' and any of these @@ -1098,6 +1039,60 @@ func customizeDiffNLB(_ context.Context, diff *schema.ResourceDiff, v interface{ return nil } +func expandLoadBalancerAccessLogsAttributes(tfMap map[string]interface{}, update bool) []*elbv2.LoadBalancerAttribute { + if tfMap == nil { + return nil + } + + var apiObjects []*elbv2.LoadBalancerAttribute + + if v, ok := tfMap["enabled"].(bool); ok { + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: aws.String(loadBalancerAttributeAccessLogsS3Enabled), + Value: flex.BoolValueToString(v), + }) + + if v { + if v, ok := tfMap["bucket"].(string); ok && (update || v != "") { + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: aws.String(loadBalancerAttributeAccessLogsS3Bucket), + Value: aws.String(v), + }) + } + + if v, ok := tfMap["prefix"].(string); ok && (update || v != "") { + apiObjects = append(apiObjects, &elbv2.LoadBalancerAttribute{ + Key: aws.String(loadBalancerAttributeAccessLogsS3Prefix), + Value: aws.String(v), + }) + } + } + } + + return apiObjects +} + +func flattenLoadBalancerAccessLogsAttributes(apiObjects []*elbv2.LoadBalancerAttribute) map[string]interface{} { + if len(apiObjects) == 0 { + return nil + } + + tfMap := map[string]interface{}{} + + for _, apiObject := range apiObjects { + switch k, v := aws.StringValue(apiObject.Key), apiObject.Value; k { + case loadBalancerAttributeAccessLogsS3Enabled: + tfMap["enabled"] = flex.StringToBoolValue(v) + case loadBalancerAttributeAccessLogsS3Bucket: + tfMap["bucket"] = aws.StringValue(v) + case loadBalancerAttributeAccessLogsS3Prefix: + tfMap["prefix"] = aws.StringValue(v) + } + } + + return tfMap +} + func expandSubnetMapping(tfMap map[string]interface{}) *elbv2.SubnetMapping { if tfMap == nil { return nil diff --git a/internal/service/elbv2/load_balancer_data_source.go b/internal/service/elbv2/load_balancer_data_source.go index d5c5ebd80ab..5f5c7c049d8 100644 --- a/internal/service/elbv2/load_balancer_data_source.go +++ b/internal/service/elbv2/load_balancer_data_source.go @@ -6,7 +6,6 @@ package elbv2 import ( "context" "log" - "strconv" "time" "github.com/aws/aws-sdk-go/aws" @@ -17,7 +16,6 @@ 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" - "github.com/hashicorp/terraform-provider-aws/internal/flex" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -75,6 +73,10 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "dns_record_client_routing_policy": { + Type: schema.TypeString, + Computed: true, + }, "drop_invalid_header_fields": { Type: schema.TypeBool, Computed: true, @@ -202,7 +204,7 @@ func dataSourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, met input.Names = aws.StringSlice([]string{v.(string)}) } - results, err := FindLoadBalancers(ctx, conn, input) + results, err := findLoadBalancers(ctx, conn, input) if err != nil { return sdkdiag.AppendErrorf(diags, "reading ELBv2 Load Balancers: %s", err) @@ -258,74 +260,22 @@ func dataSourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, met d.Set("vpc_id", lb.VpcId) d.Set("zone_id", lb.CanonicalHostedZoneId) - attributesResp, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, &elbv2.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: aws.String(d.Id()), - }) - if err != nil { - return sdkdiag.AppendErrorf(diags, "retrieving LB Attributes: %s", err) - } - - accessLogMap := map[string]interface{}{ - "bucket": "", - "enabled": false, - "prefix": "", - } + attributes, err := FindLoadBalancerAttributesByARN(ctx, conn, d.Id()) - for _, attr := range attributesResp.Attributes { - switch aws.StringValue(attr.Key) { - case "access_logs.s3.enabled": - accessLogMap["enabled"] = flex.StringToBoolValue(attr.Value) - case "access_logs.s3.bucket": - accessLogMap["bucket"] = aws.StringValue(attr.Value) - case "access_logs.s3.prefix": - accessLogMap["prefix"] = aws.StringValue(attr.Value) - case "idle_timeout.timeout_seconds": - timeout, err := strconv.Atoi(aws.StringValue(attr.Value)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing ALB timeout: %s", err) - } - d.Set("idle_timeout", timeout) - case "routing.http.drop_invalid_header_fields.enabled": - dropInvalidHeaderFieldsEnabled := flex.StringToBoolValue(attr.Value) - d.Set("drop_invalid_header_fields", dropInvalidHeaderFieldsEnabled) - case "routing.http.preserve_host_header.enabled": - preserveHostHeaderEnabled := flex.StringToBoolValue(attr.Value) - d.Set("preserve_host_header", preserveHostHeaderEnabled) - case "deletion_protection.enabled": - protectionEnabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_deletion_protection", protectionEnabled) - case "routing.http2.enabled": - http2Enabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_http2", http2Enabled) - case "waf.fail_open.enabled": - wafFailOpenEnabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_waf_fail_open", wafFailOpenEnabled) - case "load_balancing.cross_zone.enabled": - crossZoneLbEnabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_cross_zone_load_balancing", crossZoneLbEnabled) - case "routing.http.desync_mitigation_mode": - desyncMitigationMode := aws.StringValue(attr.Value) - d.Set("desync_mitigation_mode", desyncMitigationMode) - case "routing.http.x_amzn_tls_version_and_cipher_suite.enabled": - tlsVersionAndCipherEnabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_tls_version_and_cipher_suite_headers", tlsVersionAndCipherEnabled) - case "routing.http.xff_client_port.enabled": - xffClientPortEnabled := flex.StringToBoolValue(attr.Value) - d.Set("enable_xff_client_port", xffClientPortEnabled) - case "routing.http.xff_header_processing.mode": - xffHeaderProcMode := aws.StringValue(attr.Value) - d.Set("xff_header_processing_mode", xffHeaderProcMode) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELBv2 Load Balancer (%s) attributes: %s", d.Id(), err) } - if err := d.Set("access_logs", []interface{}{accessLogMap}); err != nil { + if err := d.Set("access_logs", []interface{}{flattenLoadBalancerAccessLogsAttributes(attributes)}); err != nil { return sdkdiag.AppendErrorf(diags, "setting access_logs: %s", err) } + loadBalancerAttributes.flatten(d, attributes) + tags, err := listTags(ctx, conn, d.Id()) if errs.IsUnsupportedOperationInPartitionError(conn.PartitionID, err) { - log.Printf("[WARN] Unable to list tags for ELBv2 Load Balancer %s: %s", d.Id(), err) + log.Printf("[WARN] Unable to list tags for ELBv2 Load Balancer (%s): %s", d.Id(), err) return diags } diff --git a/internal/service/elbv2/load_balancer_test.go b/internal/service/elbv2/load_balancer_test.go index b0c4ab376fb..3f5bcae5930 100644 --- a/internal/service/elbv2/load_balancer_test.go +++ b/internal/service/elbv2/load_balancer_test.go @@ -20,6 +20,7 @@ import ( "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" + "github.com/hashicorp/terraform-provider-aws/names" ) func init() { @@ -372,6 +373,16 @@ func TestAccELBV2LoadBalancer_ipv6SubnetMapping(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" + importStateVerifyIgnore := []string{ + "drop_invalid_header_fields", + "enable_http2", + "idle_timeout", + } + // GovCloud doesn't support dns_record_client_routing_policy. + if acctest.Partition() == names.USGovCloudPartitionID { + importStateVerifyIgnore = append(importStateVerifyIgnore, "dns_record_client_routing_policy") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -388,14 +399,10 @@ func TestAccELBV2LoadBalancer_ipv6SubnetMapping(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{ - "drop_invalid_header_fields", - "enable_http2", - "idle_timeout", - }, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: importStateVerifyIgnore, }, }, }) @@ -523,6 +530,12 @@ func TestAccELBV2LoadBalancer_NLB_privateIPv4Address(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" + var importStateVerifyIgnore []string + // GovCloud doesn't support dns_record_client_routing_policy. + if acctest.Partition() == names.USGovCloudPartitionID { + importStateVerifyIgnore = append(importStateVerifyIgnore, "dns_record_client_routing_policy") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -539,9 +552,10 @@ func TestAccELBV2LoadBalancer_NLB_privateIPv4Address(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: importStateVerifyIgnore, }, }, }) @@ -589,10 +603,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateCrossZone(t *testing.T) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -635,10 +645,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateHTTP2(t *testing.T) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -679,10 +685,7 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateDropInvalidHeaderFie ctx := acctest.Context(t) var pre, mid, post elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } + resourceName := "aws_lb.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -693,26 +696,26 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateDropInvalidHeaderFie { Config: testAccLoadBalancerConfig_enableDropInvalidHeaderFields(rName, false), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &pre), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.drop_invalid_header_fields.enabled", "false"), - resource.TestCheckResourceAttr("aws_lb.test", "drop_invalid_header_fields", "false"), + testAccCheckLoadBalancerExists(ctx, resourceName, &pre), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.drop_invalid_header_fields.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "drop_invalid_header_fields", "false"), ), }, { Config: testAccLoadBalancerConfig_enableDropInvalidHeaderFields(rName, true), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &mid), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.drop_invalid_header_fields.enabled", "true"), - resource.TestCheckResourceAttr("aws_lb.test", "drop_invalid_header_fields", "true"), + testAccCheckLoadBalancerExists(ctx, resourceName, &mid), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.drop_invalid_header_fields.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "drop_invalid_header_fields", "true"), testAccCheckLoadBalancerNotRecreated(&pre, &mid), ), }, { Config: testAccLoadBalancerConfig_enableDropInvalidHeaderFields(rName, false), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &post), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.drop_invalid_header_fields.enabled", "false"), - resource.TestCheckResourceAttr("aws_lb.test", "drop_invalid_header_fields", "false"), + testAccCheckLoadBalancerExists(ctx, resourceName, &post), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.drop_invalid_header_fields.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "drop_invalid_header_fields", "false"), testAccCheckLoadBalancerNotRecreated(&mid, &post), ), }, @@ -724,10 +727,7 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updatePreserveHostHeader(t ctx := acctest.Context(t) var pre, mid, post elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } + resourceName := "aws_lb.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -738,26 +738,26 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updatePreserveHostHeader(t { Config: testAccLoadBalancerConfig_enablePreserveHostHeader(rName, false), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &pre), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.preserve_host_header.enabled", "false"), - resource.TestCheckResourceAttr("aws_lb.test", "preserve_host_header", "false"), + testAccCheckLoadBalancerExists(ctx, resourceName, &pre), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.preserve_host_header.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "preserve_host_header", "false"), ), }, { Config: testAccLoadBalancerConfig_enablePreserveHostHeader(rName, true), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &mid), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.preserve_host_header.enabled", "true"), - resource.TestCheckResourceAttr("aws_lb.test", "preserve_host_header", "true"), + testAccCheckLoadBalancerExists(ctx, resourceName, &mid), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.preserve_host_header.enabled", "true"), + resource.TestCheckResourceAttr(resourceName, "preserve_host_header", "true"), testAccCheckLoadBalancerNotRecreated(&pre, &mid), ), }, { Config: testAccLoadBalancerConfig_enablePreserveHostHeader(rName, false), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, "aws_lb.test", &post), - testAccCheckLoadBalancerAttribute(ctx, "aws_lb.test", "routing.http.preserve_host_header.enabled", "false"), - resource.TestCheckResourceAttr("aws_lb.test", "preserve_host_header", "false"), + testAccCheckLoadBalancerExists(ctx, resourceName, &post), + testAccCheckLoadBalancerAttribute(ctx, resourceName, "routing.http.preserve_host_header.enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "preserve_host_header", "false"), testAccCheckLoadBalancerNotRecreated(&mid, &post), ), }, @@ -771,10 +771,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateDeletionProtection(t rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -817,10 +813,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateWAFFailOpen(t *testi rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -847,11 +839,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateWAFFailOpen(t *testi testAccCheckLoadBalancerNotRecreated(&pre, &mid), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_enableWAFFailOpen(rName, false), Check: resource.ComposeAggregateTestCheckFunc( @@ -900,12 +887,12 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updatedSecurityGroups(t *t rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + // GovCloud Regions don't always have 3 AZs. + acctest.PreCheckPartitionNot(t, names.USGovCloudPartitionID) + }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckLoadBalancerDestroy(ctx), @@ -937,12 +924,12 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_updateSubnets(t *testing.T rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + // GovCloud Regions don't always have 3 AZs. + acctest.PreCheckPartitionNot(t, names.USGovCloudPartitionID) + }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckLoadBalancerDestroy(ctx), @@ -1006,10 +993,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogs(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -1047,11 +1030,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_albAccessLogs(true, rName, ""), Check: resource.ComposeAggregateTestCheckFunc( @@ -1065,11 +1043,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_albAccessLogsNoBlocks(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -1083,11 +1056,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -1098,10 +1066,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogsPrefix(t *testin rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -1139,11 +1103,6 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogsPrefix(t *testin resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_albAccessLogs(true, rName, "prefix1"), Check: resource.ComposeAggregateTestCheckFunc( @@ -1157,25 +1116,22 @@ func TestAccELBV2LoadBalancer_ApplicationLoadBalancer_accessLogsPrefix(t *testin resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", "prefix1"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogs(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var conf elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" + var importStateVerifyIgnore []string + // GovCloud doesn't support dns_record_client_routing_policy. + if acctest.Partition() == names.USGovCloudPartitionID { + importStateVerifyIgnore = append(importStateVerifyIgnore, "dns_record_client_routing_policy") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -1196,9 +1152,10 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogs(t *testing.T) { ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: importStateVerifyIgnore, }, { Config: testAccLoadBalancerConfig_nlbAccessLogs(false, rName, ""), @@ -1213,11 +1170,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_nlbAccessLogs(true, rName, ""), Check: resource.ComposeAggregateTestCheckFunc( @@ -1231,11 +1183,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_nlbAccessLogsNoBlocks(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -1249,25 +1196,22 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogs(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogsPrefix(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var conf elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" + var importStateVerifyIgnore []string + // GovCloud doesn't support dns_record_client_routing_policy. + if acctest.Partition() == names.USGovCloudPartitionID { + importStateVerifyIgnore = append(importStateVerifyIgnore, "dns_record_client_routing_policy") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -1288,9 +1232,10 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogsPrefix(t *testing.T) ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: importStateVerifyIgnore, }, { Config: testAccLoadBalancerConfig_nlbAccessLogs(true, rName, ""), @@ -1305,11 +1250,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogsPrefix(t *testing.T) resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", ""), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_nlbAccessLogs(true, rName, "prefix1"), Check: resource.ComposeAggregateTestCheckFunc( @@ -1323,11 +1263,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_accessLogsPrefix(t *testing.T) resource.TestCheckResourceAttr(resourceName, "access_logs.0.prefix", "prefix1"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -1339,7 +1274,11 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateDNSRecordClientRoutingPo resourceName := "aws_lb.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + // GovCloud doesn't support dns_record_client_routing_policy. + acctest.PreCheckPartitionNot(t, names.USGovCloudPartitionID) + }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckLoadBalancerDestroy(ctx), @@ -1351,6 +1290,11 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateDNSRecordClientRoutingPo resource.TestCheckResourceAttr(resourceName, "dns_record_client_routing_policy", "availability_zone_affinity"), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccLoadBalancerConfig_nlbDNSRecordClientRoutingPolicyPartialAffinity(rName), Check: resource.ComposeAggregateTestCheckFunc( @@ -1365,11 +1309,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateDNSRecordClientRoutingPo resource.TestCheckResourceAttr(resourceName, "dns_record_client_routing_policy", "any_availability_zone"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, }, }) } @@ -1427,10 +1366,16 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSecurityGroups(t *testin func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing.T) { ctx := acctest.Context(t) - var lb1 elbv2.LoadBalancer + var lb elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" + var importStateVerifyIgnore []string + // GovCloud doesn't support dns_record_client_routing_policy. + if acctest.Partition() == names.USGovCloudPartitionID { + importStateVerifyIgnore = append(importStateVerifyIgnore, "dns_record_client_routing_policy") + } + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), @@ -1440,19 +1385,20 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing. { Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "off"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + testAccCheckLoadBalancerExists(ctx, resourceName, &lb), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off")), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: importStateVerifyIgnore, }, { Config: testAccLoadBalancerConfig_nlbSecurityGroups(rName, 1), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + testAccCheckLoadBalancerExists(ctx, resourceName, &lb), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), ), @@ -1460,7 +1406,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing. { Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "on"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + testAccCheckLoadBalancerExists(ctx, resourceName, &lb), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "on"), ), @@ -1468,7 +1414,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing. { Config: testAccLoadBalancerConfig_nlbSecurityGroupsEnforcePrivateLink(rName, 1, "off"), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + testAccCheckLoadBalancerExists(ctx, resourceName, &lb), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), ), @@ -1476,7 +1422,7 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_enforcePrivateLink(t *testing. { Config: testAccLoadBalancerConfig_nlbSecurityGroups(rName, 1), Check: resource.ComposeAggregateTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, resourceName, &lb1), + testAccCheckLoadBalancerExists(ctx, resourceName, &lb), resource.TestCheckResourceAttr(resourceName, "security_groups.#", "1"), resource.TestCheckResourceAttr(resourceName, "enforce_security_group_inbound_rules_on_private_link_traffic", "off"), ), @@ -1492,7 +1438,11 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSubnets(t *testing.T) { resourceName := "aws_lb.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, + PreCheck: func() { + acctest.PreCheck(ctx, t) + // GovCloud Regions don't always have 3 AZs. + acctest.PreCheckPartitionNot(t, names.USGovCloudPartitionID) + }, ErrorCheck: acctest.ErrorCheck(t, elbv2.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, CheckDestroy: testAccCheckLoadBalancerDestroy(ctx), @@ -1518,10 +1468,6 @@ func TestAccELBV2LoadBalancer_NetworkLoadBalancer_updateSubnets(t *testing.T) { func TestAccELBV2LoadBalancer_updateDesyncMitigationMode(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var pre, mid, post elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" @@ -1553,11 +1499,6 @@ func TestAccELBV2LoadBalancer_updateDesyncMitigationMode(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "desync_mitigation_mode", "monitor"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_desyncMitigationMode(rName, "defensive"), Check: resource.ComposeAggregateTestCheckFunc( @@ -1572,10 +1513,6 @@ func TestAccELBV2LoadBalancer_updateDesyncMitigationMode(t *testing.T) { func TestAccELBV2LoadBalancer_ALB_updateTLSVersionAndCipherSuite(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var conf elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" @@ -1607,11 +1544,6 @@ func TestAccELBV2LoadBalancer_ALB_updateTLSVersionAndCipherSuite(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "enable_tls_version_and_cipher_suite_headers", "true"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_tlscipherSuiteEnabled(rName, false), Check: resource.ComposeAggregateTestCheckFunc( @@ -1626,10 +1558,6 @@ func TestAccELBV2LoadBalancer_ALB_updateTLSVersionAndCipherSuite(t *testing.T) { func TestAccELBV2LoadBalancer_ALB_updateXffHeaderProcessingMode(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var conf elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" @@ -1661,11 +1589,6 @@ func TestAccELBV2LoadBalancer_ALB_updateXffHeaderProcessingMode(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "xff_header_processing_mode", "preserve"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_xffHeaderProcessingMode(rName, "remove"), Check: resource.ComposeAggregateTestCheckFunc( @@ -1680,10 +1603,6 @@ func TestAccELBV2LoadBalancer_ALB_updateXffHeaderProcessingMode(t *testing.T) { func TestAccELBV2LoadBalancer_ALB_updateXffClientPort(t *testing.T) { ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - var conf elbv2.LoadBalancer rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_lb.test" @@ -1715,11 +1634,6 @@ func TestAccELBV2LoadBalancer_ALB_updateXffClientPort(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "enable_xff_client_port", "true"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, { Config: testAccLoadBalancerConfig_xffClientPort(rName, false), Check: resource.ComposeAggregateTestCheckFunc( @@ -1759,10 +1673,6 @@ func testAccCheckLoadBalancerExists(ctx context.Context, n string, v *elbv2.Load return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No ELBv2 Load Balancer ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) output, err := tfelbv2.FindLoadBalancerByARN(ctx, conn, rs.Primary.ID) @@ -1784,27 +1694,26 @@ func testAccCheckLoadBalancerAttribute(ctx context.Context, n, key, value string return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return errors.New("No LB ID is set") - } - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx) - attributesResp, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, &elbv2.DescribeLoadBalancerAttributesInput{ - LoadBalancerArn: aws.String(rs.Primary.ID), - }) + + attributes, err := tfelbv2.FindLoadBalancerAttributesByARN(ctx, conn, rs.Primary.ID) + if err != nil { - return fmt.Errorf("Error retrieving LB Attributes: %s", err) + return err } - for _, attr := range attributesResp.Attributes { - if aws.StringValue(attr.Key) == key { - if aws.StringValue(attr.Value) == value { + for _, v := range attributes { + if aws.StringValue(v.Key) == key { + got := aws.StringValue(v.Value) + if got == value { return nil } - return fmt.Errorf("LB attribute %s expected: %q actual: %q", key, value, aws.StringValue(attr.Value)) + + return fmt.Errorf("ELBv2 Load Balancer (%s) attribute (%s) = %v, want %v", rs.Primary.ID, key, got, value) } } - return fmt.Errorf("LB attribute %s does not exist on LB: %s", key, rs.Primary.ID) + + return fmt.Errorf("ELBv2 Load Balancer (%s) attribute (%s) not found", rs.Primary.ID, key) } } diff --git a/internal/service/elbv2/load_balancers_data_source.go b/internal/service/elbv2/load_balancers_data_source.go index 90f641674c1..cd35511d852 100644 --- a/internal/service/elbv2/load_balancers_data_source.go +++ b/internal/service/elbv2/load_balancers_data_source.go @@ -42,7 +42,7 @@ func dataSourceLoadBalancersRead(ctx context.Context, d *schema.ResourceData, me conn := meta.(*conns.AWSClient).ELBV2Conn(ctx) ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - results, err := FindLoadBalancers(ctx, conn, &elbv2.DescribeLoadBalancersInput{}) + results, err := findLoadBalancers(ctx, conn, &elbv2.DescribeLoadBalancersInput{}) if err != nil { return create.AppendDiagError(diags, names.ELBV2, create.ErrActionReading, DSNameLoadBalancers, "", err) diff --git a/internal/service/elbv2/target_group.go b/internal/service/elbv2/target_group.go index b873c0bcf4d..da35f69df66 100644 --- a/internal/service/elbv2/target_group.go +++ b/internal/service/elbv2/target_group.go @@ -471,7 +471,7 @@ func resourceTargetGroupCreate(ctx context.Context, d *schema.ResourceData, meta // 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) { + if input.Tags != nil && tfawserr.ErrMessageContains(err, errCodeValidationError, tagsOnCreationErrMessage) { input.Tags = nil output, err = conn.CreateTargetGroupWithContext(ctx, input) diff --git a/internal/service/elbv2/trust_store.go b/internal/service/elbv2/trust_store.go index 00176394bdf..f4979c93714 100644 --- a/internal/service/elbv2/trust_store.go +++ b/internal/service/elbv2/trust_store.go @@ -125,7 +125,7 @@ func resourceTrustStoreCreate(ctx context.Context, d *schema.ResourceData, meta // 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) { + if input.Tags != nil && tfawserr.ErrMessageContains(err, errCodeValidationError, tagsOnCreationErrMessage) { input.Tags = nil output, err = conn.CreateTrustStoreWithContext(ctx, input)