Support env variables based on ConfigMaps sent in payload

Fixes #12363

Signed-off-by: Jakub Dzon <jdzon@redhat.com>
This commit is contained in:
Jakub Dzon
2021-11-19 18:00:14 +01:00
parent 90c635fd67
commit dd80635df0
3 changed files with 370 additions and 6 deletions

View File

@ -16,6 +16,7 @@ Currently, the supported Kubernetes kinds are:
- Pod
- Deployment
- PersistentVolumeClaim
- ConfigMap
`Kubernetes Pods or Deployments`
@ -68,6 +69,40 @@ like:
The build will consider `foobar` to be the context directory for the build. If there is an image in local storage
called `foobar`, the image will not be built unless the `--build` flag is used.
`Kubernetes ConfigMap`
Kubernetes ConfigMap can be referred as a source of environment variables in Pods or Deployments.
For example ConfigMap defined in following YAML:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: foo
data:
FOO: bar
```
can be referred in a Pod in following way:
```
apiVersion: v1
kind: Pod
metadata:
...
spec:
containers:
- command:
- top
name: container-1
image: foobar
envFrom:
- configMapRef:
name: foo
optional: false
```
and as a result environment variable `FOO` will be set to `bar` for container `container-1`.
## OPTIONS
#### **--authfile**=*path*

View File

@ -57,6 +57,8 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
ipIndex := 0
var configMaps []v1.ConfigMap
// create pod on each document if it is a pod or deployment
// any other kube kind will be skipped
for _, document := range documentList {
@ -77,7 +79,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
podTemplateSpec.ObjectMeta = podYAML.ObjectMeta
podTemplateSpec.Spec = podYAML.Spec
r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex, podYAML.Annotations)
r, err := ic.playKubePod(ctx, podTemplateSpec.ObjectMeta.Name, &podTemplateSpec, options, &ipIndex, podYAML.Annotations, configMaps)
if err != nil {
return nil, err
}
@ -91,7 +93,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube Deployment", path)
}
r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex)
r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex, configMaps)
if err != nil {
return nil, err
}
@ -112,6 +114,13 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
report.Volumes = append(report.Volumes, r.Volumes...)
validKinds++
case "ConfigMap":
var configMap v1.ConfigMap
if err := yaml.Unmarshal(document, &configMap); err != nil {
return nil, errors.Wrapf(err, "unable to read YAML %q as Kube ConfigMap", path)
}
configMaps = append(configMaps, configMap)
default:
logrus.Infof("Kube kind %s not supported", kind)
continue
@ -125,7 +134,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options en
return report, nil
}
func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions, ipIndex *int) (*entities.PlayKubeReport, error) {
func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAML *v1apps.Deployment, options entities.PlayKubeOptions, ipIndex *int, configMaps []v1.ConfigMap) (*entities.PlayKubeReport, error) {
var (
deploymentName string
podSpec v1.PodTemplateSpec
@ -147,7 +156,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM
// create "replicas" number of pods
for i = 0; i < numReplicas; i++ {
podName := fmt.Sprintf("%s-pod-%d", deploymentName, i)
podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, deploymentYAML.Annotations)
podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, deploymentYAML.Annotations, configMaps)
if err != nil {
return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName)
}
@ -156,7 +165,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM
return &report, nil
}
func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int, annotations map[string]string) (*entities.PlayKubeReport, error) {
func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int, annotations map[string]string, configMaps []v1.ConfigMap) (*entities.PlayKubeReport, error) {
var (
writer io.Writer
playKubePod entities.PlayKubePod
@ -252,7 +261,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
ctrRestartPolicy = define.RestartPolicyAlways
}
configMaps := []v1.ConfigMap{}
configMapIndex := make(map[string]struct{})
for _, configMap := range configMaps {
configMapIndex[configMap.Name] = struct{}{}
}
for _, p := range options.ConfigMaps {
f, err := os.Open(p)
if err != nil {
@ -265,6 +277,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, errors.Wrapf(err, "%q", p)
}
if _, present := configMapIndex[cm.Name]; present {
return nil, errors.Errorf("ambiguous configuration: the same config map %s is present in YAML and in --configmaps %s file", cm.Name, p)
}
configMaps = append(configMaps, cm)
}

View File

@ -2,8 +2,13 @@ package integration
import (
"bytes"
"context"
"fmt"
"github.com/containers/podman/v3/pkg/bindings"
"github.com/containers/podman/v3/pkg/bindings/play"
"io/ioutil"
"net"
"net/url"
"os"
"path/filepath"
"strconv"
@ -436,6 +441,41 @@ spec:
{{ end }}
env:
- name: HOSTNAME
{{ range .Env }}
- name: {{ .Name }}
{{ if (eq .ValueFrom "configmap") }}
valueFrom:
configMapKeyRef:
name: {{ .RefName }}
key: {{ .RefKey }}
optional: {{ .Optional }}
{{ end }}
{{ if (eq .ValueFrom "secret") }}
valueFrom:
secretKeyRef:
name: {{ .RefName }}
key: {{ .RefKey }}
optional: {{ .Optional }}
{{ end }}
{{ if (eq .ValueFrom "") }}
value: {{ .Value }}
{{ end }}
{{ end }}
{{ with .EnvFrom}}
envFrom:
{{ range . }}
{{ if (eq .From "configmap") }}
- configMapRef:
name: {{ .Name }}
optional: {{ .Optional }}
{{ end }}
{{ if (eq .From "secret") }}
- secretRef:
name: {{ .Name }}
optional: {{ .Optional }}
{{ end }}
{{ end }}
{{ end }}
image: {{ .Image }}
name: {{ .Name }}
imagePullPolicy: {{ .PullPolicy }}
@ -2908,4 +2948,277 @@ ENV OPENJ9_JAVA_OPTIONS=%q
})
})
Context("with configmap in multi-doc yaml", func() {
It("podman play kube uses env value", func() {
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO", false))))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
})
It("podman play kube fails for required env value with missing key", func() {
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "MISSING_KEY", false))))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).To(ExitWithError())
})
It("podman play kube succeeds for optional env value with missing key", func() {
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "MISSING_KEY", true))))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ range .Config.Env }}[{{ . }}]{{end}}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`[FOO=]`))
})
It("podman play kube uses all key-value pairs as envs", func() {
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO1", "foo1"), withConfigMapData("FOO2", "foo2"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnvFrom("foo", "configmap", false))))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO1=foo1`))
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO2=foo2`))
})
It("podman play kube deployment uses variable from config map", func() {
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO", false))))
deployment := getDeployment(withPod(pod))
deploymentYaml, err := getKubeYaml("deployment", deployment)
yamls := []string{cmYaml, deploymentYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", fmt.Sprintf("%s-%s-%s", deployment.Name, "pod-0", defaultCtrName), "--format", "'{{ .Config }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
})
It("podman play kube uses env value from configmap for HTTP API client", func() {
SkipIfRemote("cannot run in a remote setup")
address := url.URL{
Scheme: "tcp",
Host: net.JoinHostPort("localhost", randomPort()),
}
session := podmanTest.Podman([]string{
"system", "service", "--log-level=debug", "--time=0", address.String(),
})
defer session.Kill()
WaitForService(address)
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(withEnv("FOO", "", "configmap", "foo", "FOO", false))))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
podmanConnection, err := bindings.NewConnection(context.Background(), address.String())
Expect(err).ToNot(HaveOccurred())
_, err = play.Kube(podmanConnection, kubeYaml, nil)
Expect(err).ToNot(HaveOccurred())
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(ContainSubstring(`FOO=foo`))
})
})
Context("with configmap in multi-doc yaml and files", func() {
It("podman play kube uses env values from both sources", func() {
SkipIfRemote("--configmaps is not supported for remote")
fsCmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
fsCm := getConfigMap(withConfigMapName("fooFs"), withConfigMapData("FOO_FS", "fooFS"))
err := generateKubeYaml("configmap", fsCm, fsCmYamlPathname)
Expect(err).To(BeNil())
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(
withEnv("FOO_FS", "", "configmap", "fooFs", "FOO_FS", false),
withEnv("FOO", "", "configmap", "foo", "FOO", false),
)))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", fsCmYamlPathname})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(And(
ContainSubstring(`FOO=foo`),
ContainSubstring(`FOO_FS=fooFS`),
))
})
It("podman play kube uses all env values from both sources", func() {
SkipIfRemote("--configmaps is not supported for remote")
fsCmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
fsCm := getConfigMap(withConfigMapName("fooFs"),
withConfigMapData("FOO_FS_1", "fooFS1"),
withConfigMapData("FOO_FS_2", "fooFS2"))
err := generateKubeYaml("configmap", fsCm, fsCmYamlPathname)
Expect(err).To(BeNil())
cm := getConfigMap(withConfigMapName("foo"),
withConfigMapData("FOO_1", "foo1"),
withConfigMapData("FOO_2", "foo2"),
)
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(
withEnvFrom("foo", "configmap", false),
withEnvFrom("fooFs", "configmap", false),
)))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", fsCmYamlPathname})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", getCtrNameInPod(pod), "--format", "'{{ .Config.Env }}'"})
inspect.WaitWithDefaultTimeout()
Expect(inspect).Should(Exit(0))
Expect(inspect.OutputToString()).To(And(
ContainSubstring(`FOO_1=foo1`),
ContainSubstring(`FOO_2=foo2`),
ContainSubstring(`FOO_FS_1=fooFS1`),
ContainSubstring(`FOO_FS_2=fooFS2`),
))
})
It("podman play kube reports error when the same configmap name is present in both sources", func() {
SkipIfRemote("--configmaps is not supported for remote")
fsCmYamlPathname := filepath.Join(podmanTest.TempDir, "foo-cm.yaml")
fsCm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "fooFS"))
err := generateKubeYaml("configmap", fsCm, fsCmYamlPathname)
Expect(err).To(BeNil())
cm := getConfigMap(withConfigMapName("foo"), withConfigMapData("FOO", "foo"))
cmYaml, err := getKubeYaml("configmap", cm)
Expect(err).To(BeNil())
pod := getPod(withCtr(getCtr(
withEnv("FOO", "", "configmap", "foo", "FOO", false),
)))
podYaml, err := getKubeYaml("pod", pod)
Expect(err).To(BeNil())
yamls := []string{cmYaml, podYaml}
err = generateMultiDocKubeYaml(yamls, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml, "--configmap", fsCmYamlPathname})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(125))
Expect(kube.ErrorToString()).To(ContainSubstring("ambiguous configuration: the same config map foo is present in YAML and in --configmaps"))
})
})
})