aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakub Dzon <jdzon@redhat.com>2021-11-19 18:00:14 +0100
committerJakub Dzon <jdzon@redhat.com>2021-11-23 11:40:28 +0100
commitdd80635df06c1ef5e231f06ea8ca6bd2ad9edb5a (patch)
treede12da06f3858918456baf72ceacdeb054f10f84
parent90c635fd675bf9378e0ee0cbc7b95229bf9653e4 (diff)
downloadpodman-dd80635df06c1ef5e231f06ea8ca6bd2ad9edb5a.tar.gz
podman-dd80635df06c1ef5e231f06ea8ca6bd2ad9edb5a.tar.bz2
podman-dd80635df06c1ef5e231f06ea8ca6bd2ad9edb5a.zip
Support env variables based on ConfigMaps sent in payload
Fixes #12363 Signed-off-by: Jakub Dzon <jdzon@redhat.com>
-rw-r--r--docs/source/markdown/podman-play-kube.1.md35
-rw-r--r--pkg/domain/infra/abi/play.go28
-rw-r--r--test/e2e/play_kube_test.go313
3 files changed, 370 insertions, 6 deletions
diff --git a/docs/source/markdown/podman-play-kube.1.md b/docs/source/markdown/podman-play-kube.1.md
index 2438f7ad8..2a511f4bd 100644
--- a/docs/source/markdown/podman-play-kube.1.md
+++ b/docs/source/markdown/podman-play-kube.1.md
@@ -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*
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index d2bb95f7c..bdf22cf0c 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -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)
}
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 64b46756f..1a3b5f8df 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -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"))
+ })
+ })
})