Merge pull request #55902 from yguo0905/annotations
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Expose single annotation/label via downward API **What this PR does / why we need it**: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/annotations-downward-api.md Support exposing single annotation via both env and volume downward API using the following syntax: ``` metadata.annotations['key'] metadata.labels['key'] ``` **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes # #31218 **Special notes for your reviewer**: This PR takes over the work in https://github.com/kubernetes/kubernetes/pull/41648. **Release note**: ``` A single value in metadata.annotations/metadata.labels can be passed into the containers via Downward API ``` /assign @thockin @vishh
This commit is contained in:
commit
83e46f0a9e
@ -4,6 +4,7 @@
|
|||||||
- k8s.io/apiserver/pkg/util/feature
|
- k8s.io/apiserver/pkg/util/feature
|
||||||
- k8s.io/kubernetes/pkg/apis/core
|
- k8s.io/kubernetes/pkg/apis/core
|
||||||
- k8s.io/kubernetes/pkg/features
|
- k8s.io/kubernetes/pkg/features
|
||||||
|
- k8s.io/kubernetes/pkg/fieldpath
|
||||||
- k8s.io/kubernetes/pkg/util
|
- k8s.io/kubernetes/pkg/util
|
||||||
- k8s.io/api/core/v1
|
- k8s.io/api/core/v1
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ filegroup(
|
|||||||
"//pkg/apis/core/fuzzer:all-srcs",
|
"//pkg/apis/core/fuzzer:all-srcs",
|
||||||
"//pkg/apis/core/helper:all-srcs",
|
"//pkg/apis/core/helper:all-srcs",
|
||||||
"//pkg/apis/core/install:all-srcs",
|
"//pkg/apis/core/install:all-srcs",
|
||||||
|
"//pkg/apis/core/pods:all-srcs",
|
||||||
"//pkg/apis/core/v1:all-srcs",
|
"//pkg/apis/core/v1:all-srcs",
|
||||||
"//pkg/apis/core/validation:all-srcs",
|
"//pkg/apis/core/validation:all-srcs",
|
||||||
],
|
],
|
||||||
|
30
pkg/apis/core/pods/BUILD
Normal file
30
pkg/apis/core/pods/BUILD
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["helpers.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/apis/core/pods",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//pkg/fieldpath:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["helpers_test.go"],
|
||||||
|
importpath = "k8s.io/kubernetes/pkg/apis/core/pods",
|
||||||
|
library = ":go_default_library",
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
63
pkg/apis/core/pods/helpers.go
Normal file
63
pkg/apis/core/pods/helpers.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pods
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/fieldpath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertDownwardAPIFieldLabel converts the specified downward API field label
|
||||||
|
// and its value in the pod of the specified version to the internal version,
|
||||||
|
// and returns the converted label and value. This function returns an error if
|
||||||
|
// the conversion fails.
|
||||||
|
func ConvertDownwardAPIFieldLabel(version, label, value string) (string, string, error) {
|
||||||
|
if version != "v1" {
|
||||||
|
return "", "", fmt.Errorf("unsupported pod version: %s", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, _, ok := fieldpath.SplitMaybeSubscriptedPath(label); ok {
|
||||||
|
switch path {
|
||||||
|
case "metadata.annotations", "metadata.labels":
|
||||||
|
return label, value, nil
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("field label does not support subscript: %s", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch label {
|
||||||
|
case "metadata.annotations",
|
||||||
|
"metadata.labels",
|
||||||
|
"metadata.name",
|
||||||
|
"metadata.namespace",
|
||||||
|
"metadata.uid",
|
||||||
|
"spec.nodeName",
|
||||||
|
"spec.restartPolicy",
|
||||||
|
"spec.serviceAccountName",
|
||||||
|
"spec.schedulerName",
|
||||||
|
"status.phase",
|
||||||
|
"status.hostIP",
|
||||||
|
"status.podIP":
|
||||||
|
return label, value, nil
|
||||||
|
// This is for backwards compatibility with old v1 clients which send spec.host
|
||||||
|
case "spec.host":
|
||||||
|
return "spec.nodeName", value, nil
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("field label not supported: %s", label)
|
||||||
|
}
|
||||||
|
}
|
87
pkg/apis/core/pods/helpers_test.go
Normal file
87
pkg/apis/core/pods/helpers_test.go
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package pods
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConvertDownwardAPIFieldLabel(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
version string
|
||||||
|
label string
|
||||||
|
value string
|
||||||
|
expectedErr bool
|
||||||
|
expectedLabel string
|
||||||
|
expectedValue string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
version: "v2",
|
||||||
|
label: "metadata.name",
|
||||||
|
value: "test-pod",
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v1",
|
||||||
|
label: "invalid-label",
|
||||||
|
value: "value",
|
||||||
|
expectedErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v1",
|
||||||
|
label: "metadata.name",
|
||||||
|
value: "test-pod",
|
||||||
|
expectedLabel: "metadata.name",
|
||||||
|
expectedValue: "test-pod",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v1",
|
||||||
|
label: "metadata.annotations",
|
||||||
|
value: "myValue",
|
||||||
|
expectedLabel: "metadata.annotations",
|
||||||
|
expectedValue: "myValue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v1",
|
||||||
|
label: "metadata.annotations['myKey']",
|
||||||
|
value: "myValue",
|
||||||
|
expectedLabel: "metadata.annotations['myKey']",
|
||||||
|
expectedValue: "myValue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v1",
|
||||||
|
label: "spec.host",
|
||||||
|
value: "127.0.0.1",
|
||||||
|
expectedLabel: "spec.nodeName",
|
||||||
|
expectedValue: "127.0.0.1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
label, value, err := ConvertDownwardAPIFieldLabel(tc.version, tc.label, tc.value)
|
||||||
|
if err != nil {
|
||||||
|
if tc.expectedErr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("ConvertDownwardAPIFieldLabel(%s, %s, %s) failed: %s",
|
||||||
|
tc.version, tc.label, tc.value, err)
|
||||||
|
}
|
||||||
|
if tc.expectedLabel != label || tc.expectedValue != value {
|
||||||
|
t.Errorf("ConvertDownwardAPIFieldLabel(%s, %s, %s) = (%s, %s, nil), expected (%s, %s, nil)",
|
||||||
|
tc.version, tc.label, tc.value, label, value, tc.expectedLabel, tc.expectedValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -157,17 +157,12 @@ func addConversionFuncs(scheme *runtime.Scheme) error {
|
|||||||
err = scheme.AddFieldLabelConversionFunc("v1", "Pod",
|
err = scheme.AddFieldLabelConversionFunc("v1", "Pod",
|
||||||
func(label, value string) (string, string, error) {
|
func(label, value string) (string, string, error) {
|
||||||
switch label {
|
switch label {
|
||||||
case "metadata.annotations",
|
case "metadata.name",
|
||||||
"metadata.labels",
|
|
||||||
"metadata.name",
|
|
||||||
"metadata.namespace",
|
"metadata.namespace",
|
||||||
"metadata.uid",
|
|
||||||
"spec.nodeName",
|
"spec.nodeName",
|
||||||
"spec.restartPolicy",
|
"spec.restartPolicy",
|
||||||
"spec.serviceAccountName",
|
|
||||||
"spec.schedulerName",
|
"spec.schedulerName",
|
||||||
"status.phase",
|
"status.phase",
|
||||||
"status.hostIP",
|
|
||||||
"status.podIP":
|
"status.podIP":
|
||||||
return label, value, nil
|
return label, value, nil
|
||||||
// This is for backwards compatibility with old v1 clients which send spec.host
|
// This is for backwards compatibility with old v1 clients which send spec.host
|
||||||
|
@ -18,10 +18,12 @@ go_library(
|
|||||||
"//pkg/api/service:go_default_library",
|
"//pkg/api/service:go_default_library",
|
||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
"//pkg/apis/core/helper:go_default_library",
|
"//pkg/apis/core/helper:go_default_library",
|
||||||
|
"//pkg/apis/core/pods:go_default_library",
|
||||||
"//pkg/apis/core/v1:go_default_library",
|
"//pkg/apis/core/v1:go_default_library",
|
||||||
"//pkg/apis/core/v1/helper:go_default_library",
|
"//pkg/apis/core/v1/helper:go_default_library",
|
||||||
"//pkg/capabilities:go_default_library",
|
"//pkg/capabilities:go_default_library",
|
||||||
"//pkg/features:go_default_library",
|
"//pkg/features:go_default_library",
|
||||||
|
"//pkg/fieldpath:go_default_library",
|
||||||
"//pkg/security/apparmor:go_default_library",
|
"//pkg/security/apparmor:go_default_library",
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
"//vendor/github.com/golang/glog:go_default_library",
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
|
@ -42,14 +42,15 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
|
||||||
apiservice "k8s.io/kubernetes/pkg/api/service"
|
apiservice "k8s.io/kubernetes/pkg/api/service"
|
||||||
"k8s.io/kubernetes/pkg/apis/core"
|
"k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||||
|
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||||
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
"k8s.io/kubernetes/pkg/capabilities"
|
"k8s.io/kubernetes/pkg/capabilities"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/kubernetes/pkg/fieldpath"
|
||||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -960,7 +961,7 @@ func validateFlockerVolumeSource(flocker *core.FlockerVolumeSource, fldPath *fie
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
var validDownwardAPIFieldPathExpressions = sets.NewString(
|
var validVolumeDownwardAPIFieldPathExpressions = sets.NewString(
|
||||||
"metadata.name",
|
"metadata.name",
|
||||||
"metadata.namespace",
|
"metadata.namespace",
|
||||||
"metadata.labels",
|
"metadata.labels",
|
||||||
@ -975,7 +976,7 @@ func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *fi
|
|||||||
}
|
}
|
||||||
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
|
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
|
||||||
if file.FieldRef != nil {
|
if file.FieldRef != nil {
|
||||||
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validVolumeDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||||
if file.ResourceFieldRef != nil {
|
if file.ResourceFieldRef != nil {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
||||||
}
|
}
|
||||||
@ -1898,7 +1899,14 @@ func ValidateEnv(vars []core.EnvVar, fldPath *field.Path) field.ErrorList {
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
var validFieldPathExpressionsEnv = sets.NewString("metadata.name", "metadata.namespace", "metadata.uid", "spec.nodeName", "spec.serviceAccountName", "status.hostIP", "status.podIP")
|
var validEnvDownwardAPIFieldPathExpressions = sets.NewString(
|
||||||
|
"metadata.name",
|
||||||
|
"metadata.namespace",
|
||||||
|
"metadata.uid",
|
||||||
|
"spec.nodeName",
|
||||||
|
"spec.serviceAccountName",
|
||||||
|
"status.hostIP",
|
||||||
|
"status.podIP")
|
||||||
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")
|
var validContainerResourceFieldPathExpressions = sets.NewString("limits.cpu", "limits.memory", "limits.ephemeral-storage", "requests.cpu", "requests.memory", "requests.ephemeral-storage")
|
||||||
|
|
||||||
func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorList {
|
func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorList {
|
||||||
@ -1912,7 +1920,7 @@ func validateEnvVarValueFrom(ev core.EnvVar, fldPath *field.Path) field.ErrorLis
|
|||||||
|
|
||||||
if ev.ValueFrom.FieldRef != nil {
|
if ev.ValueFrom.FieldRef != nil {
|
||||||
numSources++
|
numSources++
|
||||||
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validFieldPathExpressionsEnv, fldPath.Child("fieldRef"))...)
|
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validEnvDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||||
}
|
}
|
||||||
if ev.ValueFrom.ResourceFieldRef != nil {
|
if ev.ValueFrom.ResourceFieldRef != nil {
|
||||||
numSources++
|
numSources++
|
||||||
@ -1945,15 +1953,35 @@ func validateObjectFieldSelector(fs *core.ObjectFieldSelector, expressions *sets
|
|||||||
|
|
||||||
if len(fs.APIVersion) == 0 {
|
if len(fs.APIVersion) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("apiVersion"), ""))
|
||||||
} else if len(fs.FieldPath) == 0 {
|
return allErrs
|
||||||
|
}
|
||||||
|
if len(fs.FieldPath) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("fieldPath"), ""))
|
||||||
} else {
|
return allErrs
|
||||||
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
|
}
|
||||||
|
|
||||||
|
internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err)))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldPath"), fs.FieldPath, fmt.Sprintf("error converting fieldPath: %v", err)))
|
||||||
} else if !expressions.Has(internalFieldPath) {
|
return allErrs
|
||||||
allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), internalFieldPath, expressions.List()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path, subscript, ok := fieldpath.SplitMaybeSubscriptedPath(internalFieldPath); ok {
|
||||||
|
switch path {
|
||||||
|
case "metadata.annotations":
|
||||||
|
for _, msg := range validation.IsQualifiedName(strings.ToLower(subscript)) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg))
|
||||||
|
}
|
||||||
|
case "metadata.labels":
|
||||||
|
for _, msg := range validation.IsQualifiedName(subscript) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, subscript, msg))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, path, "does not support subscript"))
|
||||||
|
}
|
||||||
|
} else if !expressions.Has(path) {
|
||||||
|
allErrs = append(allErrs, field.NotSupported(fldPath.Child("fieldPath"), path, expressions.List()))
|
||||||
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
|
@ -2628,6 +2628,20 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
FieldPath: "metadata.labels",
|
FieldPath: "metadata.labels",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Path: "labels with subscript",
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: "v1",
|
||||||
|
FieldPath: "metadata.labels['key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "labels with complex subscript",
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: "v1",
|
||||||
|
FieldPath: "metadata.labels['test.example.com/key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Path: "annotations",
|
Path: "annotations",
|
||||||
FieldRef: &core.ObjectFieldSelector{
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
@ -2635,6 +2649,20 @@ func TestValidateVolumes(t *testing.T) {
|
|||||||
FieldPath: "metadata.annotations",
|
FieldPath: "metadata.annotations",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Path: "annotations with subscript",
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: "v1",
|
||||||
|
FieldPath: "metadata.annotations['key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Path: "annotations with complex subscript",
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: "v1",
|
||||||
|
FieldPath: "metadata.annotations['TEST.EXAMPLE.COM/key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Path: "namespace",
|
Path: "namespace",
|
||||||
FieldRef: &core.ObjectFieldSelector{
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
@ -3824,6 +3852,24 @@ func TestValidateEnv(t *testing.T) {
|
|||||||
{Name: "abc", Value: ""},
|
{Name: "abc", Value: ""},
|
||||||
{Name: "a.b.c", Value: "value"},
|
{Name: "a.b.c", Value: "value"},
|
||||||
{Name: "a-b-c", Value: "value"},
|
{Name: "a-b-c", Value: "value"},
|
||||||
|
{
|
||||||
|
Name: "abc",
|
||||||
|
ValueFrom: &core.EnvVarSource{
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(),
|
||||||
|
FieldPath: "metadata.annotations['key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "abc",
|
||||||
|
ValueFrom: &core.EnvVarSource{
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
APIVersion: legacyscheme.Registry.GroupOrDie(core.GroupName).GroupVersion.String(),
|
||||||
|
FieldPath: "metadata.labels['key']",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "abc",
|
Name: "abc",
|
||||||
ValueFrom: &core.EnvVarSource{
|
ValueFrom: &core.EnvVarSource{
|
||||||
@ -4095,7 +4141,20 @@ func TestValidateEnv(t *testing.T) {
|
|||||||
expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
|
expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.whoops": error converting fieldPath`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid fieldPath labels",
|
name: "metadata.name with subscript",
|
||||||
|
envs: []core.EnvVar{{
|
||||||
|
Name: "labels",
|
||||||
|
ValueFrom: &core.EnvVarSource{
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.name['key']",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
expectedError: `[0].valueFrom.fieldRef.fieldPath: Invalid value: "metadata.name['key']": error converting fieldPath: field label does not support subscript`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadata.labels without subscript",
|
||||||
envs: []core.EnvVar{{
|
envs: []core.EnvVar{{
|
||||||
Name: "labels",
|
Name: "labels",
|
||||||
ValueFrom: &core.EnvVarSource{
|
ValueFrom: &core.EnvVarSource{
|
||||||
@ -4108,7 +4167,7 @@ func TestValidateEnv(t *testing.T) {
|
|||||||
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"`,
|
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"`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid fieldPath annotations",
|
name: "metadata.annotations without subscript",
|
||||||
envs: []core.EnvVar{{
|
envs: []core.EnvVar{{
|
||||||
Name: "abc",
|
Name: "abc",
|
||||||
ValueFrom: &core.EnvVarSource{
|
ValueFrom: &core.EnvVarSource{
|
||||||
@ -4120,6 +4179,32 @@ func TestValidateEnv(t *testing.T) {
|
|||||||
}},
|
}},
|
||||||
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"`,
|
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"`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "metadata.annotations with invalid key",
|
||||||
|
envs: []core.EnvVar{{
|
||||||
|
Name: "abc",
|
||||||
|
ValueFrom: &core.EnvVarSource{
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.annotations['invalid~key']",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
expectedError: `field[0].valueFrom.fieldRef: Invalid value: "invalid~key"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadata.labels with invalid key",
|
||||||
|
envs: []core.EnvVar{{
|
||||||
|
Name: "abc",
|
||||||
|
ValueFrom: &core.EnvVarSource{
|
||||||
|
FieldRef: &core.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.labels['Www.k8s.io/test']",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
expectedError: `field[0].valueFrom.fieldRef: Invalid value: "Www.k8s.io/test"`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unsupported fieldPath",
|
name: "unsupported fieldPath",
|
||||||
envs: []core.EnvVar{{
|
envs: []core.EnvVar{{
|
||||||
|
@ -13,7 +13,10 @@ go_library(
|
|||||||
"fieldpath.go",
|
"fieldpath.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kubernetes/pkg/fieldpath",
|
importpath = "k8s.io/kubernetes/pkg/fieldpath",
|
||||||
deps = ["//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library"],
|
deps = [
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
|
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatMap formats map[string]string to a string.
|
// FormatMap formats map[string]string to a string.
|
||||||
@ -42,6 +43,23 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if path, subscript, ok := SplitMaybeSubscriptedPath(fieldPath); ok {
|
||||||
|
switch path {
|
||||||
|
case "metadata.annotations":
|
||||||
|
if errs := validation.IsQualifiedName(strings.ToLower(subscript)); len(errs) != 0 {
|
||||||
|
return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
return accessor.GetAnnotations()[subscript], nil
|
||||||
|
case "metadata.labels":
|
||||||
|
if errs := validation.IsQualifiedName(subscript); len(errs) != 0 {
|
||||||
|
return "", fmt.Errorf("invalid key subscript in %s: %s", fieldPath, strings.Join(errs, ";"))
|
||||||
|
}
|
||||||
|
return accessor.GetLabels()[subscript], nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("fieldPath %q does not support subscript", fieldPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch fieldPath {
|
switch fieldPath {
|
||||||
case "metadata.annotations":
|
case "metadata.annotations":
|
||||||
return FormatMap(accessor.GetAnnotations()), nil
|
return FormatMap(accessor.GetAnnotations()), nil
|
||||||
@ -57,3 +75,29 @@ func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error)
|
|||||||
|
|
||||||
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
|
return "", fmt.Errorf("unsupported fieldPath: %v", fieldPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitMaybeSubscriptedPath checks whether the specified fieldPath is
|
||||||
|
// subscripted, and
|
||||||
|
// - if yes, this function splits the fieldPath into path and subscript, and
|
||||||
|
// returns (path, subscript, true).
|
||||||
|
// - if no, this function returns (fieldPath, "", false).
|
||||||
|
//
|
||||||
|
// Example inputs and outputs:
|
||||||
|
// - "metadata.annotations['myKey']" --> ("metadata.annotations", "myKey", true)
|
||||||
|
// - "metadata.annotations['a[b]c']" --> ("metadata.annotations", "a[b]c", true)
|
||||||
|
// - "metadata.labels['']" --> ("metadata.labels", "", true)
|
||||||
|
// - "metadata.labels" --> ("metadata.labels", "", false)
|
||||||
|
func SplitMaybeSubscriptedPath(fieldPath string) (string, string, bool) {
|
||||||
|
if !strings.HasSuffix(fieldPath, "']") {
|
||||||
|
return fieldPath, "", false
|
||||||
|
}
|
||||||
|
s := strings.TrimSuffix(fieldPath, "']")
|
||||||
|
parts := strings.SplitN(s, "['", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return fieldPath, "", false
|
||||||
|
}
|
||||||
|
if len(parts[0]) == 0 {
|
||||||
|
return fieldPath, "", false
|
||||||
|
}
|
||||||
|
return parts[0], parts[1], true
|
||||||
|
}
|
||||||
|
@ -36,7 +36,6 @@ func TestExtractFieldPathAsString(t *testing.T) {
|
|||||||
name: "not an API object",
|
name: "not an API object",
|
||||||
fieldPath: "metadata.name",
|
fieldPath: "metadata.name",
|
||||||
obj: "",
|
obj: "",
|
||||||
expectedMessageFragment: "expected struct",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ok - namespace",
|
name: "ok - namespace",
|
||||||
@ -88,7 +87,26 @@ func TestExtractFieldPathAsString(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedValue: "builder=\"john-doe\"",
|
expectedValue: "builder=\"john-doe\"",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "ok - annotation",
|
||||||
|
fieldPath: "metadata.annotations['spec.pod.beta.kubernetes.io/statefulset-index']",
|
||||||
|
obj: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"spec.pod.beta.kubernetes.io/statefulset-index": "1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedValue: "1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ok - annotation",
|
||||||
|
fieldPath: "metadata.annotations['Www.k8s.io/test']",
|
||||||
|
obj: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"Www.k8s.io/test": "1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedValue: "1",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "invalid expression",
|
name: "invalid expression",
|
||||||
fieldPath: "metadata.whoops",
|
fieldPath: "metadata.whoops",
|
||||||
@ -99,6 +117,26 @@ func TestExtractFieldPathAsString(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectedMessageFragment: "unsupported fieldPath",
|
expectedMessageFragment: "unsupported fieldPath",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid annotation key",
|
||||||
|
fieldPath: "metadata.annotations['invalid~key']",
|
||||||
|
obj: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedMessageFragment: "invalid key subscript in metadata.annotations",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid label key",
|
||||||
|
fieldPath: "metadata.labels['Www.k8s.io/test']",
|
||||||
|
obj: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Annotations: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedMessageFragment: "invalid key subscript in metadata.labels",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@ -111,8 +149,93 @@ func TestExtractFieldPathAsString(t *testing.T) {
|
|||||||
} else {
|
} else {
|
||||||
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
t.Errorf("%v: unexpected error: %v", tc.name, err)
|
||||||
}
|
}
|
||||||
|
} else if tc.expectedMessageFragment != "" {
|
||||||
|
t.Errorf("%v: expected error: %v", tc.name, tc.expectedMessageFragment)
|
||||||
} else if e := tc.expectedValue; e != "" && e != actual {
|
} else if e := tc.expectedValue; e != "" && e != actual {
|
||||||
t.Errorf("%v: unexpected result; got %q, expected %q", tc.name, actual, e)
|
t.Errorf("%v: unexpected result; got %q, expected %q", tc.name, actual, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitMaybeSubscriptedPath(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
fieldPath string
|
||||||
|
expectedPath string
|
||||||
|
expectedSubscript string
|
||||||
|
expectedOK bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.annotations['key']",
|
||||||
|
expectedPath: "metadata.annotations",
|
||||||
|
expectedSubscript: "key",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.annotations['a[b']c']",
|
||||||
|
expectedPath: "metadata.annotations",
|
||||||
|
expectedSubscript: "a[b']c",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels['['key']",
|
||||||
|
expectedPath: "metadata.labels",
|
||||||
|
expectedSubscript: "['key",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels['key']']",
|
||||||
|
expectedPath: "metadata.labels",
|
||||||
|
expectedSubscript: "key']",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels['']",
|
||||||
|
expectedPath: "metadata.labels",
|
||||||
|
expectedSubscript: "",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels[' ']",
|
||||||
|
expectedPath: "metadata.labels",
|
||||||
|
expectedSubscript: " ",
|
||||||
|
expectedOK: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels[ 'key' ]",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels[]",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels[']",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels['key']foo",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "['key']",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldPath: "metadata.labels",
|
||||||
|
expectedOK: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
path, subscript, ok := SplitMaybeSubscriptedPath(tc.fieldPath)
|
||||||
|
if !ok {
|
||||||
|
if tc.expectedOK {
|
||||||
|
t.Errorf("SplitMaybeSubscriptedPath(%q) expected to return (_, _, true)", tc.fieldPath)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if path != tc.expectedPath || subscript != tc.expectedSubscript {
|
||||||
|
t.Errorf("SplitMaybeSubscriptedPath(%q) = (%q, %q, true), expect (%q, %q, true)",
|
||||||
|
tc.fieldPath, path, subscript, tc.expectedPath, tc.expectedSubscript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,10 +29,10 @@ go_library(
|
|||||||
],
|
],
|
||||||
importpath = "k8s.io/kubernetes/pkg/kubelet",
|
importpath = "k8s.io/kubernetes/pkg/kubelet",
|
||||||
deps = [
|
deps = [
|
||||||
"//pkg/api/legacyscheme:go_default_library",
|
|
||||||
"//pkg/api/v1/pod:go_default_library",
|
"//pkg/api/v1/pod:go_default_library",
|
||||||
"//pkg/api/v1/resource:go_default_library",
|
"//pkg/api/v1/resource:go_default_library",
|
||||||
"//pkg/apis/core:go_default_library",
|
"//pkg/apis/core:go_default_library",
|
||||||
|
"//pkg/apis/core/pods:go_default_library",
|
||||||
"//pkg/apis/core/v1:go_default_library",
|
"//pkg/apis/core/v1:go_default_library",
|
||||||
"//pkg/apis/core/v1/helper:go_default_library",
|
"//pkg/apis/core/v1/helper:go_default_library",
|
||||||
"//pkg/apis/core/v1/helper/qos:go_default_library",
|
"//pkg/apis/core/v1/helper/qos:go_default_library",
|
||||||
|
@ -43,9 +43,9 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/tools/remotecommand"
|
"k8s.io/client-go/tools/remotecommand"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
|
||||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||||
"k8s.io/kubernetes/pkg/api/v1/resource"
|
"k8s.io/kubernetes/pkg/api/v1/resource"
|
||||||
|
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
|
v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/v1/validation"
|
"k8s.io/kubernetes/pkg/apis/core/v1/validation"
|
||||||
@ -777,7 +777,7 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container
|
|||||||
// podFieldSelectorRuntimeValue returns the runtime value of the given
|
// podFieldSelectorRuntimeValue returns the runtime value of the given
|
||||||
// selector for a pod.
|
// selector for a pod.
|
||||||
func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod *v1.Pod, podIP string) (string, error) {
|
func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *v1.ObjectFieldSelector, pod *v1.Pod, podIP string) (string, error) {
|
||||||
internalFieldPath, _, err := legacyscheme.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
|
internalFieldPath, _, err := podshelper.ConvertDownwardAPIFieldLabel(fs.APIVersion, fs.FieldPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -544,6 +544,48 @@ func TestCollectDataWithDownwardAPI(t *testing.T) {
|
|||||||
payload map[string]util.FileProjection
|
payload map[string]util.FileProjection
|
||||||
success bool
|
success bool
|
||||||
}{
|
}{
|
||||||
|
{
|
||||||
|
name: "annotation",
|
||||||
|
volumeFile: []v1.DownwardAPIVolumeFile{
|
||||||
|
{Path: "annotation", FieldRef: &v1.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.annotations['a1']"}}},
|
||||||
|
pod: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: testPodName,
|
||||||
|
Namespace: testNamespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"a1": "value1",
|
||||||
|
"a2": "value2",
|
||||||
|
},
|
||||||
|
UID: testPodUID},
|
||||||
|
},
|
||||||
|
mode: 0644,
|
||||||
|
payload: map[string]util.FileProjection{
|
||||||
|
"annotation": {Data: []byte("value1"), Mode: 0644},
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "annotation-error",
|
||||||
|
volumeFile: []v1.DownwardAPIVolumeFile{
|
||||||
|
{Path: "annotation", FieldRef: &v1.ObjectFieldSelector{
|
||||||
|
FieldPath: "metadata.annotations['']"}}},
|
||||||
|
pod: &v1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: testPodName,
|
||||||
|
Namespace: testNamespace,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"a1": "value1",
|
||||||
|
"a2": "value2",
|
||||||
|
},
|
||||||
|
UID: testPodUID},
|
||||||
|
},
|
||||||
|
mode: 0644,
|
||||||
|
payload: map[string]util.FileProjection{
|
||||||
|
"annotation": {Data: []byte("does-not-matter-because-this-test-case-will-fail-anyway"), Mode: 0644},
|
||||||
|
},
|
||||||
|
success: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "labels",
|
name: "labels",
|
||||||
volumeFile: []v1.DownwardAPIVolumeFile{
|
volumeFile: []v1.DownwardAPIVolumeFile{
|
||||||
|
Loading…
Reference in New Issue
Block a user