Skip to content

Commit 82c9eec

Browse files
committed
Expose single annotation/label via downward API
1 parent 754017b commit 82c9eec

File tree

9 files changed

+276
-19
lines changed

9 files changed

+276
-19
lines changed

Diff for: hack/import-restrictions.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- k8s.io/apiserver/pkg/util/feature
55
- k8s.io/kubernetes/pkg/apis/core
66
- k8s.io/kubernetes/pkg/features
7+
- k8s.io/kubernetes/pkg/fieldpath
78
- k8s.io/kubernetes/pkg/util
89
- k8s.io/api/core/v1
910

Diff for: pkg/apis/core/v1/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ go_library(
1616
"//pkg/apis/core:go_default_library",
1717
"//pkg/apis/extensions:go_default_library",
1818
"//pkg/features:go_default_library",
19+
"//pkg/fieldpath:go_default_library",
1920
"//pkg/util/parsers:go_default_library",
2021
"//pkg/util/pointer:go_default_library",
2122
"//vendor/k8s.io/api/core/v1:go_default_library",

Diff for: pkg/apis/core/v1/conversion.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"k8s.io/apimachinery/pkg/util/validation/field"
2929
"k8s.io/kubernetes/pkg/apis/core"
3030
"k8s.io/kubernetes/pkg/apis/extensions"
31+
"k8s.io/kubernetes/pkg/fieldpath"
3132
)
3233

3334
// This is a "fast-path" that avoids reflection for common types. It focuses on the objects that are
@@ -156,7 +157,8 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
156157
// Add field conversion funcs.
157158
err = scheme.AddFieldLabelConversionFunc("v1", "Pod",
158159
func(label, value string) (string, string, error) {
159-
switch label {
160+
path, _ := fieldpath.SplitMaybeSubscriptedPath(label)
161+
switch path {
160162
case "metadata.annotations",
161163
"metadata.labels",
162164
"metadata.name",
@@ -170,7 +172,7 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
170172
"status.hostIP",
171173
"status.podIP":
172174
return label, value, nil
173-
// This is for backwards compatibility with old v1 clients which send spec.host
175+
// This is for backwards compatibility with old v1 clients which send spec.host
174176
case "spec.host":
175177
return "spec.nodeName", value, nil
176178
default:

Diff for: pkg/apis/core/validation/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ go_library(
2222
"//pkg/apis/core/v1/helper:go_default_library",
2323
"//pkg/capabilities:go_default_library",
2424
"//pkg/features:go_default_library",
25+
"//pkg/fieldpath:go_default_library",
2526
"//pkg/security/apparmor:go_default_library",
2627
"//vendor/github.com/golang/glog:go_default_library",
2728
"//vendor/k8s.io/api/core/v1:go_default_library",

Diff for: pkg/apis/core/validation/validation.go

+41-11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import (
5050
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
5151
"k8s.io/kubernetes/pkg/capabilities"
5252
"k8s.io/kubernetes/pkg/features"
53+
"k8s.io/kubernetes/pkg/fieldpath"
5354
"k8s.io/kubernetes/pkg/security/apparmor"
5455
)
5556

@@ -960,11 +961,13 @@ func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *fie
960961
return allErrs
961962
}
962963

963-
var validDownwardAPIFieldPathExpressions = sets.NewString(
964+
var validVolumeDownwardAPIFieldPathExpressions = sets.NewString(
964965
"metadata.name",
965966
"metadata.namespace",
966967
"metadata.labels",
968+
"metadata.labels[]", // represents "metadata.labels" with an arbitary subscript
967969
"metadata.annotations",
970+
"metadata.annotations[]", // represents "metadata.annotations" with an arbitary subscript
968971
"metadata.uid")
969972

970973
func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *field.Path) field.ErrorList {
@@ -975,7 +978,7 @@ func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *fi
975978
}
976979
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
977980
if file.FieldRef != nil {
978-
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
981+
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
979982
if file.ResourceFieldRef != nil {
980983
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
981984
}
@@ -1898,7 +1901,16 @@ func ValidateEnv(vars []core.EnvVar, fldPath *field.Path) field.ErrorList {
18981901
return allErrs
18991902
}
19001903

1901-
var validFieldPathExpressionsEnv = sets.NewString("metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP")
1904+
var validEnvDownwardAPIFieldPathExpressions = sets.NewString(
1905+
"metadata.annotations[]", // represents "metadata.annotations" with an arbitary subscript
1906+
"metadata.labels[]", // represents "metadata.labels" with an arbitary subscript
1907+
"metadata.name",
1908+
"metadata.namespace",
1909+
"metadata.uid",
1910+
"spec.nodeName",
1911+
"spec.serviceAccountName",
1912+
"status.hostIP",
1913+
"status.podIP")
19021914
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")
19031915

19041916
func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorList {
@@ -1912,7 +1924,7 @@ func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorLis
19121924

19131925
if ev.ValueFrom.FieldRef != nil {
19141926
numSources++
1915-
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validFieldPathExpressionsEnv, fldPath.Child("fieldRef"))...)
1927+
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
19161928
}
19171929
if ev.ValueFrom.ResourceFieldRef != nil {
19181930
numSources++
@@ -1945,14 +1957,32 @@ func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets
19451957

19461958
if len(fs.APIVersion) == 0 {
19471959
allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), ""))
1948-
} else if len(fs.FieldPath) == 0 {
1960+
return allErrs
1961+
}
1962+
if len(fs.FieldPath) == 0 {
19491963
allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), ""))
1950-
} else {
1951-
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
1952-
if err != nil {
1953-
allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err)))
1954-
} else if !expressions.Has(internalFieldPath) {
1955-
allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), internalFieldPath, expressions.List()))
1964+
return allErrs
1965+
}
1966+
1967+
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
1968+
if err != nil {
1969+
allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err)))
1970+
return allErrs
1971+
}
1972+
1973+
path, subscript := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath)
1974+
if len(subscript) > 0 {
1975+
// This is to indicate that the internalFieldPath has a subscript, so
1976+
// that we can compare the path against the allowed path set easily.
1977+
path += "[]"
1978+
}
1979+
if !expressions.Has(path) {
1980+
allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), path, expressions.List()))
1981+
return allErrs
1982+
}
1983+
if len(subscript) > 0 {
1984+
for _, msg := range validation.IsQualifiedName(subscript) {
1985+
allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg))
19561986
}
19571987
}
19581988

Diff for: pkg/apis/core/validation/validation_test.go

+50-5
Original file line numberDiff line numberDiff line change
@@ -2628,13 +2628,27 @@ func TestValidateVolumes(t *testing.T) {
26282628
FieldPath: "metadata.labels",
26292629
},
26302630
},
2631+
{
2632+
Path: "labels with subscript",
2633+
FieldRef: &core.ObjectFieldSelector{
2634+
APIVersion: "v1",
2635+
FieldPath: "metadata.labels['key']",
2636+
},
2637+
},
26312638
{
26322639
Path: "annotations",
26332640
FieldRef: &core.ObjectFieldSelector{
26342641
APIVersion: "v1",
26352642
FieldPath: "metadata.annotations",
26362643
},
26372644
},
2645+
{
2646+
Path: "annotations with subscript",
2647+
FieldRef: &core.ObjectFieldSelector{
2648+
APIVersion: "v1",
2649+
FieldPath: "metadata.annotations['key']",
2650+
},
2651+
},
26382652
{
26392653
Path: "namespace",
26402654
FieldRef: &core.ObjectFieldSelector{
@@ -3824,6 +3838,24 @@ func TestValidateEnv(t *testing.T) {
38243838
{Name: "abc", Value: ""},
38253839
{Name: "a.b.c", Value: "value"},
38263840
{Name: "a-b-c", Value: "value"},
3841+
{
3842+
Name: "abc",
3843+
ValueFrom: &core.EnvVarSource{
3844+
FieldRef: &core.ObjectFieldSelector{
3845+
APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(),
3846+
FieldPath: "metadata.annotations['key']",
3847+
},
3848+
},
3849+
},
3850+
{
3851+
Name: "abc",
3852+
ValueFrom: &core.EnvVarSource{
3853+
FieldRef: &core.ObjectFieldSelector{
3854+
APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(),
3855+
FieldPath: "metadata.labels['key']",
3856+
},
3857+
},
3858+
},
38273859
{
38283860
Name: "abc",
38293861
ValueFrom: &core.EnvVarSource{
@@ -4095,7 +4127,20 @@ func TestValidateEnv(t *testing.T) {
40954127
expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
40964128
},
40974129
{
4098-
name: "invalid fieldPath labels",
4130+
name: "metadata.name with subscript",
4131+
envs: []core.EnvVar{{
4132+
Name: "labels",
4133+
ValueFrom: &core.EnvVarSource{
4134+
FieldRef: &core.ObjectFieldSelector{
4135+
FieldPath: "metadata.name['key']",
4136+
APIVersion: "v1",
4137+
},
4138+
},
4139+
}},
4140+
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.name[]": supported values: "metadata.annotations[]", "metadata.labels[]", "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
4141+
},
4142+
{
4143+
name: "metadata.labels without subscript",
40994144
envs: []core.EnvVar{{
41004145
Name: "labels",
41014146
ValueFrom: &core.EnvVarSource{
@@ -4105,10 +4150,10 @@ func TestValidateEnv(t *testing.T) {
41054150
},
41064151
},
41074152
}},
4108-
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
4153+
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.labels": supported values: "metadata.annotations[]", "metadata.labels[]", "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
41094154
},
41104155
{
4111-
name: "invalid fieldPath annotations",
4156+
name: "metadata.annotations without subscript",
41124157
envs: []core.EnvVar{{
41134158
Name: "abc",
41144159
ValueFrom: &core.EnvVarSource{
@@ -4118,7 +4163,7 @@ func TestValidateEnv(t *testing.T) {
41184163
},
41194164
},
41204165
}},
4121-
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
4166+
expectedError: `[0].valueFrom.fieldRef.fieldPath: Unsupported value: "metadata.annotations": supported values: "metadata.annotations[]", "metadata.labels[]", "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
41224167
},
41234168
{
41244169
name: "unsupported fieldPath",
@@ -4131,7 +4176,7 @@ func TestValidateEnv(t *testing.T) {
41314176
},
41324177
},
41334178
}},
4134-
expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
4179+
expectedError: `valueFrom.fieldRef.fieldPath: Unsupported value: "status.phase": supported values: "metadata.annotations[]", "metadata.labels[]", "metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP"`,
41354180
},
41364181
}
41374182
for _, tc := range errorCases {

Diff for: pkg/fieldpath/fieldpath.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,20 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
4242
return "", nil
4343
}
4444

45-
switch fieldPath {
45+
path, subscript := SplitMaybeSubscriptedPath(fieldPath)
46+
47+
if len(subscript) > 0 {
48+
switch path {
49+
case "metadata.annotations":
50+
return accessor.GetAnnotations()[subscript], nil
51+
case "metadata.labels":
52+
return accessor.GetLabels()[subscript], nil
53+
default:
54+
return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath)
55+
}
56+
}
57+
58+
switch path {
4659
case "metadata.annotations":
4760
return FormatMap(accessor.GetAnnotations()), nil
4861
case "metadata.labels":
@@ -57,3 +70,29 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
5770

5871
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
5972
}
73+
74+
// SplitMaybeSubscriptedPath checks whether the specified fieldPath is
75+
// subscripted, and
76+
// - if yes, this function splits the fieldPath into path and subscript, and
77+
// returns (path, subscript).
78+
// - if no, this function returns (fieldPath, "").
79+
//
80+
// Example inputs and outputs:
81+
// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey")
82+
// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c")
83+
// - "metadata.labels" --> ("metadata.labels", "")
84+
// - "metadata.labels['']" --> ("metadata.labels", "")
85+
func SplitMaybeSubscriptedPath(fieldPath string) (string, string) {
86+
if !strings.HasSuffix(fieldPath, "']") {
87+
return fieldPath, ""
88+
}
89+
s := strings.TrimSuffix(fieldPath, "']")
90+
parts := strings.SplitN(s, "['", 2)
91+
if len(parts) < 2 {
92+
return fieldPath, ""
93+
}
94+
if len(parts[0]) == 0 || len(parts[1]) == 0 {
95+
return fieldPath, ""
96+
}
97+
return parts[0], parts[1]
98+
}

0 commit comments

Comments
 (0)