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>
This commit is contained in:
parent
7324d94648
commit
7d331d35dd
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
// Create a KubeVolume from one of the supported VolumeSource
|
||||
func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) {
|
||||
if volumeSource.HostPath != nil {
|
||||
return VolumeFromHostPath(volumeSource.HostPath)
|
||||
} else if volumeSource.PersistentVolumeClaim != nil {
|
||||
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
|
||||
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 {
|
||||
return nil, errors.Errorf("HostPath and PersistentVolumeClaim are currently the only supported VolumeSource")
|
||||
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, configMaps []v1.ConfigMap) (*KubeVolume, error) {
|
||||
switch {
|
||||
case volumeSource.HostPath != nil:
|
||||
return VolumeFromHostPath(volumeSource.HostPath)
|
||||
case volumeSource.PersistentVolumeClaim != nil:
|
||||
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -379,6 +379,18 @@ spec:
|
|||
{{- if (eq .VolumeType "PersistentVolumeClaim") }}
|
||||
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 }}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue