From c857dc1196cae69574f2122a42475cf70a761066 Mon Sep 17 00:00:00 2001 From: Tim Hockin Date: Thu, 20 Nov 2014 14:27:11 +0800 Subject: [PATCH] Add namespacing for label keys --- pkg/api/types.go | 11 ++++++++- pkg/api/validation/validation.go | 16 +++++++++++-- pkg/api/validation/validation_test.go | 33 +++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/pkg/api/types.go b/pkg/api/types.go index 5ed942e19d2..76953e64449 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -107,12 +107,21 @@ type ObjectMeta struct { CreationTimestamp util.Time `json:"creationTimestamp,omitempty" yaml:"creationTimestamp,omitempty"` // Labels are key value pairs that may be used to scope and select individual resources. + // Label keys are of the form: + // label-key ::= prefixed-name | name + // prefixed-name ::= prefix '/' name + // prefix ::= DNS_SUBDOMAIN + // name ::= DNS_LABEL + // The prefix is optional. If the prefix is not specified, the key is assumed to be private + // to the user. Other system components that wish to use labels must specify a prefix. The + // "kubernetes.io/" prefix is reserved for use by kubernetes components. // TODO: replace map[string]string with labels.LabelSet type Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"` // Annotations are unstructured key value data stored with a resource that may be set by // external tooling. They are not queryable and should be preserved when modifying - // objects. + // objects. Annotation keys have the same formatting restrictions as Label keys. See the + // comments on Labels for details. Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"` } diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 00512de07d6..320e3cc971a 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -385,8 +385,20 @@ func ValidatePodSpec(spec *api.PodSpec) errs.ValidationErrorList { func validateLabels(labels map[string]string, field string) errs.ValidationErrorList { allErrs := errs.ValidationErrorList{} for k := range labels { - if !util.IsDNS952Label(k) { - allErrs = append(allErrs, errs.NewFieldNotSupported(field, k)) + var n, ns string + parts := strings.Split(k, "/") + switch len(parts) { + case 1: + n = parts[0] + case 2: + ns = parts[0] + n = parts[1] + default: + allErrs = append(allErrs, errs.NewFieldInvalid(field, k, "")) + continue + } + if (ns != "" && !util.IsDNSSubdomain(ns)) || !util.IsDNS952Label(n) { + allErrs = append(allErrs, errs.NewFieldInvalid(field, k, "")) } } return allErrs diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 05a500d917f..54f78a1dabd 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -35,6 +35,39 @@ func expectPrefix(t *testing.T, prefix string, errs errors.ValidationErrorList) } } +func TestValidateLabels(t *testing.T) { + successCases := []map[string]string{ + {"simple": "bar"}, + {"now-with-dashes": "bar"}, + {"simple/simple": "bar"}, + {"now-with-dashes/simple": "bar"}, + {"now-with-dashes/now-with-dashes": "bar"}, + {"now.with.dots/simple": "bar"}, + {"now-with.dashes-and.dots/simple": "bar"}, + } + for i := range successCases { + errs := validateLabels(successCases[i], "field") + if len(errs) != 0 { + t.Errorf("case[%d] expected success, got %#v", i, errs) + } + } + + errorCases := []map[string]string{ + {"123cantbeginwithnumber": "bar"}, //invalid + {"NoUppercase123": "bar"}, //invalid + {"nospecialchars^=@": "bar"}, //invalid + {"cantendwithadash-": "bar"}, //invalid + {"rfc952-mustbe24charactersorless": "bar"}, //invalid + {"only/one/slash": "bar"}, + } + for i := range errorCases { + errs := validateLabels(errorCases[i], "field") + if len(errs) != 1 { + t.Errorf("case[%d] expected failure", i) + } + } +} + func TestValidateVolumes(t *testing.T) { successCase := []api.Volume{ {Name: "abc"},