Skip to content

Commit 9499551

Browse files
authored
Create tags with templated strings (#48)
1 parent 25bd54a commit 9499551

File tree

3 files changed

+146
-3
lines changed

3 files changed

+146
-3
lines changed

README.md

+30-1
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,40 @@ The `k8s-aws-ebs-tagger` watches for new PersistentVolumeClaims and when new AWS
3434

3535
#### ignored tags
3636

37-
The following tags are ignored
37+
The following tags are ignored by default
3838
- `kubernetes.io/*`
3939
- `KubernetesCluster`
4040
- `Name`
4141

42+
#### Tag Templates
43+
44+
Tag values can be Go templates using values from the PVC's `Name`, `Namespace`, `Annotations`, and `Labels`.
45+
46+
Some examples could be:
47+
48+
```yaml
49+
apiVersion: v1
50+
kind: PersistentVolumeClaim
51+
metadata:
52+
name: touge-test
53+
namespace: touge
54+
labels:
55+
TeamID: "Frontend"
56+
annotations:
57+
CostCenter: "1234"
58+
aws-ebs-tagger/tags: |
59+
{"Owner": "{{ .Labels.TeamID }}-{{ .Annotations.CostCenter }}"}
60+
---
61+
apiVersion: v1
62+
kind: PersistentVolumeClaim
63+
metadata:
64+
name: app-1
65+
namespace: my-app
66+
annotations:
67+
aws-ebs-tagger/tags: |
68+
{"OwnerID": "{{ .Namespace }}/{{ .Name }}"}
69+
```
70+
4271
### Installation
4372
4473
#### AWS IAM Role

kubernetes.go

+36-2
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
package main
2020

2121
import (
22+
"bytes"
2223
"context"
2324
"encoding/json"
2425
"errors"
26+
"html/template"
2527
"io/ioutil"
2628
"os"
2729
"path/filepath"
@@ -50,6 +52,13 @@ const (
5052
regexpAWSVolumeID = `^aws:\/\/\w{2}-\w{4,9}-\d\w\/(vol-\w+)$`
5153
)
5254

55+
type TagTemplate struct {
56+
Name string
57+
Namespace string
58+
Labels map[string]string
59+
Annotations map[string]string
60+
}
61+
5362
func BuildClient(kubeconfig string, kubeContext string) (*kubernetes.Clientset, error) {
5463
config, err := rest.InClusterConfig()
5564
if err != nil {
@@ -171,7 +180,7 @@ func buildTags(pvc *corev1.PersistentVolumeClaim) map[string]string {
171180
if _, ok := annotations[annotationPrefix+"/ignore"]; ok {
172181
log.Debugln(annotationPrefix + "/ignore annotation is set")
173182
promIgnoredTotal.Inc()
174-
return tags
183+
return renderTagTemplates(pvc, tags)
175184
}
176185

177186
// Set the default tags
@@ -191,7 +200,7 @@ func buildTags(pvc *corev1.PersistentVolumeClaim) map[string]string {
191200
tagString, ok := annotations[annotationPrefix+"/tags"]
192201
if !ok {
193202
log.Debugln("Does not have " + annotationPrefix + "/tags annotation")
194-
return tags
203+
return renderTagTemplates(pvc, tags)
195204
}
196205
if tagFormat == "csv" {
197206
customTags = parseCsv(tagString)
@@ -215,6 +224,31 @@ func buildTags(pvc *corev1.PersistentVolumeClaim) map[string]string {
215224
tags[k] = v
216225
}
217226

227+
return renderTagTemplates(pvc, tags)
228+
}
229+
230+
func renderTagTemplates(pvc *corev1.PersistentVolumeClaim, tags map[string]string) map[string]string {
231+
232+
tplData := TagTemplate{
233+
Name: pvc.GetName(),
234+
Namespace: pvc.GetNamespace(),
235+
Labels: pvc.GetLabels(),
236+
Annotations: pvc.GetAnnotations(),
237+
}
238+
239+
for k, v := range tags {
240+
tmpl, err := template.New("tag").Parse(v)
241+
if err != nil {
242+
continue
243+
}
244+
buf := new(bytes.Buffer)
245+
err = tmpl.Execute(buf, tplData)
246+
if err != nil {
247+
continue
248+
}
249+
tags[k] = buf.String()
250+
}
251+
218252
return tags
219253
}
220254

kubernetes_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ func Test_buildTags(t *testing.T) {
315315
t.Errorf("buildTags() = %v, want %v", got, tt.want)
316316
}
317317
tagFormat = "json"
318+
defaultTags = map[string]string{}
318319
})
319320
}
320321
}
@@ -323,6 +324,7 @@ func Test_annotationPrefix(t *testing.T) {
323324

324325
pvc := &corev1.PersistentVolumeClaim{}
325326
pvc.SetName("my-pvc")
327+
defaultAnnotationPrefix := annotationPrefix
326328

327329
tests := []struct {
328330
name string
@@ -368,6 +370,8 @@ func Test_annotationPrefix(t *testing.T) {
368370
if got := buildTags(pvc); !reflect.DeepEqual(got, tt.want) {
369371
t.Errorf("buildTags() = %v, want %v", got, tt.want)
370372
}
373+
annotationPrefix = defaultAnnotationPrefix
374+
defaultTags = map[string]string{}
371375
})
372376
}
373377
}
@@ -484,3 +488,79 @@ func Test_processPersistentVolumeClaim(t *testing.T) {
484488
}
485489

486490
}
491+
492+
func Test_templatedTags(t *testing.T) {
493+
494+
pvc := &corev1.PersistentVolumeClaim{}
495+
pvc.SetName("my-pvc")
496+
pvc.SetNamespace("my-namespace")
497+
498+
tests := []struct {
499+
name string
500+
defaultTags map[string]string
501+
annotations map[string]string
502+
labels map[string]string
503+
want map[string]string
504+
}{
505+
{
506+
name: "default tag with template",
507+
defaultTags: map[string]string{"foo": "{{ .Name }}-{{ .Namespace }}"},
508+
annotations: map[string]string{},
509+
labels: map[string]string{},
510+
want: map[string]string{"foo": "my-pvc-my-namespace"},
511+
},
512+
{
513+
name: "default tag overwritten with tag template",
514+
defaultTags: map[string]string{"foo": "bar"},
515+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Name }}-{{ .Namespace }}\"}"},
516+
labels: map[string]string{},
517+
want: map[string]string{"foo": "my-pvc-my-namespace"},
518+
},
519+
{
520+
name: "template using annotation",
521+
defaultTags: map[string]string{},
522+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Name }}-{{ .Annotations.TeamID }}\"}", "TeamID": "1234"},
523+
labels: map[string]string{},
524+
want: map[string]string{"foo": "my-pvc-1234"},
525+
},
526+
{
527+
name: "template using label",
528+
defaultTags: map[string]string{},
529+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Name }}-{{ .Labels.TeamID }}\"}"},
530+
labels: map[string]string{"TeamID": "1234"},
531+
want: map[string]string{"foo": "my-pvc-1234"},
532+
},
533+
{
534+
name: "template using label and annotation",
535+
defaultTags: map[string]string{},
536+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Name }}-{{ .Labels.TeamID }}\",\"bar\": \"{{ .Name }}-{{ .Annotations.DeptID }}\"}", "DeptID": "ABC"},
537+
labels: map[string]string{"TeamID": "1234"},
538+
want: map[string]string{"foo": "my-pvc-1234", "bar": "my-pvc-ABC"},
539+
},
540+
{
541+
name: "template using invalid label",
542+
defaultTags: map[string]string{},
543+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Name }}-{{ .Labels.SomeLabel }}\"}"},
544+
labels: map[string]string{"TeamID": "1234"},
545+
want: map[string]string{"foo": "my-pvc-"},
546+
},
547+
{
548+
name: "template using invalid field",
549+
defaultTags: map[string]string{},
550+
annotations: map[string]string{annotationPrefix + "/tags": "{\"foo\": \"{{ .Blah }}-{{ .Labels.TeamID }}\"}"},
551+
labels: map[string]string{"TeamID": "1234"},
552+
want: map[string]string{"foo": "{{ .Blah }}-{{ .Labels.TeamID }}"},
553+
},
554+
}
555+
for _, tt := range tests {
556+
t.Run(tt.name, func(t *testing.T) {
557+
pvc.SetAnnotations(tt.annotations)
558+
pvc.SetLabels(tt.labels)
559+
defaultTags = tt.defaultTags
560+
if got := buildTags(pvc); !reflect.DeepEqual(got, tt.want) {
561+
t.Errorf("buildTags() = %v, want %v", got, tt.want)
562+
}
563+
defaultTags = map[string]string{}
564+
})
565+
}
566+
}

0 commit comments

Comments
 (0)