Skip to content

Commit 38d8ce7

Browse files
authored
Merge pull request #4281 from pascal-hofmann/action-annotation-with-target-group-name
feat: allow targetGroupName instead of targetGroupARN in forward action ingress annotations
2 parents f20c9a3 + a64b174 commit 38d8ce7

File tree

7 files changed

+242
-21
lines changed

7 files changed

+242
-21
lines changed

docs/guide/ingress/annotations.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -285,18 +285,19 @@ Traffic Routing can be controlled with following annotations:
285285

286286
The `action-name` in the annotation must match the serviceName in the Ingress rules, and servicePort must be `use-annotation`.
287287

288-
!!!note "use ARN in forward Action"
289-
ARN can be used in forward action(both simplified schema and advanced schema), it must be an targetGroup created outside of k8s, typically an targetGroup for legacy application.
288+
!!!note "use TargetGroupARN/TargetGroupName in forward Action"
289+
TargetGroupARN/TargetGroupName can be used in forward action (both simplified schema and advanced schema), it must be a target group created outside of k8s, typically a targetGroup for a legacy application.
290290
!!!note "use ServiceName/ServicePort in forward Action"
291-
ServiceName/ServicePort can be used in forward action(advanced schema only).
291+
ServiceName/ServicePort can be used in forward action (advanced schema only).
292292

293293
!!!warning ""
294-
[Auth related annotations](#authentication) on Service object will only be respected if a single TargetGroup in is used.
294+
[Auth related annotations](#authentication) on a Service object will only be respected if a single TargetGroup is used.
295295

296296
!!!example
297297
- response-503: return fixed 503 response
298298
- redirect-to-eks: redirect to an external url
299299
- forward-single-tg: forward to a single targetGroup [**simplified schema**]
300+
- forward-single-tg-by-name: forward to a single targetGroup identified by its name [**simplified schema**]
300301
- forward-multiple-tg: forward to multiple targetGroups with different weights and stickiness config [**advanced schema**]
301302

302303
```yaml
@@ -313,8 +314,10 @@ Traffic Routing can be controlled with following annotations:
313314
{"type":"redirect","redirectConfig":{"host":"aws.amazon.com","path":"/eks/","port":"443","protocol":"HTTPS","query":"k=v","statusCode":"HTTP_302"}}
314315
alb.ingress.kubernetes.io/actions.forward-single-tg: >
315316
{"type":"forward","targetGroupARN": "arn-of-your-target-group"}
317+
alb.ingress.kubernetes.io/actions.forward-single-tg-by-name: >
318+
{"type":"forward","targetGroupName": "name-of-your-target-group"}
316319
alb.ingress.kubernetes.io/actions.forward-multiple-tg: >
317-
{"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"service-1","servicePort":"http","weight":20},{"serviceName":"service-2","servicePort":80,"weight":20},{"targetGroupARN":"arn-of-your-non-k8s-target-group","weight":60}],"targetGroupStickinessConfig":{"enabled":true,"durationSeconds":200}}}
320+
{"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"service-1","servicePort":"http","weight":20},{"serviceName":"service-2","servicePort":80,"weight":20},{"targetGroupARN":"arn-of-your-non-k8s-target-group","weight":60},{"targetGroupName":"name-of-your-non-k8s-target-group","weight":80}],"targetGroupStickinessConfig":{"enabled":true,"durationSeconds":200}}}
318321
spec:
319322
ingressClassName: alb
320323
rules:
@@ -342,6 +345,13 @@ Traffic Routing can be controlled with following annotations:
342345
port:
343346
name: use-annotation
344347
- path: /path2
348+
pathType: Exact
349+
backend:
350+
service:
351+
name: forward-single-tg-by-name
352+
port:
353+
name: use-annotation
354+
- path: /path3
345355
pathType: Exact
346356
backend:
347357
service:
@@ -647,7 +657,7 @@ ALB supports authentication with Cognito or OIDC. See [Authenticate Users Using
647657
- <a name="auth-idp-oidc">`alb.ingress.kubernetes.io/auth-idp-oidc`</a> specifies the oidc idp configuration.
648658

649659
!!!tip ""
650-
You need to create an [secret](https://kubernetes.io/docs/concepts/configuration/secret/) within the same namespace as Ingress to hold your OIDC clientID and clientSecret. The format of secret is as below:
660+
You need to create a [secret](https://kubernetes.io/docs/concepts/configuration/secret/) within the same namespace as Ingress to hold your OIDC clientID and clientSecret. The format of secret is as below:
651661
```yaml
652662
apiVersion: v1
653663
kind: Secret
@@ -667,7 +677,7 @@ ALB supports authentication with Cognito or OIDC. See [Authenticate Users Using
667677
- <a name="auth-on-unauthenticated-request">`alb.ingress.kubernetes.io/auth-on-unauthenticated-request`</a> specifies the behavior if the user is not authenticated.
668678

669679
!!!info "options:"
670-
* **authenticate**: try authenticate with configured IDP.
680+
* **authenticate**: try to authenticate with configured IDP.
671681
* **deny**: return an HTTP 401 Unauthorized error.
672682
* **allow**: allow the request to be forwarded to the target.
673683

@@ -837,10 +847,10 @@ TLS support can be controlled with the following annotations:
837847
- This annotation is not applicable for Outposts, Local Zones or Wavelength zones.
838848
- "Configuration Options"
839849
- `port: listen port `
840-
- Must be a HTTPS port specified by [listen-ports](#listen-ports).
850+
- Must be an HTTPS port specified by [listen-ports](#listen-ports).
841851
- `mode: "off" (default) | "passthrough" | "verify"`
842852
- `verify` mode requires an existing trust store resource.
843-
- See [Create a trust store](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html#create-trust-store) in the AWS documentation for more details.
853+
- See [Create a trust store](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html#create-trust-store) in the AWS documentation for more details.
844854
- `trustStore: ARN (arn:aws:elasticloadbalancing:trustStoreArn) | Name (my-trust-store)`
845855
- Both ARN and Name of trustStore are supported values.
846856
- `trustStore` is required when mode is `verify`.

pkg/ingress/config_types.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ type TargetGroupTuple struct {
6868
// The Amazon Resource Name (ARN) of the target group.
6969
TargetGroupARN *string `json:"targetGroupARN"`
7070

71+
// The Name of the target group.
72+
// +optional
73+
TargetGroupName *string `json:"targetGroupName,omitempty"`
74+
7175
// the K8s service Name
7276
ServiceName *string `json:"serviceName"`
7377

@@ -80,8 +84,12 @@ type TargetGroupTuple struct {
8084
}
8185

8286
func (t *TargetGroupTuple) validate() error {
83-
if (t.TargetGroupARN != nil) == (t.ServiceName != nil) {
84-
return errors.New("precisely one of targetGroupARN and serviceName can be specified")
87+
if t.TargetGroupARN == nil && t.TargetGroupName == nil && t.ServiceName == nil {
88+
return errors.New("missing serviceName or targetGroupARN/targetGroupName")
89+
}
90+
91+
if (t.TargetGroupARN != nil || t.TargetGroupName != nil) && t.ServiceName != nil {
92+
return errors.New("either serviceName or targetGroupARN/targetGroupName can be specified")
8593
}
8694

8795
if t.ServiceName != nil && t.ServicePort == nil {
@@ -146,6 +154,9 @@ type Action struct {
146154
// or more target groups, use ForwardConfig instead.
147155
TargetGroupARN *string `json:"targetGroupARN"`
148156

157+
// Name of the target group. Can be specified instead of TargetGroupARN.
158+
TargetGroupName *string `json:"targetGroupName,omitempty"`
159+
149160
// [Application Load Balancer] Information for creating an action that returns a custom HTTP response.
150161
// +optional
151162
FixedResponseConfig *FixedResponseActionConfig `json:"fixedResponseConfig,omitempty"`
@@ -176,8 +187,11 @@ func (a *Action) validate() error {
176187
return errors.Wrap(err, "invalid RedirectConfig")
177188
}
178189
case ActionTypeForward:
179-
if (a.TargetGroupARN != nil) == (a.ForwardConfig != nil) {
180-
return errors.New("precisely one of TargetGroupArn and ForwardConfig can be specified")
190+
if a.TargetGroupARN == nil && a.TargetGroupName == nil && a.ForwardConfig == nil {
191+
return errors.New("missing ForwardConfig or TargetGroupARN/TargetGroupName")
192+
}
193+
if (a.TargetGroupARN != nil || a.TargetGroupName != nil) && (a.ForwardConfig != nil) {
194+
return errors.New("either ForwardConfig or TargetGroupARN/TargetGroupName can be specified")
181195
}
182196
if a.ForwardConfig != nil {
183197
if err := a.ForwardConfig.validate(); err != nil {

pkg/ingress/enhanced_backend_builder.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,14 @@ func (b *defaultEnhancedBackendBuilder) buildActionViaServiceAndServicePort(_ co
208208
// normalizeSimplifiedSchemaForwardAction will normalize to the advanced schema for forward action to share common processing logic.
209209
// we support a simplified schema in action annotation when configure forward to a single TargetGroup.
210210
func (b *defaultEnhancedBackendBuilder) normalizeSimplifiedSchemaForwardAction(_ context.Context, action *Action) {
211-
if action.Type == ActionTypeForward && action.TargetGroupARN != nil {
211+
if action.Type == ActionTypeForward && (action.TargetGroupARN != nil || action.TargetGroupName != nil) {
212212
*action = Action{
213213
Type: ActionTypeForward,
214214
ForwardConfig: &ForwardActionConfig{
215215
TargetGroups: []TargetGroupTuple{
216216
{
217-
TargetGroupARN: action.TargetGroupARN,
217+
TargetGroupARN: action.TargetGroupARN,
218+
TargetGroupName: action.TargetGroupName,
218219
},
219220
},
220221
},

pkg/ingress/enhanced_backend_builder_test.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1048,11 +1048,30 @@ func Test_defaultEnhancedBackendBuilder_buildActionViaAnnotation(t *testing.T) {
10481048
},
10491049
},
10501050
},
1051+
{
1052+
name: "forward action - simplified schema with target group name",
1053+
args: args{
1054+
ingAnnotation: map[string]string{
1055+
"alb.ingress.kubernetes.io/actions.forward-single-tg": `{"type":"forward","targetGroupName": "tg-name"}`,
1056+
},
1057+
svcName: "forward-single-tg",
1058+
},
1059+
want: Action{
1060+
Type: ActionTypeForward,
1061+
ForwardConfig: &ForwardActionConfig{
1062+
TargetGroups: []TargetGroupTuple{
1063+
{
1064+
TargetGroupName: awssdk.String("tg-name"),
1065+
},
1066+
},
1067+
},
1068+
},
1069+
},
10511070
{
10521071
name: "forward action - advanced schema",
10531072
args: args{
10541073
ingAnnotation: map[string]string{
1055-
"alb.ingress.kubernetes.io/actions.forward-multiple-tg": `{"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"service-1","servicePort":"http","weight":20},{"serviceName":"service-2","servicePort":80,"weight":20},{"targetGroupARN":"tg-arn","weight":60}],"targetGroupStickinessConfig":{"enabled":true,"durationSeconds":200}}}`,
1074+
"alb.ingress.kubernetes.io/actions.forward-multiple-tg": `{"type":"forward","forwardConfig":{"targetGroups":[{"serviceName":"service-1","servicePort":"http","weight":20},{"serviceName":"service-2","servicePort":80,"weight":20},{"targetGroupARN":"tg-arn","weight":60},{"targetGroupName":"tg-name","weight":80}],"targetGroupStickinessConfig":{"enabled":true,"durationSeconds":200}}}`,
10561075
},
10571076
svcName: "forward-multiple-tg",
10581077
},
@@ -1074,6 +1093,10 @@ func Test_defaultEnhancedBackendBuilder_buildActionViaAnnotation(t *testing.T) {
10741093
TargetGroupARN: awssdk.String("tg-arn"),
10751094
Weight: awssdk.Int32(60),
10761095
},
1096+
{
1097+
TargetGroupName: awssdk.String("tg-name"),
1098+
Weight: awssdk.Int32(80),
1099+
},
10771100
},
10781101
TargetGroupStickinessConfig: &TargetGroupStickinessConfig{
10791102
Enabled: awssdk.Bool(true),

pkg/ingress/model_build_actions.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ func (t *defaultModelBuildTask) buildForwardAction(ctx context.Context, ing Clas
105105
var tgARN core.StringToken
106106
if tgt.TargetGroupARN != nil {
107107
tgARN = core.LiteralStringToken(*tgt.TargetGroupARN)
108+
} else if tgt.TargetGroupName != nil {
109+
targetGroupARN, err := t.targetGroupNameToArnMapper.getArnByName(ctx, *tgt.TargetGroupName)
110+
if err != nil {
111+
return elbv2model.Action{}, fmt.Errorf("searching TargetGroup with name %s: %w", *tgt.TargetGroupName, err)
112+
}
113+
tgARN = core.LiteralStringToken(targetGroupARN)
108114
} else {
109115
svcKey := types.NamespacedName{
110116
Namespace: ing.Ing.Namespace,

pkg/ingress/model_build_actions_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ package ingress
33
import (
44
"context"
55
awssdk "github.com/aws/aws-sdk-go-v2/aws"
6+
elbv2sdk "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2"
7+
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
8+
"github.com/golang/mock/gomock"
69
"github.com/pkg/errors"
710
"github.com/stretchr/testify/assert"
811
corev1 "k8s.io/api/core/v1"
912
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1013
"k8s.io/apimachinery/pkg/runtime"
1114
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
15+
"sigs.k8s.io/aws-load-balancer-controller/pkg/aws/services"
16+
"sigs.k8s.io/aws-load-balancer-controller/pkg/model/core"
1217
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
1318
testclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
1419
"testing"
@@ -339,3 +344,117 @@ func Test_defaultModelBuildTask_buildSSLRedirectAction(t *testing.T) {
339344
})
340345
}
341346
}
347+
348+
func Test_defaultModelBuildTask_buildForwardActionWithTargetGroupName(t *testing.T) {
349+
type describeTargetGroupsAsListCall struct {
350+
req *elbv2sdk.DescribeTargetGroupsInput
351+
resp []elbv2types.TargetGroup
352+
err error
353+
}
354+
type args struct {
355+
ingress ClassifiedIngress
356+
forwardActionConfig ForwardActionConfig
357+
describeTargetGroupsAsListCalls []describeTargetGroupsAsListCall
358+
cache map[string]string
359+
}
360+
tests := []struct {
361+
name string
362+
args args
363+
want elbv2model.Action
364+
wantErr error
365+
wantCache map[string]string
366+
}{
367+
{
368+
name: "Forward to target group identified by name",
369+
args: args{
370+
forwardActionConfig: ForwardActionConfig{
371+
TargetGroups: []TargetGroupTuple{{TargetGroupName: awssdk.String("tg-name1")}},
372+
},
373+
374+
describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{
375+
{
376+
req: &elbv2sdk.DescribeTargetGroupsInput{
377+
Names: []string{"tg-name1"},
378+
},
379+
resp: []elbv2types.TargetGroup{
380+
{
381+
TargetGroupArn: awssdk.String("tg-arn1"),
382+
TargetType: elbv2types.TargetTypeEnum("instance"),
383+
},
384+
},
385+
},
386+
},
387+
cache: map[string]string{},
388+
},
389+
want: elbv2model.Action{
390+
Type: elbv2model.ActionTypeForward,
391+
ForwardConfig: &elbv2model.ForwardActionConfig{
392+
TargetGroups: []elbv2model.TargetGroupTuple{{
393+
TargetGroupARN: core.LiteralStringToken("tg-arn1"),
394+
}},
395+
},
396+
},
397+
wantCache: map[string]string{
398+
"tg-name1": "tg-arn1",
399+
},
400+
},
401+
{
402+
name: "Forward to target group identified by name is cached",
403+
args: args{
404+
forwardActionConfig: ForwardActionConfig{
405+
TargetGroups: []TargetGroupTuple{{TargetGroupName: awssdk.String("tg-name2")}},
406+
},
407+
408+
describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{},
409+
cache: map[string]string{
410+
"tg-name2": "tg-arn2",
411+
},
412+
},
413+
want: elbv2model.Action{
414+
Type: elbv2model.ActionTypeForward,
415+
ForwardConfig: &elbv2model.ForwardActionConfig{
416+
TargetGroups: []elbv2model.TargetGroupTuple{{
417+
TargetGroupARN: core.LiteralStringToken("tg-arn2"),
418+
}},
419+
},
420+
},
421+
wantCache: map[string]string{
422+
"tg-name2": "tg-arn2",
423+
},
424+
},
425+
}
426+
427+
for _, tt := range tests {
428+
t.Run(tt.name, func(t1 *testing.T) {
429+
ctrl := gomock.NewController(t)
430+
defer ctrl.Finish()
431+
432+
elbv2Client := services.NewMockELBV2(ctrl)
433+
for _, call := range tt.args.describeTargetGroupsAsListCalls {
434+
elbv2Client.EXPECT().DescribeTargetGroupsAsList(gomock.Any(), call.req).Return(call.resp, call.err)
435+
}
436+
task := &defaultModelBuildTask{
437+
elbv2Client: elbv2Client,
438+
targetGroupNameToArnMapper: newTargetGroupNameToArnMapper(elbv2Client, defaultTargetGroupNameToARNCacheTTL),
439+
}
440+
441+
for targetGroupName, cachedArn := range tt.args.cache {
442+
task.targetGroupNameToArnMapper.cache.Set(targetGroupName, cachedArn, defaultTargetGroupNameToARNCacheTTL)
443+
}
444+
445+
got, err := task.buildForwardAction(context.Background(), tt.args.ingress, Action{
446+
Type: ActionTypeForward,
447+
ForwardConfig: &tt.args.forwardActionConfig,
448+
})
449+
assert.Equal(t, tt.want, got)
450+
assert.Equal(t, tt.wantErr, err)
451+
assert.Equal(t, len(tt.wantCache), task.targetGroupNameToArnMapper.cache.Len())
452+
for targetGroupName, expectedArn := range tt.wantCache {
453+
rawCacheItem, exists := task.targetGroupNameToArnMapper.cache.Get(targetGroupName)
454+
assert.True(t, exists)
455+
cachedArn := rawCacheItem.(string)
456+
assert.Equal(t, expectedArn, cachedArn)
457+
}
458+
})
459+
}
460+
}

0 commit comments

Comments
 (0)