mirror of
https://github.com/containers/podman.git
synced 2025-06-24 19:42:56 +08:00
play kube: support optional/mandatory env var from config map
In K8S the pod creation fails if an env var reference a non existing config map key. It can be marked as optional, but per default it is mandatory. Podman on the other hand always treat such references as optional. Rework envVarsFrom() and envVarValue() to additionaly return an error and add support for the optional attribute in configMapRef and configMapKeyRef. Signed-off-by: Alban Bedel <albeu@free.fr>
This commit is contained in:
@ -210,12 +210,18 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||
}
|
||||
|
||||
for _, env := range opts.Container.Env {
|
||||
value := envVarValue(env, opts)
|
||||
value, err := envVarValue(env, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
envs[env.Name] = value
|
||||
}
|
||||
for _, envFrom := range opts.Container.EnvFrom {
|
||||
cmEnvs := envVarsFrom(envFrom, opts)
|
||||
cmEnvs, err := envVarsFrom(envFrom, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range cmEnvs {
|
||||
envs[k] = v
|
||||
@ -326,39 +332,54 @@ func quantityToInt64(quantity *resource.Quantity) (int64, error) {
|
||||
}
|
||||
|
||||
// envVarsFrom returns all key-value pairs as env vars from a configMap that matches the envFrom setting of a container
|
||||
func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) map[string]string {
|
||||
func envVarsFrom(envFrom v1.EnvFromSource, opts *CtrSpecGenOptions) (map[string]string, error) {
|
||||
envs := map[string]string{}
|
||||
|
||||
if envFrom.ConfigMapRef != nil {
|
||||
cmName := envFrom.ConfigMapRef.Name
|
||||
cmRef := envFrom.ConfigMapRef
|
||||
err := errors.Errorf("Configmap %v not found", cmRef.Name)
|
||||
|
||||
for _, c := range opts.ConfigMaps {
|
||||
if cmName == c.Name {
|
||||
if cmRef.Name == c.Name {
|
||||
envs = c.Data
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil && (cmRef.Optional == nil || !*cmRef.Optional) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return envs
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
// envVarValue returns the environment variable value configured within the container's env setting.
|
||||
// It gets the value from a configMap if specified, otherwise returns env.Value
|
||||
func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) string {
|
||||
func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) {
|
||||
if env.ValueFrom != nil {
|
||||
if env.ValueFrom.ConfigMapKeyRef != nil {
|
||||
cmKeyRef := env.ValueFrom.ConfigMapKeyRef
|
||||
err := errors.Errorf("Cannot set env %v: configmap %v not found", env.Name, cmKeyRef.Name)
|
||||
|
||||
for _, c := range opts.ConfigMaps {
|
||||
if env.ValueFrom.ConfigMapKeyRef.Name == c.Name {
|
||||
if value, ok := c.Data[env.ValueFrom.ConfigMapKeyRef.Key]; ok {
|
||||
return value
|
||||
if cmKeyRef.Name == c.Name {
|
||||
if value, ok := c.Data[cmKeyRef.Key]; ok {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return env.Value
|
||||
return env.Value, nil
|
||||
}
|
||||
|
||||
// getPodPorts converts a slice of kube container descriptions to an
|
||||
|
@ -13,6 +13,7 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
name string
|
||||
envFrom v1.EnvFromSource
|
||||
options CtrSpecGenOptions
|
||||
succeed bool
|
||||
expected map[string]string
|
||||
}{
|
||||
{
|
||||
@ -27,6 +28,7 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
true,
|
||||
map[string]string{
|
||||
"myvar": "foo",
|
||||
},
|
||||
@ -43,6 +45,23 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"OptionalConfigMapDoesNotExist",
|
||||
v1.EnvFromSource{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
true,
|
||||
map[string]string{},
|
||||
},
|
||||
{
|
||||
@ -57,6 +76,23 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: []v1.ConfigMap{},
|
||||
},
|
||||
false,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"OptionalEmptyConfigMapList",
|
||||
v1.EnvFromSource{
|
||||
ConfigMapRef: &v1.ConfigMapEnvSource{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: []v1.ConfigMap{},
|
||||
},
|
||||
true,
|
||||
map[string]string{},
|
||||
},
|
||||
}
|
||||
@ -64,7 +100,8 @@ func TestEnvVarsFrom(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := envVarsFrom(test.envFrom, &test.options)
|
||||
result, err := envVarsFrom(test.envFrom, &test.options)
|
||||
assert.Equal(t, err == nil, test.succeed)
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
@ -75,6 +112,7 @@ func TestEnvVarValue(t *testing.T) {
|
||||
name string
|
||||
envVar v1.EnvVar
|
||||
options CtrSpecGenOptions
|
||||
succeed bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
@ -93,6 +131,7 @@ func TestEnvVarValue(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
true,
|
||||
"foo",
|
||||
},
|
||||
{
|
||||
@ -111,6 +150,27 @@ func TestEnvVarValue(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"OptionalContainerKeyDoesNotExistInConfigMap",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Key: "doesnotexist",
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
@ -129,6 +189,27 @@ func TestEnvVarValue(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"OptionalConfigMapDoesNotExist",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "doesnotexist",
|
||||
},
|
||||
Key: "myvar",
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: configMapList,
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
{
|
||||
@ -147,6 +228,27 @@ func TestEnvVarValue(t *testing.T) {
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: []v1.ConfigMap{},
|
||||
},
|
||||
false,
|
||||
"",
|
||||
},
|
||||
{
|
||||
"OptionalEmptyConfigMapList",
|
||||
v1.EnvVar{
|
||||
Name: "FOO",
|
||||
ValueFrom: &v1.EnvVarSource{
|
||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
||||
LocalObjectReference: v1.LocalObjectReference{
|
||||
Name: "foo",
|
||||
},
|
||||
Key: "myvar",
|
||||
Optional: &optional,
|
||||
},
|
||||
},
|
||||
},
|
||||
CtrSpecGenOptions{
|
||||
ConfigMaps: []v1.ConfigMap{},
|
||||
},
|
||||
true,
|
||||
"",
|
||||
},
|
||||
}
|
||||
@ -154,7 +256,8 @@ func TestEnvVarValue(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result := envVarValue(test.envVar, &test.options)
|
||||
result, err := envVarValue(test.envVar, &test.options)
|
||||
assert.Equal(t, err == nil, test.succeed)
|
||||
assert.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
@ -184,3 +287,5 @@ var configMapList = []v1.ConfigMap{
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var optional = true
|
||||
|
@ -141,6 +141,7 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: {{ .RefName }}
|
||||
key: {{ .RefKey }}
|
||||
optional: {{ .Optional }}
|
||||
{{ else }}
|
||||
value: {{ .Value }}
|
||||
{{ end }}
|
||||
@ -151,6 +152,7 @@ spec:
|
||||
{{ if (eq .From "configmap") }}
|
||||
- configMapRef:
|
||||
name: {{ .Name }}
|
||||
optional: {{ .Optional }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@ -746,7 +748,7 @@ func withVolumeMount(mountPath string, readonly bool) ctrOption {
|
||||
}
|
||||
}
|
||||
|
||||
func withEnv(name, value, valueFrom, refName, refKey string) ctrOption {
|
||||
func withEnv(name, value, valueFrom, refName, refKey string, optional bool) ctrOption {
|
||||
return func(c *Ctr) {
|
||||
e := Env{
|
||||
Name: name,
|
||||
@ -754,17 +756,19 @@ func withEnv(name, value, valueFrom, refName, refKey string) ctrOption {
|
||||
ValueFrom: valueFrom,
|
||||
RefName: refName,
|
||||
RefKey: refKey,
|
||||
Optional: optional,
|
||||
}
|
||||
|
||||
c.Env = append(c.Env, e)
|
||||
}
|
||||
}
|
||||
|
||||
func withEnvFrom(name, from string) ctrOption {
|
||||
func withEnvFrom(name, from string, optional bool) ctrOption {
|
||||
return func(c *Ctr) {
|
||||
e := EnvFrom{
|
||||
Name: name,
|
||||
From: from,
|
||||
Name: name,
|
||||
From: from,
|
||||
Optional: optional,
|
||||
}
|
||||
|
||||
c.EnvFrom = append(c.EnvFrom, e)
|
||||
@ -822,11 +826,13 @@ type Env struct {
|
||||
ValueFrom string
|
||||
RefName string
|
||||
RefKey string
|
||||
Optional bool
|
||||
}
|
||||
|
||||
type EnvFrom struct {
|
||||
Name string
|
||||
From string
|
||||
Name string
|
||||
From string
|
||||
Optional bool
|
||||
}
|
||||
|
||||
func milliCPUToQuota(milliCPU string) int {
|
||||
@ -1062,7 +1068,7 @@ var _ = Describe("Podman play kube", func() {
|
||||
err := generateKubeYaml("configmap", cm, cmYamlPathname)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO"))))
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
@ -1076,6 +1082,68 @@ var _ = Describe("Podman play kube", func() {
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
|
||||
})
|
||||
|
||||
It("podman play kube test required env value from configmap with missing key", func() {
|
||||
SkipIfRemote("configmap list is not supported as a param")
|
||||
cmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
|
||||
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
|
||||
err := generateKubeYaml("configmap", cm, cmYamlPathname)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "MISSING_KEY", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", cmYamlPathname})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test required env value from missing configmap", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "missing_cm", "FOO", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test optional env value from configmap with missing key", func() {
|
||||
SkipIfRemote("configmap list is not supported as a param")
|
||||
cmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
|
||||
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
|
||||
err := generateKubeYaml("configmap", cm, cmYamlPathname)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "MISSING_KEY", true))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", cmYamlPathname})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ range .Config.Env }}[{{ . }}]{{end}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`[FOO=]`))
|
||||
})
|
||||
|
||||
It("podman play kube test optional env value from missing configmap", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "missing_cm", "FOO", true))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ range .Config.Env }}[{{ . }}]{{end}}'"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect.ExitCode()).To(Equal(0))
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`[FOO=]`))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from configmap as envs", func() {
|
||||
SkipIfRemote("configmap list is not supported as a param")
|
||||
cmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
|
||||
@ -1083,7 +1151,7 @@ var _ = Describe("Podman play kube", func() {
|
||||
err := generateKubeYaml("configmap", cm, cmYamlPathname)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("foo", "configmap"))))
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("foo", "configmap", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
@ -1098,6 +1166,26 @@ var _ = Describe("Podman play kube", func() {
|
||||
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO2=foo2`))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from required configmap as envs", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("missing_cm", "configmap", false))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Not(Equal(0)))
|
||||
})
|
||||
|
||||
It("podman play kube test get all key-value pairs from optional configmap as envs", func() {
|
||||
pod := getPod(withCtr(getCtr(withEnvFrom("missing_cm", "configmap", true))))
|
||||
err = generateKubeYaml("pod", pod, kubeYaml)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||
kube.WaitWithDefaultTimeout()
|
||||
Expect(kube.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman play kube test hostname", func() {
|
||||
pod := getPod()
|
||||
err := generateKubeYaml("pod", pod, kubeYaml)
|
||||
|
Reference in New Issue
Block a user