Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes errors with target_group_arn and forward for aws_lb_listener and aws_lb_listener_rule #35671

Merged
merged 13 commits into from
Feb 7, 2024
Merged
15 changes: 15 additions & 0 deletions .changelog/35671.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
```release-note:bug
resource/aws_lb_listener: Was not storing `default_action[].forward` in state if only a single `target_group` was set.
```

```release-note:bug
resource/aws_lb_listener_rule: Was not storing `action[].forward` in state if only a single `target_group` was set.
```

```release-note:bug
resource/aws_lb_listener: Was incorrectly reporting conflicting `default_action[].target_group_arn` when `ignore_changes` was set.
```

```release-note:bug
resource/aws_lb_listener_rule: Was incorrectly reporting conflicting `action[].target_group_arn` when `ignore_changes` was set.
```
91 changes: 76 additions & 15 deletions internal/service/elbv2/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,11 @@ func ResourceListener() *schema.Resource {
},
},
"forward": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Type: schema.TypeList,
Optional: true,
DiffSuppressOnRefresh: true,
DiffSuppressFunc: diffSuppressMissingForward("default_action"),
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"target_group": {
Expand Down Expand Up @@ -511,7 +513,7 @@ func resourceListenerRead(ctx context.Context, d *schema.ResourceData, meta inte
sort.Slice(listener.DefaultActions, func(i, j int) bool {
return aws.ToInt32(listener.DefaultActions[i].Order) < aws.ToInt32(listener.DefaultActions[j].Order)
})
if err := d.Set("default_action", flattenLbListenerActions(d, listener.DefaultActions)); err != nil {
if err := d.Set("default_action", flattenLbListenerActions(d, "default_action", listener.DefaultActions)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting default_action: %s", err)
}
d.Set("load_balancer_arn", listener.LoadBalancerArn)
Expand Down Expand Up @@ -928,32 +930,34 @@ func expandLbListenerActionForwardConfigTargetGroupStickinessConfig(l []interfac
return nil
}

// The Plugin SDK stores a `nil` returned by the API as a `0` in the state. This is a invalid value.
var duration *int32
if v := tfMap["duration"].(int); v > 0 {
duration = aws.Int32(int32(v))
}

return &awstypes.TargetGroupStickinessConfig{
Enabled: aws.Bool(tfMap["enabled"].(bool)),
DurationSeconds: aws.Int32(int32(tfMap["duration"].(int))),
DurationSeconds: duration,
}
}

func flattenLbListenerActions(d *schema.ResourceData, Actions []awstypes.Action) []interface{} {
if len(Actions) == 0 {
func flattenLbListenerActions(d *schema.ResourceData, attrName string, actions []awstypes.Action) []interface{} {
if len(actions) == 0 {
return []interface{}{}
}

var vActions []interface{}

for i, action := range Actions {
for i, action := range actions {
m := map[string]interface{}{
"type": string(action.Type),
"order": aws.ToInt32(action.Order),
}

switch action.Type {
case awstypes.ActionTypeEnumForward:
if aws.ToString(action.TargetGroupArn) != "" {
m["target_group_arn"] = aws.ToString(action.TargetGroupArn)
} else {
m["forward"] = flattenLbListenerActionForwardConfig(action.ForwardConfig)
}
flattenLbForwardAction(d, attrName, i, action, m)

case awstypes.ActionTypeEnumRedirect:
m["redirect"] = flattenLbListenerActionRedirectConfig(action.RedirectConfig)
Expand All @@ -968,7 +972,7 @@ func flattenLbListenerActions(d *schema.ResourceData, Actions []awstypes.Action)
// The LB API currently provides no way to read the ClientSecret
// Instead we passthrough the configuration value into the state
var clientSecret string
if v, ok := d.GetOk("default_action." + strconv.Itoa(i) + ".authenticate_oidc.0.client_secret"); ok {
if v, ok := d.GetOk(attrName + "." + strconv.Itoa(i) + ".authenticate_oidc.0.client_secret"); ok {
clientSecret = v.(string)
}

Expand All @@ -981,6 +985,50 @@ func flattenLbListenerActions(d *schema.ResourceData, Actions []awstypes.Action)
return vActions
}

func flattenLbForwardAction(d *schema.ResourceData, attrName string, i int, awsAction awstypes.Action, actionMap map[string]any) {
// On create and update, we have a Config
// On refresh, we have a populated State
// On import, we have an empty State and empty Config

if rawConfig := d.GetRawConfig(); rawConfig.IsKnown() && !rawConfig.IsNull() {
actions := rawConfig.GetAttr(attrName)
flattenLbForwardActionOneOf(actions, i, awsAction, actionMap)
return
}

rawState := d.GetRawState()
defaultActions := rawState.GetAttr(attrName)

if defaultActions.LengthInt() > 0 {
flattenLbForwardActionOneOf(defaultActions, i, awsAction, actionMap)
return
}

flattenLbForwardActionBoth(awsAction, actionMap)
}

func flattenLbForwardActionOneOf(actions cty.Value, i int, awsAction awstypes.Action, actionMap map[string]any) {
if actions.IsKnown() && !actions.IsNull() {
index := cty.NumberIntVal(int64(i))
if actions.HasIndex(index).True() {
action := actions.Index(index)
if action.IsKnown() && !action.IsNull() {
forward := action.GetAttr("forward")
if forward.IsKnown() && forward.LengthInt() > 0 {
actionMap["forward"] = flattenLbListenerActionForwardConfig(awsAction.ForwardConfig)
} else {
actionMap["target_group_arn"] = aws.ToString(awsAction.TargetGroupArn)
}
}
}
}
}

func flattenLbForwardActionBoth(awsAction awstypes.Action, actionMap map[string]any) {
actionMap["target_group_arn"] = aws.ToString(awsAction.TargetGroupArn)
actionMap["forward"] = flattenLbListenerActionForwardConfig(awsAction.ForwardConfig)
}

func flattenMutualAuthenticationAttributes(description *awstypes.MutualAuthenticationAttributes) []interface{} {
if description == nil {
return []interface{}{}
Expand Down Expand Up @@ -1195,7 +1243,8 @@ func listenerActionPlantimeValidate(actionPath cty.Path, action cty.Value, diags
tga := action.GetAttr("target_group_arn")
f := action.GetAttr("forward")

if !tga.IsNull() && (!f.IsNull() && f.LengthInt() > 0) {
// If `ignore_changes` is set, even if there is no value in the configuration, the value in RawConfig is "" on refresh.
if (tga.IsKnown() && !tga.IsNull() && tga.AsString() != "") && (f.IsKnown() && !f.IsNull() && f.LengthInt() > 0) {
*diags = append(*diags, errs.NewAttributeErrorDiagnostic(actionPath,
"Invalid Attribute Combination",
fmt.Sprintf("Only one of %q or %q can be specified.",
Expand Down Expand Up @@ -1321,3 +1370,15 @@ func listenerActionRuntimeValidate(actionPath cty.Path, action map[string]any, d
}
}
}

func diffSuppressMissingForward(attrName string) schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
if regexache.MustCompile(fmt.Sprintf(`^%s\.\d+\.forward\.#$`, attrName)).MatchString(k) {
return old == "1" && new == "0"
}
if regexache.MustCompile(fmt.Sprintf(`^%s\.\d+\.forward\.\d+\.target_group\.#$`, attrName)).MatchString(k) {
return old == "1" && new == "0"
}
return false
}
}
21 changes: 11 additions & 10 deletions internal/service/elbv2/listener_certificate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import (
"fmt"
"testing"

"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/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
awstypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/errs"
tfelbv2 "github.com/hashicorp/terraform-provider-aws/internal/service/elbv2"
)

Expand Down Expand Up @@ -211,33 +212,33 @@ func TestAccELBV2ListenerCertificate_disappears_Listener(t *testing.T) {

func testAccCheckListenerCertificateDestroy(ctx context.Context) resource.TestCheckFunc {
return func(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Conn(ctx)
conn := acctest.Provider.Meta().(*conns.AWSClient).ELBV2Client(ctx)

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_lb_listener_certificate" {
continue
}

input := &elbv2.DescribeListenerCertificatesInput{
input := &elasticloadbalancingv2.DescribeListenerCertificatesInput{
ListenerArn: aws.String(rs.Primary.Attributes["listener_arn"]),
PageSize: aws.Int64(400),
PageSize: aws.Int32(400),
}

resp, err := conn.DescribeListenerCertificatesWithContext(ctx, input)
resp, err := conn.DescribeListenerCertificates(ctx, input)
if err != nil {
if tfawserr.ErrCodeEquals(err, elbv2.ErrCodeListenerNotFoundException) {
if errs.IsA[*awstypes.ListenerNotFoundException](err) {
return nil
}
return err
}

for _, cert := range resp.Certificates {
// We only care about additional certificates.
if aws.BoolValue(cert.IsDefault) {
if aws.ToBool(cert.IsDefault) {
continue
}

if aws.StringValue(cert.CertificateArn) == rs.Primary.Attributes["certificate_arn"] {
if aws.ToString(cert.CertificateArn) == rs.Primary.Attributes["certificate_arn"] {
return errors.New("LB listener certificate not destroyed")
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/service/elbv2/listener_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func dataSourceListenerRead(ctx context.Context, d *schema.ResourceData, meta in
sort.Slice(listener.DefaultActions, func(i, j int) bool {
return aws.ToInt32(listener.DefaultActions[i].Order) < aws.ToInt32(listener.DefaultActions[j].Order)
})
if err := d.Set("default_action", flattenLbListenerActions(d, listener.DefaultActions)); err != nil {
if err := d.Set("default_action", flattenLbListenerActions(d, "default_action", listener.DefaultActions)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting default_action: %s", err)
}
d.Set("load_balancer_arn", listener.LoadBalancerArn)
Expand Down
44 changes: 7 additions & 37 deletions internal/service/elbv2/listener_rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ func ResourceListenerRule() *schema.Resource {
},

"forward": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Type: schema.TypeList,
Optional: true,
DiffSuppressOnRefresh: true,
DiffSuppressFunc: diffSuppressMissingForward("action"),
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"target_group": {
Expand Down Expand Up @@ -591,41 +593,9 @@ func resourceListenerRuleRead(ctx context.Context, d *schema.ResourceData, meta
sort.Slice(rule.Actions, func(i, j int) bool {
return aws.ToInt32(rule.Actions[i].Order) < aws.ToInt32(rule.Actions[j].Order)
})
actions := make([]interface{}, len(rule.Actions))
for i, action := range rule.Actions {
actionMap := map[string]interface{}{
"type": string(action.Type),
"order": aws.ToInt32(action.Order),
}

switch action.Type {
case awstypes.ActionTypeEnumForward:
if aws.ToString(action.TargetGroupArn) != "" {
actionMap["target_group_arn"] = aws.ToString(action.TargetGroupArn)
} else {
actionMap["forward"] = flattenLbListenerActionForwardConfig(action.ForwardConfig)
}

case awstypes.ActionTypeEnumRedirect:
actionMap["redirect"] = flattenLbListenerActionRedirectConfig(action.RedirectConfig)

case awstypes.ActionTypeEnumFixedResponse:
actionMap["fixed_response"] = flattenLbListenerActionFixedResponseConfig(action.FixedResponseConfig)

case awstypes.ActionTypeEnumAuthenticateCognito:
actionMap["authenticate_cognito"] = flattenLbListenerActionAuthenticateCognitoConfig(action.AuthenticateCognitoConfig)

case awstypes.ActionTypeEnumAuthenticateOidc:
// The LB API currently provides no way to read the ClientSecret
// Instead we passthrough the configuration value into the state
clientSecret := d.Get("action." + strconv.Itoa(i) + ".authenticate_oidc.0.client_secret").(string)

actionMap["authenticate_oidc"] = flattenAuthenticateOIDCActionConfig(action.AuthenticateOidcConfig, clientSecret)
}

actions[i] = actionMap
if err := d.Set("action", flattenLbListenerActions(d, "action", rule.Actions)); err != nil {
return sdkdiag.AppendErrorf(diags, "setting action: %s", err)
}
d.Set("action", actions)

conditions := make([]interface{}, len(rule.Conditions))
for i, condition := range rule.Conditions {
Expand Down
Loading
Loading