Skip to content

Commit e9bd1f3

Browse files
committed
feat: Add default tags CLI option [K8SPCORE-1707]
1 parent a8120f7 commit e9bd1f3

File tree

8 files changed

+147
-4
lines changed

8 files changed

+147
-4
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,9 @@ spec:
250250
podName: some-pod
251251
```
252252

253+
##### Default tags
254+
Tags can be defined from CLI as default tags, what will be applied to EIP and ENI resources.
255+
253256
### ENIs
254257

255258
To be documented

api/v1alpha1/eni_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ type ENISpec struct {
3737
Attachment *ENIAttachment `json:"attachment,omitempty"`
3838

3939
Description string `json:"description,omitempty"`
40+
41+
// Tags that will be applied to the created EIP.
42+
// +optional
43+
Tags *map[string]string `json:"tags,omitempty"`
4044
}
4145

4246
// ENIStatus defines the observed state of ENI

config/samples/aws_v1alpha1_eip.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ kind: EIP
33
metadata:
44
name: eip-sample
55
spec:
6-
tags:
6+
tags: # Optional tags to apply to the EIP
77
owner: Myself
88
assignment:
99
eni: eni-sample

config/samples/aws_v1alpha1_eni.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ spec:
88
secondaryPrivateIPAddressCount: 2
99
securityGroups:
1010
- sg-abcdef12
11+
tags: # Optional tags to apply to the ENI
12+
team: VCS

controllers/eip_controller.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type EIPReconciler struct {
4040
NonCachingClient client.Client
4141
Log logr.Logger
4242
EC2 *ec2.EC2
43+
Tags map[string]string
4344
}
4445

4546
// +kubebuilder:rbac:groups=aws.k8s.logmein.com,resources=eips,verbs=get;list;watch;create;update;patch;delete
@@ -190,6 +191,12 @@ func (r *EIPReconciler) allocateEIP(ctx context.Context, eip *awsv1alpha1.EIP, l
190191
}
191192
}
192193

194+
tags := ec2.TagSpecification{
195+
ResourceType: aws.String("elastic-ip"),
196+
Tags: r.combineDefaultAndDefinedTags(eip),
197+
}
198+
input.TagSpecifications = []*ec2.TagSpecification{&tags}
199+
193200
if resp, err := r.EC2.AllocateAddressWithContext(ctx, input); err != nil {
194201
return err
195202
} else {
@@ -202,7 +209,17 @@ func (r *EIPReconciler) allocateEIP(ctx context.Context, eip *awsv1alpha1.EIP, l
202209
}
203210
}
204211

205-
return r.reconcileTags(ctx, eip, []*ec2.Tag{})
212+
return nil
213+
}
214+
215+
// combineDefaultAndDefinedTags combines the default tags defined in the controller
216+
// with the tags defined in the EIP spec. Tags defined in the EIP spec override
217+
// default tags in case of key conflicts.
218+
func (r EIPReconciler) combineDefaultAndDefinedTags(eip *awsv1alpha1.EIP) []*ec2.Tag {
219+
var tags []*ec2.Tag
220+
tags = convertMapToTags(r.Tags)
221+
tags = append(tags, convertMapToTags(*eip.Spec.Tags)...)
222+
return tags
206223
}
207224

208225
func (r *EIPReconciler) reconcileTags(ctx context.Context, eip *awsv1alpha1.EIP, existingTags []*ec2.Tag) error {
@@ -225,6 +242,7 @@ func (r *EIPReconciler) reconcileTags(ctx context.Context, eip *awsv1alpha1.EIP,
225242
tagsToCreate = append(tagsToCreate, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
226243
}
227244
}
245+
228246
if len(tagsToCreate) > 0 {
229247
if _, err := r.EC2.CreateTagsWithContext(ctx, &ec2.CreateTagsInput{
230248
Resources: resources,
@@ -234,12 +252,14 @@ func (r *EIPReconciler) reconcileTags(ctx context.Context, eip *awsv1alpha1.EIP,
234252
}
235253
}
236254

255+
// remove tags that are not defined in the spec and are not default ones
237256
var tagsToRemove []*ec2.Tag
238257
for _, tag := range existingTags {
239-
if _, ok := (*eip.Spec.Tags)[aws.StringValue(tag.Key)]; !ok {
258+
if !isTagPresent(r.combineDefaultAndDefinedTags(eip), tag) {
240259
tagsToRemove = append(tagsToRemove, tag)
241260
}
242261
}
262+
243263
if len(tagsToRemove) > 0 {
244264
_, err := r.EC2.DeleteTagsWithContext(ctx, &ec2.DeleteTagsInput{
245265
Resources: resources,

controllers/eni_controller.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type ENIReconciler struct {
4141
NonCachingClient client.Client
4242
Log logr.Logger
4343
EC2 *ec2.EC2
44+
Tags map[string]string
4445
}
4546

4647
// +kubebuilder:rbac:groups=aws.k8s.logmein.com,resources=enis,verbs=get;list;watch;create;update;patch;delete
@@ -74,6 +75,13 @@ func (r *ENIReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
7475
if eni.Spec.SecondaryPrivateIPAddressCount > 0 {
7576
input.SecondaryPrivateIpAddressCount = aws.Int64(eni.Spec.SecondaryPrivateIPAddressCount)
7677
}
78+
79+
tags := ec2.TagSpecification{
80+
ResourceType: aws.String("network-interface"),
81+
}
82+
tags.Tags = convertMapToTags(r.Tags)
83+
input.TagSpecifications = []*ec2.TagSpecification{&tags}
84+
7785
resp, err := r.EC2.CreateNetworkInterface(input)
7886
if err != nil {
7987
return ctrl.Result{}, err
@@ -200,6 +208,12 @@ func (r *ENIReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R
200208
return ctrl.Result{RequeueAfter: 3 * time.Second}, err
201209
}
202210
}
211+
212+
// reconcile tags
213+
if err := r.reconcileTags(ctx, &eni, eniInfo.TagSet); err != nil {
214+
return ctrl.Result{}, err
215+
}
216+
203217
eni.Status.Attachment = eni.Spec.Attachment
204218
return ctrl.Result{}, r.Update(ctx, &eni)
205219
} else if containsString(eni.ObjectMeta.Finalizers, finalizerName) {
@@ -356,3 +370,59 @@ func (r *ENIReconciler) SetupWithManager(mgr ctrl.Manager) error {
356370
For(&awsv1alpha1.ENI{}).
357371
Complete(r)
358372
}
373+
374+
// combineDefaultAndDefinedTags combines the default tags defined in the controller
375+
// with the tags defined in the ENI spec. Tags defined in the ENI spec override
376+
// default tags in case of key conflicts.
377+
func (r ENIReconciler) combineDefaultAndDefinedTags(eni *awsv1alpha1.ENI) []*ec2.Tag {
378+
var tags []*ec2.Tag
379+
tags = convertMapToTags(r.Tags)
380+
tags = append(tags, convertMapToTags(*eni.Spec.Tags)...)
381+
return tags
382+
}
383+
384+
func (r *ENIReconciler) reconcileTags(ctx context.Context, eni *awsv1alpha1.ENI, existingTags []*ec2.Tag) error {
385+
resources := []*string{aws.String(eni.Status.NetworkInterfaceID)}
386+
387+
// create tags that are defined in the spec but not present yet
388+
var tagsToCreate []*ec2.Tag
389+
for k, v := range *eni.Spec.Tags {
390+
create := true
391+
for _, tag := range existingTags {
392+
if aws.StringValue(tag.Key) == k && aws.StringValue(tag.Value) == v {
393+
create = false
394+
break
395+
}
396+
}
397+
if create {
398+
tagsToCreate = append(tagsToCreate, &ec2.Tag{Key: aws.String(k), Value: aws.String(v)})
399+
}
400+
}
401+
402+
if len(tagsToCreate) > 0 {
403+
if _, err := r.EC2.CreateTagsWithContext(ctx, &ec2.CreateTagsInput{
404+
Resources: resources,
405+
Tags: tagsToCreate,
406+
}); err != nil {
407+
return err
408+
}
409+
}
410+
411+
// remove tags that are not defined in the spec and are not default ones
412+
var tagsToRemove []*ec2.Tag
413+
for _, tag := range existingTags {
414+
if !isTagPresent(r.combineDefaultAndDefinedTags(eni), tag) {
415+
tagsToRemove = append(tagsToRemove, tag)
416+
}
417+
}
418+
419+
if len(tagsToRemove) > 0 {
420+
_, err := r.EC2.DeleteTagsWithContext(ctx, &ec2.DeleteTagsInput{
421+
Resources: resources,
422+
Tags: tagsToRemove,
423+
})
424+
return err
425+
}
426+
427+
return nil
428+
}

controllers/utils.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package controllers
22

3+
import (
4+
"github.com/aws/aws-sdk-go/aws"
5+
"github.com/aws/aws-sdk-go/service/ec2"
6+
)
7+
38
const (
49
finalizerName = "aws.k8s.logmein.com"
510
)
@@ -22,3 +27,23 @@ func removeString(slice []string, s string) (result []string) {
2227
}
2328
return
2429
}
30+
31+
func isTagPresent(tags []*ec2.Tag, searchedTag *ec2.Tag) bool {
32+
for _, tag := range tags {
33+
if tag.Key != nil && *tag.Key == *searchedTag.Key {
34+
return true
35+
}
36+
}
37+
return false
38+
}
39+
40+
func convertMapToTags(tagMap map[string]string) []*ec2.Tag {
41+
var tags []*ec2.Tag
42+
for k, v := range tagMap {
43+
tags = append(tags, &ec2.Tag{
44+
Key: aws.String(k),
45+
Value: aws.String(v),
46+
})
47+
}
48+
return tags
49+
}

main.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package main
1818
import (
1919
"flag"
2020
"os"
21+
"strings"
2122

2223
"github.com/aws/aws-sdk-go/aws"
2324
"github.com/aws/aws-sdk-go/aws/session"
@@ -45,11 +46,12 @@ func init() {
4546
}
4647

4748
func main() {
48-
var metricsAddr, region, leaderElectionID, leaderElectionNamespace string
49+
var metricsAddr, region, leaderElectionID, leaderElectionNamespace, defaultTags string
4950
flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
5051
flag.StringVar(&region, "region", "", "AWS region")
5152
flag.StringVar(&leaderElectionID, "leader-election-id", "k8s-aws-operator", "the name of the configmap do use as leader election lock")
5253
flag.StringVar(&leaderElectionNamespace, "leader-election-namespace", "", "the namespace in which the leader election lock will be held")
54+
flag.StringVar(&defaultTags, "default-tags", "", "default tags to add to created resources, in the format key1=value1,key2=value2")
5355
opts := zap.Options{
5456
Development: true,
5557
}
@@ -90,11 +92,18 @@ func main() {
9092
os.Exit(1)
9193
}
9294

95+
defaultTagsMap := make(map[string]string)
96+
if defaultTags != "" {
97+
parseTags(&defaultTagsMap, defaultTags)
98+
setupLog.Info("Default tags set", "tags", defaultTagsMap)
99+
}
100+
93101
err = (&controllers.EIPReconciler{
94102
Client: cachingClient,
95103
NonCachingClient: nonCachingClient,
96104
Log: ctrl.Log.WithName("controllers").WithName("EIP"),
97105
EC2: ec2,
106+
Tags: defaultTagsMap,
98107
}).SetupWithManager(mgr)
99108
if err != nil {
100109
setupLog.Error(err, "unable to create controller", "controller", "EIP")
@@ -105,6 +114,7 @@ func main() {
105114
NonCachingClient: nonCachingClient,
106115
Log: ctrl.Log.WithName("controllers").WithName("ENI"),
107116
EC2: ec2,
117+
Tags: defaultTagsMap,
108118
}).SetupWithManager(mgr)
109119
if err != nil {
110120
setupLog.Error(err, "unable to create controller", "controller", "ENI")
@@ -126,3 +136,12 @@ func main() {
126136
os.Exit(1)
127137
}
128138
}
139+
140+
func parseTags(tagMap *map[string]string, tags string) {
141+
for _, tag := range strings.Split(tags, ",") {
142+
kv := strings.SplitN(tag, "=", 2)
143+
if len(kv) == 2 {
144+
(*tagMap)[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)