Skip to content

Commit

Permalink
Merge pull request #35671 from hashicorp/b-lb-listener-stickiness-3
Browse files Browse the repository at this point in the history
Fixes errors with `target_group_arn` and `forward` for `aws_lb_listener` and `aws_lb_listener_rule`
  • Loading branch information
gdavison authored Feb 7, 2024
2 parents 18afc65 + 6e3cc02 commit 32a681f
Show file tree
Hide file tree
Showing 9 changed files with 2,762 additions and 622 deletions.
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

0 comments on commit 32a681f

Please sign in to comment.