summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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"