mirror of https://github.com/containers/podman.git
Merge pull request #12440 from umohnani8/cm
Add support for configmap volumes to play kube
This commit is contained in:
commit
dd109daa45
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -1047,11 +1059,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
|
||||
|
@ -1079,6 +1098,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
|
||||
|
@ -2311,6 +2344,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