Skip to content

Commit f0568d7

Browse files
committed
fix: allow prefixes in tags and taints with noprefix (#50)
Signed-off-by: Patrik Cyvoct <[email protected]>
1 parent a7f5637 commit f0568d7

File tree

3 files changed

+68
-18
lines changed

3 files changed

+68
-18
lines changed

docs/tags.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,29 @@ Once the tag is removed from the instance, it will also be removed as a label on
1313

1414
### Non prefixed labels
1515

16-
It is possible to add labels nop prefixed with `k8s.scaleway.com`. The downside, is that when you will delete the associated tag, the label won't get removed.
16+
It is possible to add labels not prefixed with `k8s.scaleway.com`. The downside, is that when you will delete the associated tag, the label won't get removed.
1717
In order to have non prefixed labels, you should prefix the tag with `noprefix=`.
1818

1919
For intance the tag `noprefix=foo=bar` will yield the `foo=bar` label on the Kubernetes nodes.
2020

21+
This is the only way to add custom prefixed labels like `node.kubernetes.io`.
22+
2123
## Taints
2224

2325
In order for a tag to be synced to a taint, it needs to be of the form `taint=foo=bar:Effect`, where `Effect` is one of `NoExecute`, `NoSchedule` or `PreferNoSchedule`.
2426
In this case, the Kubernetes nodes will have the taint `k8s.scaleway.com/foo=bar` with the effect `Effect`.
2527

2628
Once the tag is removed from the instance, it will also be removed as a taint on the node.
2729

30+
### Non prefixed Tains
31+
32+
It is possible to add taints not prefixed with `k8s.scaleway.com`. The downside, is that when you will delete the associated tag, the taint won't get removed.
33+
In order to have non prefixed taints, you should prefix the taint with `taint=noprefix=`.
34+
35+
For intance the tag `taint=noprefix=foo=bar:Effect` will yield the `foo=bar` taint on the Kubernetes nodes with the `Effect` effect.
36+
37+
This is the only way to add custom prefixed taints like `node.kubernetes.io`.
38+
2839
## Special Kubernetes Labels
2940

3041
- `node.kubernetes.io/exclude-from-external-load-balancers` can be set on the Kubernetes nodes if this same value is set as a tag on the instance. It will have the value `managed-by-scaleway-ccm` and will be deleted if deleted from the tags.

scaleway/sync.go

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
v1 "k8s.io/api/core/v1"
2929
"k8s.io/apimachinery/pkg/fields"
3030
"k8s.io/apimachinery/pkg/util/runtime"
31+
"k8s.io/apimachinery/pkg/util/validation"
3132
"k8s.io/apimachinery/pkg/util/wait"
3233
clientset "k8s.io/client-go/kubernetes"
3334
"k8s.io/client-go/tools/cache"
@@ -53,13 +54,20 @@ const (
5354
exponentialMaxRetries = 30
5455
)
5556

57+
// from k8s.io/apimachinery/pkg/util/validation/validation.go
58+
const qnameCharFmt string = "[A-Za-z0-9]"
59+
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
60+
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
61+
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
62+
const qualifiedNameMaxLength int = 63
63+
5664
var (
57-
splitRegexp = regexp.MustCompile(`[:= ]+`)
58-
labelsRegexp = regexp.MustCompile(`^(([A-Za-z0-9][-A-Za-z0-9_\\.]*)?[A-Za-z0-9])?$`)
59-
labelsPrefix = "k8s.scaleway.com/"
60-
taintsPrefix = "k8s.scaleway.com/"
61-
labelTaintPrefix = "taint="
62-
labelNoPrefix = "noprefix="
65+
splitRegexp = regexp.MustCompile(`[:= ]+`)
66+
qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
67+
labelsPrefix = "k8s.scaleway.com/"
68+
taintsPrefix = "k8s.scaleway.com/"
69+
labelTaintPrefix = "taint="
70+
labelNoPrefix = "noprefix="
6371

6472
// K8S labels
6573
labelNodeRoleExcludeBalancer = "node.kubernetes.io/exclude-from-external-load-balancers"
@@ -335,22 +343,50 @@ func (s *syncController) SyncLBTags() {
335343

336344
func tagLabelParser(tag string) (key string, value string) {
337345
prefix := labelsPrefix
346+
tagValue := tag
338347

339348
if strings.HasPrefix(tag, labelNoPrefix) {
340349
prefix = ""
341-
tag = strings.TrimPrefix(tag, labelNoPrefix)
350+
tagValue = strings.TrimPrefix(tag, labelNoPrefix)
351+
tagSplit := strings.Split(tagValue, "/")
352+
353+
if len(tagSplit) > 2 {
354+
klog.Errorf("tag %s is not valid: %s contains more than 1 '/'", tag, tagValue)
355+
return "", ""
356+
}
357+
if len(tagSplit) == 2 {
358+
if tagSplit[0] == "" {
359+
klog.Errorf("tag %s is not valid: prefix is empty")
360+
return "", ""
361+
}
362+
if errs := validation.IsDNS1123Subdomain(tagSplit[0]); len(errs) != 0 {
363+
klog.Errorf("tag %s is not valid: prefix is not composed of DNS labels: %s", tag, strings.Join(errs, ","))
364+
return "", ""
365+
}
366+
prefix = tagSplit[0] + "/"
367+
tagValue = tagSplit[1]
368+
}
342369
}
343370

344-
split := splitRegexp.Split(tag, -1)
371+
split := splitRegexp.Split(tagValue, -1)
345372

346-
key = prefix + labelsRegexp.FindString(split[0])
347-
if key == prefix || len(key) > 63 {
373+
if len(split[0]) == 0 {
374+
klog.Errorf("tag %s have an empty key", tag)
348375
return "", ""
349376
}
350-
377+
if len(split[0]) > qualifiedNameMaxLength {
378+
klog.Errorf("tag %s have a key too long, got %d instead of max %d", tag, len(split[0]), qualifiedNameMaxLength)
379+
return "", ""
380+
}
381+
if !qualifiedNameRegexp.MatchString(split[0]) {
382+
klog.Errorf("tag %s key must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character", tag)
383+
return "", ""
384+
}
385+
key = prefix + split[0]
351386
if len(split) > 1 {
352-
value = labelsRegexp.FindString(split[1])
353-
if value == "" || len(value) > 63 {
387+
value = split[1]
388+
if errs := validation.IsValidLabelValue(value); len(errs) != 0 {
389+
klog.Errorf("tag %s value must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character", tag)
354390
return "", ""
355391
}
356392
}

scaleway/sync_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,12 @@ func TestTagTaintParser(t *testing.T) {
3535
tagsTest := map[string][3]string{
3636
"taint=": {"", "", ""},
3737
"taint=word=word:NoExecute": {"k8s.scaleway.com/word", "word", "NoExecute"},
38-
"taint=underscore_word=underscore_word:NoSchedule": {"k8s.scaleway.com/underscore_word", "underscore_word", "NoSchedule"},
39-
"taint=dash-word=dash-word:PreferNoSchedule": {"k8s.scaleway.com/dash-word", "dash-word", "PreferNoSchedule"},
40-
"taint=dash-word=dash-word:foo": {"", "", ""},
41-
"taint=dash-word=dash-word": {"", "", ""},
38+
"taint=underscore_word=underscore_word:NoSchedule": {"k8s.scaleway.com/underscore_word", "underscore_word", "NoSchedule"},
39+
"taint=dash-word=dash-word:PreferNoSchedule": {"k8s.scaleway.com/dash-word", "dash-word", "PreferNoSchedule"},
40+
"taint=dash-word=dash-word:foo": {"", "", ""},
41+
"taint=dash-word=dash-word": {"", "", ""},
42+
"taint=noprefix=dash-word=dash-word:PreferNoSchedule": {"dash-word", "dash-word", "PreferNoSchedule"},
43+
"taint=noprefix=node.k8s.io/dash-word=dash-word:PreferNoSchedule": {"node.k8s.io/dash-word", "dash-word", "PreferNoSchedule"},
4244
}
4345
for tag, expected := range tagsTest {
4446
t.Run(tag, func(t *testing.T) {
@@ -59,6 +61,7 @@ func TestTagLabelParser(t *testing.T) {
5961
"dash-word": {"k8s.scaleway.com/dash-word", ""},
6062
"equal1=value": {"k8s.scaleway.com/equal1", "value"},
6163
"noprefix=equal1=value": {"equal1", "value"},
64+
"noprefix=node.kubernetes.io/role=myrole": {"node.kubernetes.io/role", "myrole"},
6265
"equal2 = value": {"k8s.scaleway.com/equal2", "value"},
6366
"equal3 =value": {"k8s.scaleway.com/equal3", "value"},
6467
"equal4= value": {"k8s.scaleway.com/equal4", "value"},

0 commit comments

Comments
 (0)