summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUrvashi Mohnani <umohnani@redhat.com>2021-11-28 19:32:10 -0500
committerUrvashi Mohnani <umohnani@redhat.com>2021-12-02 14:48:04 -0500
commit7d331d35ddf5e511bd474505b675191d3f949dc0 (patch)
tree36c0bc83bb0ff98aade05a9546d6a886843348dd
parent7324d94648a9987b0de2dc95cf1b6fbc20592532 (diff)
downloadpodman-7d331d35ddf5e511bd474505b675191d3f949dc0.tar.gz
podman-7d331d35ddf5e511bd474505b675191d3f949dc0.tar.bz2
podman-7d331d35ddf5e511bd474505b675191d3f949dc0.zip
Add support for configmap volumes to play kube
If the k8s yaml has volumes from a configmap, play kube will now create a volume based on the data from the configmap and volume source and set it to the right path in the container accordingly. Add tests for this and update some test for ENV from configmap. Signed-off-by: Urvashi Mohnani <umohnani@redhat.com>
-rw-r--r--pkg/domain/infra/abi/play.go71
-rw-r--r--pkg/specgen/generate/kube/kube.go12
-rw-r--r--pkg/specgen/generate/kube/volume.go64
-rw-r--r--test/e2e/play_kube_test.go120
4 files changed, 230 insertions, 37 deletions
diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go
index bdf22cf0c..ab52fad64 100644
--- a/pkg/domain/infra/abi/play.go
+++ b/pkg/domain/infra/abi/play.go
@@ -239,27 +239,6 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
return nil, err
}
podSpec := entities.PodSpec{PodSpecGen: *p}
- volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes)
- if err != nil {
- return nil, err
- }
-
- seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)
- if err != nil {
- return nil, err
- }
-
- var ctrRestartPolicy string
- switch podYAML.Spec.RestartPolicy {
- case v1.RestartPolicyAlways:
- ctrRestartPolicy = define.RestartPolicyAlways
- case v1.RestartPolicyOnFailure:
- ctrRestartPolicy = define.RestartPolicyOnFailure
- case v1.RestartPolicyNever:
- ctrRestartPolicy = define.RestartPolicyNo
- default: // Default to Always
- ctrRestartPolicy = define.RestartPolicyAlways
- }
configMapIndex := make(map[string]struct{})
for _, configMap := range configMaps {
@@ -284,6 +263,56 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
configMaps = append(configMaps, cm)
}
+ volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes, configMaps)
+ if err != nil {
+ return nil, err
+ }
+
+ // Go through the volumes and create a podman volume for all volumes that have been
+ // defined by a configmap
+ for _, v := range volumes {
+ if v.Type == kube.KubeVolumeTypeConfigMap && !v.Optional {
+ vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source))
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot create a local volume for volume from configmap %q", v.Source)
+ }
+ mountPoint, err := vol.MountPoint()
+ if err != nil || mountPoint == "" {
+ return nil, errors.Wrapf(err, "unable to get mountpoint of volume %q", vol.Name())
+ }
+ // Create files and add data to the volume mountpoint based on the Items in the volume
+ for k, v := range v.Items {
+ dataPath := filepath.Join(mountPoint, k)
+ f, err := os.Create(dataPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot create file %q at volume mountpoint %q", k, mountPoint)
+ }
+ defer f.Close()
+ _, err = f.WriteString(v)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ }
+
+ seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)
+ if err != nil {
+ return nil, err
+ }
+
+ var ctrRestartPolicy string
+ switch podYAML.Spec.RestartPolicy {
+ case v1.RestartPolicyAlways:
+ ctrRestartPolicy = define.RestartPolicyAlways
+ case v1.RestartPolicyOnFailure:
+ ctrRestartPolicy = define.RestartPolicyOnFailure
+ case v1.RestartPolicyNever:
+ ctrRestartPolicy = define.RestartPolicyNo
+ default: // Default to Always
+ ctrRestartPolicy = define.RestartPolicyAlways
+ }
+
if podOpt.Infra {
infraImage := util.DefaultContainerConfig().Engine.InfraImage
infraOptions := entities.NewInfraContainerCreateOptions()
diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go
index c502a6e62..6d9f598c9 100644
--- a/pkg/specgen/generate/kube/kube.go
+++ b/pkg/specgen/generate/kube/kube.go
@@ -310,6 +310,11 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
}
+ // Skip if the volume is optional. This means that a configmap for a configmap volume was not found but it was
+ // optional so we can move on without throwing an error
+ if exists && volumeSource.Optional {
+ continue
+ }
dest, options, err := parseMountPath(volume.MountPath, volume.ReadOnly)
if err != nil {
@@ -341,6 +346,13 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
Options: options,
}
s.Volumes = append(s.Volumes, &namedVolume)
+ case KubeVolumeTypeConfigMap:
+ cmVolume := specgen.NamedVolume{
+ Dest: volume.MountPath,
+ Name: volumeSource.Source,
+ Options: options,
+ }
+ s.Volumes = append(s.Volumes, &cmVolume)
default:
return nil, errors.Errorf("Unsupported volume source type")
}
diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go
index a8042b532..76ec0a390 100644
--- a/pkg/specgen/generate/kube/volume.go
+++ b/pkg/specgen/generate/kube/volume.go
@@ -23,6 +23,7 @@ type KubeVolumeType int
const (
KubeVolumeTypeBindMount KubeVolumeType = iota
KubeVolumeTypeNamed KubeVolumeType = iota
+ KubeVolumeTypeConfigMap KubeVolumeType = iota
)
// nolint:golint
@@ -31,6 +32,14 @@ type KubeVolume struct {
Type KubeVolumeType
// Path for bind mount or volume name for named volume
Source string
+ // Items to add to a named volume created where the key is the file name and the value is the data
+ // This is only used when there are volumes in the yaml that refer to a configmap
+ // Example: if configmap has data "SPECIAL_LEVEL: very" then the file name is "SPECIAL_LEVEL" and the
+ // data in that file is "very".
+ Items map[string]string
+ // If the volume is optional, we can move on if it is not found
+ // Only used when there are volumes in a yaml that refer to a configmap
+ Optional bool
}
// Create a KubeVolume from an HostPathVolumeSource
@@ -98,23 +107,64 @@ func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource
}, nil
}
+func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
+ var configMap *v1.ConfigMap
+ kv := &KubeVolume{Type: KubeVolumeTypeConfigMap, Items: map[string]string{}}
+ for _, cm := range configMaps {
+ if cm.Name == configMapVolumeSource.Name {
+ matchedCM := cm
+ // Set the source to the config map name
+ kv.Source = cm.Name
+ configMap = &matchedCM
+ break
+ }
+ }
+
+ if configMap == nil {
+ // If the volumeSource was optional, move on even if a matching configmap wasn't found
+ if *configMapVolumeSource.Optional {
+ kv.Source = configMapVolumeSource.Name
+ kv.Optional = *configMapVolumeSource.Optional
+ return kv, nil
+ }
+ return nil, errors.Errorf("no such ConfigMap %q", configMapVolumeSource.Name)
+ }
+
+ // If there are Items specified in the volumeSource, that overwrites the Data from the configmap
+ if len(configMapVolumeSource.Items) > 0 {
+ for _, item := range configMapVolumeSource.Items {
+ if val, ok := configMap.Data[item.Key]; ok {
+ kv.Items[item.Path] = val
+ }
+ }
+ } else {
+ for k, v := range configMap.Data {
+ kv.Items[k] = v
+ }
+ }
+ return kv, nil
+}
+
// Create a KubeVolume from one of the supported VolumeSource
-func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) {
- if volumeSource.HostPath != nil {
+func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
+ switch {
+ case volumeSource.HostPath != nil:
return VolumeFromHostPath(volumeSource.HostPath)
- } else if volumeSource.PersistentVolumeClaim != nil {
+ case volumeSource.PersistentVolumeClaim != nil:
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
- } else {
- return nil, errors.Errorf("HostPath and PersistentVolumeClaim are currently the only supported VolumeSource")
+ case volumeSource.ConfigMap != nil:
+ return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps)
+ default:
+ return nil, errors.Errorf("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource")
}
}
// Create a map of volume name to KubeVolume
-func InitializeVolumes(specVolumes []v1.Volume) (map[string]*KubeVolume, error) {
+func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap) (map[string]*KubeVolume, error) {
volumes := make(map[string]*KubeVolume)
for _, specVolume := range specVolumes {
- volume, err := VolumeFromSource(specVolume.VolumeSource)
+ volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps)
if err != nil {
return nil, errors.Wrapf(err, "failed to create volume %q", specVolume.Name)
}
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 1a3b5f8df..97e97baa4 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -4,8 +4,6 @@ import (
"bytes"
"context"
"fmt"
- "github.com/containers/podman/v3/pkg/bindings"
- "github.com/containers/podman/v3/pkg/bindings/play"
"io/ioutil"
"net"
"net/url"
@@ -17,6 +15,8 @@ import (
"time"
"github.com/containers/podman/v3/libpod/define"
+ "github.com/containers/podman/v3/pkg/bindings"
+ "github.com/containers/podman/v3/pkg/bindings/play"
"github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils"
"github.com/containers/storage/pkg/stringid"
@@ -380,6 +380,18 @@ spec:
persistentVolumeClaim:
claimName: {{ .PersistentVolumeClaim.ClaimName }}
{{- end }}
+ {{- if (eq .VolumeType "ConfigMap") }}
+ configMap:
+ name: {{ .ConfigMap.Name }}
+ optional: {{ .ConfigMap.Optional }}
+ {{- with .ConfigMap.Items }}
+ items:
+ {{- range . }}
+ - key: {{ .key }}
+ path: {{ .path }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
{{ end }}
{{ end }}
status: {}
@@ -619,14 +631,14 @@ func createSecret(podmanTest *PodmanTestIntegration, name string, value []byte)
Expect(secret).Should(Exit(0))
}
-// ConfigMap describes the options a kube yaml can be configured at configmap level
-type ConfigMap struct {
+// CM describes the options a kube yaml can be configured at configmap level
+type CM struct {
Name string
Data map[string]string
}
-func getConfigMap(options ...configMapOption) *ConfigMap {
- cm := ConfigMap{
+func getConfigMap(options ...configMapOption) *CM {
+ cm := CM{
Name: defaultConfigMapName,
Data: map[string]string{},
}
@@ -638,16 +650,16 @@ func getConfigMap(options ...configMapOption) *ConfigMap {
return &cm
}
-type configMapOption func(*ConfigMap)
+type configMapOption func(*CM)
func withConfigMapName(name string) configMapOption {
- return func(configmap *ConfigMap) {
+ return func(configmap *CM) {
configmap.Name = name
}
}
func withConfigMapData(k, v string) configMapOption {
- return func(configmap *ConfigMap) {
+ return func(configmap *CM) {
configmap.Data[k] = v
}
}
@@ -1053,11 +1065,18 @@ type PersistentVolumeClaim struct {
ClaimName string
}
+type ConfigMap struct {
+ Name string
+ Items []map[string]string
+ Optional bool
+}
+
type Volume struct {
VolumeType string
Name string
HostPath
PersistentVolumeClaim
+ ConfigMap
}
// getHostPathVolume takes a type and a location for a HostPath
@@ -1085,6 +1104,20 @@ func getPersistentVolumeClaimVolume(vName string) *Volume {
}
}
+// getConfigMap returns a new ConfigMap Volume given the name and items
+// of the ConfigMap.
+func getConfigMapVolume(vName string, items []map[string]string, optional bool) *Volume {
+ return &Volume{
+ VolumeType: "ConfigMap",
+ Name: defaultVolName,
+ ConfigMap: ConfigMap{
+ Name: vName,
+ Items: items,
+ Optional: optional,
+ },
+ }
+}
+
type Env struct {
Name string
Value string
@@ -2317,6 +2350,75 @@ VOLUME %s`, ALPINE, hostPathDir+"/")
Expect(inspect.OutputToString()).To(Equal(correct))
})
+ It("podman play kube ConfigMap volume with no items", func() {
+ volumeName := "cmVol"
+ cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("FOO", "foobar"))
+ cmYaml, err := getKubeYaml("configmap", cm)
+ Expect(err).To(BeNil())
+
+ ctr := getCtr(withVolumeMount("/test", false), withImage(BB))
+ pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, false)), withCtr(ctr))
+ 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))
+
+ cmData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"})
+ cmData.WaitWithDefaultTimeout()
+ Expect(cmData).Should(Exit(0))
+ Expect(cmData.OutputToString()).To(Equal("foobar"))
+ })
+
+ It("podman play kube ConfigMap volume with items", func() {
+ volumeName := "cmVol"
+ cm := getConfigMap(withConfigMapName(volumeName), withConfigMapData("FOO", "foobar"))
+ cmYaml, err := getKubeYaml("configmap", cm)
+ Expect(err).To(BeNil())
+ volumeContents := []map[string]string{{
+ "key": "FOO",
+ "path": "BAR",
+ }}
+
+ ctr := getCtr(withVolumeMount("/test", false), withImage(BB))
+ pod := getPod(withVolume(getConfigMapVolume(volumeName, volumeContents, false)), withCtr(ctr))
+ 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))
+
+ cmData := podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/BAR"})
+ cmData.WaitWithDefaultTimeout()
+ Expect(cmData).Should(Exit(0))
+ Expect(cmData.OutputToString()).To(Equal("foobar"))
+
+ cmData = podmanTest.Podman([]string{"exec", getCtrNameInPod(pod), "cat", "/test/FOO"})
+ cmData.WaitWithDefaultTimeout()
+ Expect(cmData).Should(Not(Exit(0)))
+ })
+
+ It("podman play kube with a missing optional ConfigMap volume", func() {
+ volumeName := "cmVol"
+
+ ctr := getCtr(withVolumeMount("/test", false), withImage(BB))
+ pod := getPod(withVolume(getConfigMapVolume(volumeName, []map[string]string{}, true)), withCtr(ctr))
+ err = generateKubeYaml("pod", pod, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+ })
+
It("podman play kube applies labels to pods", func() {
var numReplicas int32 = 5
expectedLabelKey := "key1"