Skip to content

Commit 283ebee

Browse files
fatmcgavmarcosdiez
andauthored
TargetGroupBinding now support targetGroupName - #2655 refresh (#3903)
* TargetGroupBinding now support targetGroupName * documentation for targetGroupName * unit tests now validate if either either TargetGroupARN or TargetGroupName has been provided * more unit tests * updated documentation * Unit Test for MutateCreate * implemented changes requested by reviewers * Fixes following migration to `aws-sdk-go-v2` * make crds Also tweaked the description for the `TargetGroupName` field --------- Co-authored-by: Marcos Diez <[email protected]>
1 parent 8ba34e2 commit 283ebee

9 files changed

+258
-26
lines changed

apis/elbv2/v1alpha1/targetgroupbinding_types.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,12 @@ type TargetGroupBindingNetworking struct {
107107
// TargetGroupBindingSpec defines the desired state of TargetGroupBinding
108108
type TargetGroupBindingSpec struct {
109109
// targetGroupARN is the Amazon Resource Name (ARN) for the TargetGroup.
110-
TargetGroupARN string `json:"targetGroupARN"`
110+
// +optional
111+
TargetGroupARN string `json:"targetGroupARN,omitempty"`
112+
113+
// targetGroupName is the Name of the TargetGroup.
114+
// +optional
115+
TargetGroupName string `json:"targetGroupName,omitempty"`
111116

112117
// MultiClusterTargetGroup Denotes if the TargetGroup is shared among multiple clusters
113118
// +optional
@@ -138,6 +143,7 @@ type TargetGroupBindingStatus struct {
138143
// +kubebuilder:printcolumn:name="SERVICE-PORT",type="string",JSONPath=".spec.serviceRef.port",description="The Kubernetes Service's port"
139144
// +kubebuilder:printcolumn:name="TARGET-TYPE",type="string",JSONPath=".spec.targetType",description="The AWS TargetGroup's TargetType"
140145
// +kubebuilder:printcolumn:name="ARN",type="string",JSONPath=".spec.targetGroupARN",description="The AWS TargetGroup's Amazon Resource Name",priority=1
146+
// +kubebuilder:printcolumn:name="NAME",type="string",JSONPath=".spec.targetGroupName",description="The AWS TargetGroup's Name",priority=2
141147
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
142148
// TargetGroupBinding is the Schema for the TargetGroupBinding API
143149
type TargetGroupBinding struct {

apis/elbv2/v1beta1/targetgroupbinding_types.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,12 @@ type TargetGroupBindingNetworking struct {
124124
// TargetGroupBindingSpec defines the desired state of TargetGroupBinding
125125
type TargetGroupBindingSpec struct {
126126
// targetGroupARN is the Amazon Resource Name (ARN) for the TargetGroup.
127-
// +kubebuilder:validation:MinLength=1
128-
TargetGroupARN string `json:"targetGroupARN"`
127+
// +optional
128+
TargetGroupARN string `json:"targetGroupARN,omitempty"`
129+
130+
// targetGroupName is the Name of the TargetGroup.
131+
// +optional
132+
TargetGroupName string `json:"targetGroupName,omitempty"`
129133

130134
// MultiClusterTargetGroup Denotes if the TargetGroup is shared among multiple clusters
131135
// +optional
@@ -169,6 +173,7 @@ type TargetGroupBindingStatus struct {
169173
// +kubebuilder:printcolumn:name="SERVICE-PORT",type="string",JSONPath=".spec.serviceRef.port",description="The Kubernetes Service's port"
170174
// +kubebuilder:printcolumn:name="TARGET-TYPE",type="string",JSONPath=".spec.targetType",description="The AWS TargetGroup's TargetType"
171175
// +kubebuilder:printcolumn:name="ARN",type="string",JSONPath=".spec.targetGroupARN",description="The AWS TargetGroup's Amazon Resource Name",priority=1
176+
// +kubebuilder:printcolumn:name="NAME",type="string",JSONPath=".spec.targetGroupName",description="The AWS TargetGroup's Name",priority=2
172177
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
173178
// TargetGroupBinding is the Schema for the TargetGroupBinding API
174179
type TargetGroupBinding struct {

config/crd/bases/elbv2.k8s.aws_targetgroupbindings.yaml

+16-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ spec:
3232
name: ARN
3333
priority: 1
3434
type: string
35+
- description: The AWS TargetGroup's Name
36+
jsonPath: .spec.targetGroupName
37+
name: NAME
38+
priority: 2
39+
type: string
3540
- jsonPath: .metadata.creationTimestamp
3641
name: AGE
3742
type: date
@@ -160,6 +165,9 @@ spec:
160165
description: targetGroupARN is the Amazon Resource Name (ARN) for
161166
the TargetGroup.
162167
type: string
168+
targetGroupName:
169+
description: targetGroupName is the Name of the TargetGroup.
170+
type: string
163171
targetType:
164172
description: targetType is the TargetType of TargetGroup. If unspecified,
165173
it will be automatically inferred.
@@ -169,7 +177,6 @@ spec:
169177
type: string
170178
required:
171179
- serviceRef
172-
- targetGroupARN
173180
type: object
174181
status:
175182
description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding
@@ -202,6 +209,11 @@ spec:
202209
name: ARN
203210
priority: 1
204211
type: string
212+
- description: The AWS TargetGroup's Name
213+
jsonPath: .spec.targetGroupName
214+
name: NAME
215+
priority: 2
216+
type: string
205217
- jsonPath: .metadata.creationTimestamp
206218
name: AGE
207219
type: date
@@ -387,7 +399,9 @@ spec:
387399
targetGroupARN:
388400
description: targetGroupARN is the Amazon Resource Name (ARN) for
389401
the TargetGroup.
390-
minLength: 1
402+
type: string
403+
targetGroupName:
404+
description: targetGroupName is the Name of the TargetGroup.
391405
type: string
392406
targetType:
393407
description: targetType is the TargetType of TargetGroup. If unspecified,
@@ -402,7 +416,6 @@ spec:
402416
type: string
403417
required:
404418
- serviceRef
405-
- targetGroupARN
406419
type: object
407420
status:
408421
description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding

docs/guide/targetgroupbinding/targetgroupbinding.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,25 @@ TargetGroupBinding CR supports TargetGroups of either `instance` or `ip` TargetT
1616
!!!tip ""
1717
If TargetType is not explicitly specified, a mutating webhook will automatically call AWS API to find the TargetType for your TargetGroup and set it to correct value.
1818

19+
## Choosing the Target Group
20+
One can either use ``targetGroupARN`` of ``targetGroupName`` to identify a Target Group. Although both are unique and immutable in an AWS region, one only has control of the ``targetGroupName``, for ``targetGroupARN`` is generated by AWS and contain random characters.
21+
22+
If you provide both ``targetGroupARN`` and ``targetGroupName``, beware that ``targetGroupARN`` prevails.
23+
24+
25+
## Sample YAMLs
26+
```yaml
27+
apiVersion: elbv2.k8s.aws/v1beta1
28+
kind: TargetGroupBinding
29+
metadata:
30+
name: my-tgb
31+
spec:
32+
serviceRef:
33+
name: awesome-service # route traffic to the awesome-service
34+
port: 80
35+
targetGroupName: <name-of-the-targetGroup>
36+
```
1937
20-
## Sample YAML
2138
```yaml
2239
apiVersion: elbv2.k8s.aws/v1beta1
2340
kind: TargetGroupBinding

helm/aws-load-balancer-controller/crds/crds.yaml

+16-3
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ spec:
272272
name: ARN
273273
priority: 1
274274
type: string
275+
- description: The AWS TargetGroup's Name
276+
jsonPath: .spec.targetGroupName
277+
name: NAME
278+
priority: 2
279+
type: string
275280
- jsonPath: .metadata.creationTimestamp
276281
name: AGE
277282
type: date
@@ -400,6 +405,9 @@ spec:
400405
description: targetGroupARN is the Amazon Resource Name (ARN) for
401406
the TargetGroup.
402407
type: string
408+
targetGroupName:
409+
description: targetGroupName is the Name of the TargetGroup.
410+
type: string
403411
targetType:
404412
description: targetType is the TargetType of TargetGroup. If unspecified,
405413
it will be automatically inferred.
@@ -409,7 +417,6 @@ spec:
409417
type: string
410418
required:
411419
- serviceRef
412-
- targetGroupARN
413420
type: object
414421
status:
415422
description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding
@@ -442,6 +449,11 @@ spec:
442449
name: ARN
443450
priority: 1
444451
type: string
452+
- description: The AWS TargetGroup's Name
453+
jsonPath: .spec.targetGroupName
454+
name: NAME
455+
priority: 2
456+
type: string
445457
- jsonPath: .metadata.creationTimestamp
446458
name: AGE
447459
type: date
@@ -627,7 +639,9 @@ spec:
627639
targetGroupARN:
628640
description: targetGroupARN is the Amazon Resource Name (ARN) for
629641
the TargetGroup.
630-
minLength: 1
642+
type: string
643+
targetGroupName:
644+
description: targetGroupName is the Name of the TargetGroup.
631645
type: string
632646
targetType:
633647
description: targetType is the TargetType of TargetGroup. If unspecified,
@@ -642,7 +656,6 @@ spec:
642656
type: string
643657
required:
644658
- serviceRef
645-
- targetGroupARN
646659
type: object
647660
status:
648661
description: TargetGroupBindingStatus defines the observed state of TargetGroupBinding

webhooks/elbv2/targetgroupbinding_mutator.go

+31
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ func (m *targetGroupBindingMutator) Prototype(_ admission.Request) (runtime.Obje
3939

4040
func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtime.Object) (runtime.Object, error) {
4141
tgb := obj.(*elbv2api.TargetGroupBinding)
42+
if tgb.Spec.TargetGroupARN == "" && tgb.Spec.TargetGroupName == "" {
43+
return nil, errors.Errorf("must provide either TargetGroupARN or TargetGroupName")
44+
}
45+
if err := m.getArnFromNameIfNeeded(ctx, tgb); err != nil {
46+
return nil, err
47+
}
4248
if err := m.defaultingTargetType(ctx, tgb); err != nil {
4349
return nil, err
4450
}
@@ -51,6 +57,17 @@ func (m *targetGroupBindingMutator) MutateCreate(ctx context.Context, obj runtim
5157
return tgb, nil
5258
}
5359

60+
func (m *targetGroupBindingMutator) getArnFromNameIfNeeded(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error {
61+
if tgb.Spec.TargetGroupARN == "" && tgb.Spec.TargetGroupName != "" {
62+
tgObj, err := m.getTargetGroupsByNameFromAWS(ctx, tgb.Spec.TargetGroupName)
63+
if err != nil {
64+
return err
65+
}
66+
tgb.Spec.TargetGroupARN = *tgObj.TargetGroupArn
67+
}
68+
return nil
69+
}
70+
5471
func (m *targetGroupBindingMutator) MutateUpdate(ctx context.Context, obj runtime.Object, oldObj runtime.Object) (runtime.Object, error) {
5572
return obj, nil
5673
}
@@ -142,6 +159,20 @@ func (m *targetGroupBindingMutator) getTargetGroupFromAWS(ctx context.Context, t
142159
return &tgList[0], nil
143160
}
144161

162+
func (m *targetGroupBindingMutator) getTargetGroupsByNameFromAWS(ctx context.Context, tgName string) (*elbv2types.TargetGroup, error) {
163+
req := &elbv2sdk.DescribeTargetGroupsInput{
164+
Names: []string{tgName},
165+
}
166+
tgList, err := m.elbv2Client.DescribeTargetGroupsAsList(ctx, req)
167+
if err != nil {
168+
return nil, err
169+
}
170+
if len(tgList) != 1 {
171+
return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgName, len(tgList))
172+
}
173+
return &tgList[0], nil
174+
}
175+
145176
func (m *targetGroupBindingMutator) getVpcIDFromAWS(ctx context.Context, tgARN string) (string, error) {
146177
targetGroup, err := m.getTargetGroupFromAWS(ctx, tgARN)
147178
if err != nil {

webhooks/elbv2/targetgroupbinding_mutator_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,53 @@ func Test_targetGroupBindingMutator_MutateCreate(t *testing.T) {
239239
},
240240
wantErr: errors.New("unable to get target group VpcID: vpcid not found"),
241241
},
242+
{
243+
name: "targetGroupBinding with TargetGroupName instead of TargetGroupARN",
244+
fields: fields{
245+
describeTargetGroupsAsListCalls: []describeTargetGroupsAsListCall{
246+
{
247+
req: &elbv2sdk.DescribeTargetGroupsInput{
248+
Names: []string{"tg-name"},
249+
},
250+
resp: []elbv2types.TargetGroup{
251+
{
252+
TargetGroupArn: awssdk.String("tg-arn"),
253+
TargetGroupName: awssdk.String("tg-name"),
254+
TargetType: elbv2types.TargetTypeEnumInstance,
255+
},
256+
},
257+
},
258+
{
259+
req: &elbv2sdk.DescribeTargetGroupsInput{
260+
TargetGroupArns: []string{"tg-arn"},
261+
},
262+
resp: []elbv2types.TargetGroup{
263+
{
264+
TargetGroupArn: awssdk.String("tg-arn"),
265+
TargetType: elbv2types.TargetTypeEnumInstance,
266+
},
267+
},
268+
},
269+
},
270+
},
271+
args: args{
272+
obj: &elbv2api.TargetGroupBinding{
273+
Spec: elbv2api.TargetGroupBindingSpec{
274+
TargetGroupName: "tg-name",
275+
TargetType: &instanceTargetType,
276+
IPAddressType: &targetGroupIPAddressTypeIPv4,
277+
},
278+
},
279+
},
280+
want: &elbv2api.TargetGroupBinding{
281+
Spec: elbv2api.TargetGroupBindingSpec{
282+
TargetGroupARN: "tg-arn",
283+
TargetGroupName: "tg-name",
284+
TargetType: &instanceTargetType,
285+
IPAddressType: &targetGroupIPAddressTypeIPv4,
286+
},
287+
},
288+
},
242289
}
243290
for _, tt := range tests {
244291
t.Run(tt.name, func(t *testing.T) {

webhooks/elbv2/targetgroupbinding_validator.go

+41-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package elbv2
22

33
import (
44
"context"
5+
"fmt"
56
elbv2types "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types"
67
"regexp"
78
"strings"
@@ -53,7 +54,7 @@ func (v *targetGroupBindingValidator) Prototype(_ admission.Request) (runtime.Ob
5354

5455
func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj runtime.Object) error {
5556
tgb := obj.(*elbv2api.TargetGroupBinding)
56-
if err := v.checkRequiredFields(tgb); err != nil {
57+
if err := v.checkRequiredFields(ctx, tgb); err != nil {
5758
return err
5859
}
5960
if err := v.checkNodeSelector(tgb); err != nil {
@@ -74,7 +75,7 @@ func (v *targetGroupBindingValidator) ValidateCreate(ctx context.Context, obj ru
7475
func (v *targetGroupBindingValidator) ValidateUpdate(ctx context.Context, obj runtime.Object, oldObj runtime.Object) error {
7576
tgb := obj.(*elbv2api.TargetGroupBinding)
7677
oldTgb := oldObj.(*elbv2api.TargetGroupBinding)
77-
if err := v.checkRequiredFields(tgb); err != nil {
78+
if err := v.checkRequiredFields(ctx, tgb); err != nil {
7879
return err
7980
}
8081
if err := v.checkImmutableFields(tgb, oldTgb); err != nil {
@@ -91,8 +92,30 @@ func (v *targetGroupBindingValidator) ValidateDelete(ctx context.Context, obj ru
9192
}
9293

9394
// checkRequiredFields will check required fields are not absent.
94-
func (v *targetGroupBindingValidator) checkRequiredFields(tgb *elbv2api.TargetGroupBinding) error {
95+
func (v *targetGroupBindingValidator) checkRequiredFields(ctx context.Context, tgb *elbv2api.TargetGroupBinding) error {
9596
var absentRequiredFields []string
97+
if tgb.Spec.TargetGroupARN == "" {
98+
if tgb.Spec.TargetGroupName == "" {
99+
absentRequiredFields = append(absentRequiredFields, "either TargetGroupARN or TargetGroupName")
100+
} else if tgb.Spec.TargetGroupName != "" {
101+
/*
102+
The purpose of this code is to guarantee that the either the ARN of the TargetGroup exists
103+
or it's possible to infer the ARN by the name of the TargetGroup (since it's unique).
104+
105+
And even though the validator can't mutate, I added tgb.Spec.TargetGroupARN = *tgObj.TargetGroupArn
106+
to guarantee the object is in a consistent state though the rest of the process.
107+
108+
The whole code of aws-load-balancer-controller was written assuming there is an ARN.
109+
By changing the object here I guarantee as early as possible that that assumption is true.
110+
*/
111+
112+
tgObj, err := v.getTargetGroupsByNameFromAWS(ctx, tgb.Spec.TargetGroupName)
113+
if err != nil {
114+
return fmt.Errorf("searching TargetGroup with name %s: %w", tgb.Spec.TargetGroupName, err)
115+
}
116+
tgb.Spec.TargetGroupARN = *tgObj.TargetGroupArn
117+
}
118+
}
96119
if tgb.Spec.TargetType == nil {
97120
absentRequiredFields = append(absentRequiredFields, "spec.targetType")
98121
}
@@ -227,6 +250,21 @@ func (v *targetGroupBindingValidator) getVpcIDFromAWS(ctx context.Context, tgARN
227250
return awssdk.ToString(targetGroup.VpcId), nil
228251
}
229252

253+
// getTargetGroupFromAWS returns the AWS target group corresponding to the tgName
254+
func (v *targetGroupBindingValidator) getTargetGroupsByNameFromAWS(ctx context.Context, tgName string) (*elbv2types.TargetGroup, error) {
255+
req := &elbv2sdk.DescribeTargetGroupsInput{
256+
Names: []string{tgName},
257+
}
258+
tgList, err := v.elbv2Client.DescribeTargetGroupsAsList(ctx, req)
259+
if err != nil {
260+
return nil, err
261+
}
262+
if len(tgList) != 1 {
263+
return nil, errors.Errorf("expecting a single targetGroup with name [%s] but got %v", tgName, len(tgList))
264+
}
265+
return &tgList[0], nil
266+
}
267+
230268
// +kubebuilder:webhook:path=/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding,mutating=false,failurePolicy=fail,groups=elbv2.k8s.aws,resources=targetgroupbindings,verbs=create;update,versions=v1beta1,name=vtargetgroupbinding.elbv2.k8s.aws,sideEffects=None,webhookVersions=v1,admissionReviewVersions=v1beta1
231269

232270
func (v *targetGroupBindingValidator) SetupWithManager(mgr ctrl.Manager) {

0 commit comments

Comments
 (0)