Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apis/v1alpha1/ack-generate-metadata.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
ack_generate_info:
build_date: "2026-06-22T23:03:45Z"
build_date: "2026-06-26T19:36:08Z"
build_hash: 2ae5d2cfadaa2a10b2ccb9e73a111b2a91c36642
go_version: go1.26.4
version: v0.60.0
api_directory_checksum: 060554dd6962e2466013922cf96fb4cf92a23706
api_version: v1alpha1
aws_sdk_go_version: v1.32.6
generator_config_info:
file_checksum: ce1168f649f03d9652bc8e82f8322d6dd2d909f0
file_checksum: 25ce95e291471e0727155bed0a1a43b2c4a7ce1c
original_file_name: generator.yaml
last_modification:
reason: API generation
8 changes: 8 additions & 0 deletions apis/v1alpha1/generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ resources:
references:
resource: Listener
path: Status.ACKResourceMetadata.ARN
Actions:
compare:
is_ignored: true
set:
- ignore: true
method: Create
- ignore: true
method: Update
Actions.targetGroupARN:
references:
resource: TargetGroup
Expand Down
8 changes: 8 additions & 0 deletions generator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,14 @@ resources:
references:
resource: Listener
path: Status.ACKResourceMetadata.ARN
Actions:
compare:
is_ignored: true
set:
- ignore: true
method: Create
- ignore: true
method: Update
Actions.targetGroupARN:
references:
resource: TargetGroup
Expand Down
7 changes: 0 additions & 7 deletions pkg/resource/rule/delta.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions pkg/resource/rule/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,65 @@ func customPreCompare(
b *resource,
) {
customCompareConditions(delta, a, b)
customCompareActions(delta, a, b)
}

// customCompareActions performs custom comparison for Rule actions.
// Actions is compared manually (compare.is_ignored: true) because the AWS
// ELBv2 API never returns the k8s-only TargetGroupRef fields and assigns an
// Order when the user omits one. Comparing the auto-generated way would always
// report a diff (the ref present in desired but absent in the observed state),
// triggering a redundant ModifyRule on every reconcile. We strip the ref fields
// and normalize the server-assigned Order before comparing; the resolved
// TargetGroupARN is still compared so genuine drift is detected.
func customCompareActions(
delta *ackcompare.Delta,
a *resource,
b *resource,
) {
if a == nil || b == nil {
return
}

desired := a.ko.Spec.Actions
observed := b.ko.Spec.Actions
if len(desired) != len(observed) {
delta.Add("Spec.Actions", desired, observed)
return
}

for i := range desired {
d := normalizeActionForCompare(desired[i])
o := normalizeActionForCompare(observed[i])
// AWS assigns an Order when the user does not specify one, so only
// compare Order when it was set in the desired state.
if desired[i] != nil && desired[i].Order == nil {
o.Order = nil
}
if !equality.Semantic.Equalities.DeepEqual(d, o) {
delta.Add("Spec.Actions", desired, observed)
return
}
}
}

// normalizeActionForCompare returns a deep copy of the action with the k8s-only
// TargetGroupRef fields removed, both at the action level and within
// ForwardConfig.TargetGroups, so they do not produce spurious diffs.
func normalizeActionForCompare(action *svcapitypes.Action) *svcapitypes.Action {
if action == nil {
return nil
}
a := action.DeepCopy()
a.TargetGroupRef = nil
if a.ForwardConfig != nil {
for j := range a.ForwardConfig.TargetGroups {
if a.ForwardConfig.TargetGroups[j] != nil {
a.ForwardConfig.TargetGroups[j].TargetGroupRef = nil
}
}
}
return a
}

// customCompareConditions performs custom comparison for Rule conditions.
Expand Down
225 changes: 225 additions & 0 deletions pkg/resource/rule/hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,20 @@ package rule
import (
"testing"

ackv1alpha1 "github.com/aws-controllers-k8s/runtime/apis/core/v1alpha1"
ackcompare "github.com/aws-controllers-k8s/runtime/pkg/compare"
"github.com/aws/aws-sdk-go/aws"
"github.com/stretchr/testify/assert"

svcapitypes "github.com/aws-controllers-k8s/elbv2-controller/apis/v1alpha1"
)

func tgRef(name string) *ackv1alpha1.AWSResourceReferenceWrapper {
return &ackv1alpha1.AWSResourceReferenceWrapper{
From: &ackv1alpha1.AWSResourceReference{Name: aws.String(name)},
}
}

func TestCustomCompareConditions(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -693,3 +700,221 @@ func TestCustomCompareConditions(t *testing.T) {
})
}
}

func TestCustomCompareActions(t *testing.T) {
tests := []struct {
name string
desired *resource
observed *resource
expectDelta bool
}{
{
name: "action-level ref in desired, absent in observed, same ARN - no delta",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
TargetGroupRef: tgRef("my-tg"),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
expectDelta: false,
},
{
name: "forwardConfig nested ref in desired, absent in observed, same ARN - no delta",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
ForwardConfig: &svcapitypes.ForwardActionConfig{
TargetGroups: []*svcapitypes.TargetGroupTuple{
{
TargetGroupRef: tgRef("my-tg"),
TargetGroupARN: aws.String("arn:tg/abc"),
Weight: aws.Int64(1),
},
},
},
},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
ForwardConfig: &svcapitypes.ForwardActionConfig{
TargetGroups: []*svcapitypes.TargetGroupTuple{
{
TargetGroupARN: aws.String("arn:tg/abc"),
Weight: aws.Int64(1),
},
},
},
},
},
},
},
},
expectDelta: false,
},
{
name: "server-assigned Order, not set in desired - no delta",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
Order: aws.Int64(1),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
expectDelta: false,
},
{
name: "different resolved ARN - delta expected",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
TargetGroupRef: tgRef("my-tg"),
TargetGroupARN: aws.String("arn:tg/new"),
},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
TargetGroupARN: aws.String("arn:tg/old"),
},
},
},
},
},
expectDelta: true,
},
{
name: "different Order both set - delta expected",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
Order: aws.Int64(1),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{
Type: aws.String("forward"),
Order: aws.Int64(2),
TargetGroupARN: aws.String("arn:tg/abc"),
},
},
},
},
},
expectDelta: true,
},
{
name: "different action count - delta expected",
desired: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{Type: aws.String("forward"), TargetGroupARN: aws.String("arn:tg/abc")},
},
},
},
},
observed: &resource{
ko: &svcapitypes.Rule{
Spec: svcapitypes.RuleSpec{
Actions: []*svcapitypes.Action{
{Type: aws.String("forward"), TargetGroupARN: aws.String("arn:tg/abc")},
{Type: aws.String("forward"), TargetGroupARN: aws.String("arn:tg/def")},
},
},
},
},
expectDelta: true,
},
{
name: "nil desired resource should not panic",
desired: nil,
observed: &resource{ko: &svcapitypes.Rule{}},
expectDelta: false,
},
{
name: "nil observed resource should not panic",
desired: &resource{ko: &svcapitypes.Rule{}},
observed: nil,
expectDelta: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
delta := ackcompare.NewDelta()
customCompareActions(delta, tt.desired, tt.observed)

if tt.expectDelta {
assert.True(t, len(delta.Differences) > 0, "Expected delta but got none")
} else {
assert.Equal(t, 0, len(delta.Differences), "Expected no delta but got: %v", delta.Differences)
}
})
}
}
Loading