play kube envVar.valueFrom.fieldRef

add support for env vars values from pod spec fields
see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#envvarsource-v1-core

relates to issue https://github.com/containers/podman/issues/12756

Signed-off-by: Yaron Dayagi <ydayagi@redhat.com>
This commit is contained in:
Yaron Dayagi 2022-01-30 20:45:30 +02:00
parent c96aa23adb
commit 2ceab11947
4 changed files with 242 additions and 25 deletions

View file

@ -365,6 +365,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
if err != nil {
return nil, err
}
for k, v := range podSpec.PodSpecGen.Labels { // add podYAML labels
labels[k] = v
}
specgenOpts := kube.CtrSpecGenOptions{
Annotations: annotations,
Container: initCtr,
@ -405,7 +410,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, err
}
for k, v := range podSpec.PodSpecGen.Labels { // add podYAML labels
labels[k] = v
}
specgenOpts := kube.CtrSpecGenOptions{
Annotations: annotations,
Container: container,
Image: pulledImage,
Volumes: volumes,

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"net"
"regexp"
"strings"
"time"
@ -291,9 +292,9 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
return nil, err
}
// Only set the env if the value is not ""
if value != "" {
envs[env.Name] = value
// Only set the env if the value is not nil
if value != nil {
envs[env.Name] = *value
}
}
for _, envFrom := range opts.Container.EnvFrom {
@ -609,7 +610,7 @@ func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]
// envVarValue returns the environment variable value configured within the container's env setting.
// It gets the value from a configMap or secret if specified, otherwise returns env.Value
func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) {
if env.ValueFrom != nil {
if env.ValueFrom.ConfigMapKeyRef != nil {
cmKeyRef := env.ValueFrom.ConfigMapKeyRef
@ -618,16 +619,16 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
for _, c := range opts.ConfigMaps {
if cmKeyRef.Name == c.Name {
if value, ok := c.Data[cmKeyRef.Key]; ok {
return value, nil
return &value, nil
}
err = errors.Errorf("Cannot set env %v: key %s not found in configmap %v", env.Name, cmKeyRef.Key, cmKeyRef.Name)
break
}
}
if cmKeyRef.Optional == nil || !*cmKeyRef.Optional {
return "", err
return nil, err
}
return "", nil
return nil, nil
}
if env.ValueFrom.SecretKeyRef != nil {
@ -635,18 +636,56 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
secret, err := k8sSecretFromSecretManager(secKeyRef.Name, opts.SecretsManager)
if err == nil {
if val, ok := secret[secKeyRef.Key]; ok {
return string(val), nil
value := string(val)
return &value, nil
}
err = errors.Errorf("Secret %v has not %v key", secKeyRef.Name, secKeyRef.Key)
}
if secKeyRef.Optional == nil || !*secKeyRef.Optional {
return "", errors.Errorf("Cannot set env %v: %v", env.Name, err)
return nil, errors.Errorf("Cannot set env %v: %v", env.Name, err)
}
return "", nil
return nil, nil
}
if env.ValueFrom.FieldRef != nil {
return envVarValueFieldRef(env, opts)
}
}
return env.Value, nil
return &env.Value, nil
}
func envVarValueFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*string, error) {
fieldRef := env.ValueFrom.FieldRef
fieldPathLabelPattern := `^metadata.labels\['(.+)'\]$`
fieldPathLabelRegex := regexp.MustCompile(fieldPathLabelPattern)
fieldPathAnnotationPattern := `^metadata.annotations\['(.+)'\]$`
fieldPathAnnotationRegex := regexp.MustCompile(fieldPathAnnotationPattern)
fieldPath := fieldRef.FieldPath
if fieldPath == "metadata.name" {
return &opts.PodName, nil
}
if fieldPath == "metadata.uid" {
return &opts.PodID, nil
}
fieldPathMatches := fieldPathLabelRegex.FindStringSubmatch(fieldPath)
if len(fieldPathMatches) == 2 { // 1 for entire regex and 1 for subexp
labelValue := opts.Labels[fieldPathMatches[1]] // not existent label is OK
return &labelValue, nil
}
fieldPathMatches = fieldPathAnnotationRegex.FindStringSubmatch(fieldPath)
if len(fieldPathMatches) == 2 { // 1 for entire regex and 1 for subexp
annotationValue := opts.Annotations[fieldPathMatches[1]] // not existent annotation is OK
return &annotationValue, nil
}
return nil, errors.Errorf(
"Can not set env %v. Reason: fieldPath %v is either not valid or not supported",
env.Name, fieldPath,
)
}
// getPodPorts converts a slice of kube container descriptions to an

View file

@ -189,13 +189,15 @@ func TestEnvVarValue(t *testing.T) {
assert.NoError(t, err)
defer os.RemoveAll(d)
secretsManager := createSecrets(t, d)
value := "foo"
emptyValue := ""
tests := []struct {
name string
envVar v1.EnvVar
options CtrSpecGenOptions
succeed bool
expected string
expected *string
}{
{
"ConfigMapExists",
@ -214,7 +216,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: configMapList,
},
true,
"foo",
&value,
},
{
"ContainerKeyDoesNotExistInConfigMap",
@ -233,7 +235,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: configMapList,
},
false,
"",
nil,
},
{
"OptionalContainerKeyDoesNotExistInConfigMap",
@ -253,7 +255,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: configMapList,
},
true,
"",
nil,
},
{
"ConfigMapDoesNotExist",
@ -272,7 +274,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: configMapList,
},
false,
"",
nil,
},
{
"OptionalConfigMapDoesNotExist",
@ -292,7 +294,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: configMapList,
},
true,
"",
nil,
},
{
"EmptyConfigMapList",
@ -311,7 +313,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: []v1.ConfigMap{},
},
false,
"",
nil,
},
{
"OptionalEmptyConfigMapList",
@ -331,7 +333,7 @@ func TestEnvVarValue(t *testing.T) {
ConfigMaps: []v1.ConfigMap{},
},
true,
"",
nil,
},
{
"SecretExists",
@ -350,7 +352,7 @@ func TestEnvVarValue(t *testing.T) {
SecretsManager: secretsManager,
},
true,
"foo",
&value,
},
{
"ContainerKeyDoesNotExistInSecret",
@ -369,7 +371,7 @@ func TestEnvVarValue(t *testing.T) {
SecretsManager: secretsManager,
},
false,
"",
nil,
},
{
"OptionalContainerKeyDoesNotExistInSecret",
@ -389,7 +391,7 @@ func TestEnvVarValue(t *testing.T) {
SecretsManager: secretsManager,
},
true,
"",
nil,
},
{
"SecretDoesNotExist",
@ -408,7 +410,7 @@ func TestEnvVarValue(t *testing.T) {
SecretsManager: secretsManager,
},
false,
"",
nil,
},
{
"OptionalSecretDoesNotExist",
@ -428,7 +430,173 @@ func TestEnvVarValue(t *testing.T) {
SecretsManager: secretsManager,
},
true,
"",
nil,
},
{
"FieldRefMetadataName",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
CtrSpecGenOptions{
PodName: value,
},
true,
&value,
},
{
"FieldRefMetadataUID",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.uid",
},
},
},
CtrSpecGenOptions{
PodID: value,
},
true,
&value,
},
{
"FieldRefMetadataLabelsExist",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.labels['label']",
},
},
},
CtrSpecGenOptions{
Labels: map[string]string{"label": value},
},
true,
&value,
},
{
"FieldRefMetadataLabelsEmpty",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.labels['label']",
},
},
},
CtrSpecGenOptions{
Labels: map[string]string{"label": ""},
},
true,
&emptyValue,
},
{
"FieldRefMetadataLabelsNotExist",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.labels['label']",
},
},
},
CtrSpecGenOptions{},
true,
&emptyValue,
},
{
"FieldRefMetadataAnnotationsExist",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.annotations['annotation']",
},
},
},
CtrSpecGenOptions{
Annotations: map[string]string{"annotation": value},
},
true,
&value,
},
{
"FieldRefMetadataAnnotationsEmpty",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.annotations['annotation']",
},
},
},
CtrSpecGenOptions{
Annotations: map[string]string{"annotation": ""},
},
true,
&emptyValue,
},
{
"FieldRefMetadataAnnotationsNotExist",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.annotations['annotation']",
},
},
},
CtrSpecGenOptions{},
true,
&emptyValue,
},
{
"FieldRefInvalid1",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.annotations['annotation]",
},
},
},
CtrSpecGenOptions{},
false,
nil,
},
{
"FieldRefInvalid2",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.dummy['annotation']",
},
},
},
CtrSpecGenOptions{},
false,
nil,
},
{
"FieldRefNotSupported",
v1.EnvVar{
Name: "FOO",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
CtrSpecGenOptions{},
false,
nil,
},
}

View file

@ -2984,7 +2984,7 @@ invalid kube kind
inspect = podmanTest.Podman([]string{"inspect", podName + "-" + ctr02Name, "--format", "'{{.Config.Labels}}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`map[]`))
Expect(inspect.OutputToString()).NotTo(ContainSubstring(autoUpdateRegistry + ":" + autoUpdateRegistryValue))
})
It("podman play kube teardown", func() {