diff options
-rw-r--r-- | pkg/domain/infra/abi/play.go | 71 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/kube.go | 12 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/volume.go | 64 | ||||
-rw-r--r-- | test/e2e/play_kube_test.go | 120 |
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" |