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
|
return nil, err
|
||||||
}
|
}
|
||||||
podSpec := entities.PodSpec{PodSpecGen: *p}
|
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{})
|
configMapIndex := make(map[string]struct{})
|
||||||
for _, configMap := range configMaps {
|
for _, configMap := range configMaps {
|
||||||
|
@ -284,6 +263,56 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||||
configMaps = append(configMaps, cm)
|
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 {
|
if podOpt.Infra {
|
||||||
infraImage := util.DefaultContainerConfig().Engine.InfraImage
|
infraImage := util.DefaultContainerConfig().Engine.InfraImage
|
||||||
infraOptions := entities.NewInfraContainerCreateOptions()
|
infraOptions := entities.NewInfraContainerCreateOptions()
|
||||||
|
|
|
@ -310,6 +310,11 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
|
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)
|
dest, options, err := parseMountPath(volume.MountPath, volume.ReadOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -341,6 +346,13 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
|
||||||
Options: options,
|
Options: options,
|
||||||
}
|
}
|
||||||
s.Volumes = append(s.Volumes, &namedVolume)
|
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:
|
default:
|
||||||
return nil, errors.Errorf("Unsupported volume source type")
|
return nil, errors.Errorf("Unsupported volume source type")
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ type KubeVolumeType int
|
||||||
const (
|
const (
|
||||||
KubeVolumeTypeBindMount KubeVolumeType = iota
|
KubeVolumeTypeBindMount KubeVolumeType = iota
|
||||||
KubeVolumeTypeNamed KubeVolumeType = iota
|
KubeVolumeTypeNamed KubeVolumeType = iota
|
||||||
|
KubeVolumeTypeConfigMap KubeVolumeType = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint:golint
|
// nolint:golint
|
||||||
|
@ -31,6 +32,14 @@ type KubeVolume struct {
|
||||||
Type KubeVolumeType
|
Type KubeVolumeType
|
||||||
// Path for bind mount or volume name for named volume
|
// Path for bind mount or volume name for named volume
|
||||||
Source string
|
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
|
// Create a KubeVolume from an HostPathVolumeSource
|
||||||
|
@ -98,23 +107,64 @@ func VolumeFromPersistentVolumeClaim(claim *v1.PersistentVolumeClaimVolumeSource
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a KubeVolume from one of the supported VolumeSource
|
func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, configMaps []v1.ConfigMap) (*KubeVolume, error) {
|
||||||
func VolumeFromSource(volumeSource v1.VolumeSource) (*KubeVolume, error) {
|
var configMap *v1.ConfigMap
|
||||||
if volumeSource.HostPath != nil {
|
kv := &KubeVolume{Type: KubeVolumeTypeConfigMap, Items: map[string]string{}}
|
||||||
return VolumeFromHostPath(volumeSource.HostPath)
|
for _, cm := range configMaps {
|
||||||
} else if volumeSource.PersistentVolumeClaim != nil {
|
if cm.Name == configMapVolumeSource.Name {
|
||||||
return VolumeFromPersistentVolumeClaim(volumeSource.PersistentVolumeClaim)
|
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 {
|
} 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
|
// 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)
|
volumes := make(map[string]*KubeVolume)
|
||||||
|
|
||||||
for _, specVolume := range specVolumes {
|
for _, specVolume := range specVolumes {
|
||||||
volume, err := VolumeFromSource(specVolume.VolumeSource)
|
volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to create volume %q", specVolume.Name)
|
return nil, errors.Wrapf(err, "failed to create volume %q", specVolume.Name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,6 +379,18 @@ spec:
|
||||||
{{- if (eq .VolumeType "PersistentVolumeClaim") }}
|
{{- if (eq .VolumeType "PersistentVolumeClaim") }}
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
claimName: {{ .PersistentVolumeClaim.ClaimName }}
|
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 }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -619,14 +631,14 @@ func createSecret(podmanTest *PodmanTestIntegration, name string, value []byte)
|
||||||
Expect(secret).Should(Exit(0))
|
Expect(secret).Should(Exit(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigMap describes the options a kube yaml can be configured at configmap level
|
// CM describes the options a kube yaml can be configured at configmap level
|
||||||
type ConfigMap struct {
|
type CM struct {
|
||||||
Name string
|
Name string
|
||||||
Data map[string]string
|
Data map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConfigMap(options ...configMapOption) *ConfigMap {
|
func getConfigMap(options ...configMapOption) *CM {
|
||||||
cm := ConfigMap{
|
cm := CM{
|
||||||
Name: defaultConfigMapName,
|
Name: defaultConfigMapName,
|
||||||
Data: map[string]string{},
|
Data: map[string]string{},
|
||||||
}
|
}
|
||||||
|
@ -638,16 +650,16 @@ func getConfigMap(options ...configMapOption) *ConfigMap {
|
||||||
return &cm
|
return &cm
|
||||||
}
|
}
|
||||||
|
|
||||||
type configMapOption func(*ConfigMap)
|
type configMapOption func(*CM)
|
||||||
|
|
||||||
func withConfigMapName(name string) configMapOption {
|
func withConfigMapName(name string) configMapOption {
|
||||||
return func(configmap *ConfigMap) {
|
return func(configmap *CM) {
|
||||||
configmap.Name = name
|
configmap.Name = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func withConfigMapData(k, v string) configMapOption {
|
func withConfigMapData(k, v string) configMapOption {
|
||||||
return func(configmap *ConfigMap) {
|
return func(configmap *CM) {
|
||||||
configmap.Data[k] = v
|
configmap.Data[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1047,11 +1059,18 @@ type PersistentVolumeClaim struct {
|
||||||
ClaimName string
|
ClaimName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ConfigMap struct {
|
||||||
|
Name string
|
||||||
|
Items []map[string]string
|
||||||
|
Optional bool
|
||||||
|
}
|
||||||
|
|
||||||
type Volume struct {
|
type Volume struct {
|
||||||
VolumeType string
|
VolumeType string
|
||||||
Name string
|
Name string
|
||||||
HostPath
|
HostPath
|
||||||
PersistentVolumeClaim
|
PersistentVolumeClaim
|
||||||
|
ConfigMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// getHostPathVolume takes a type and a location for a HostPath
|
// 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 {
|
type Env struct {
|
||||||
Name string
|
Name string
|
||||||
Value string
|
Value string
|
||||||
|
@ -2311,6 +2344,75 @@ VOLUME %s`, ALPINE, hostPathDir+"/")
|
||||||
Expect(inspect.OutputToString()).To(Equal(correct))
|
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() {
|
It("podman play kube applies labels to pods", func() {
|
||||||
var numReplicas int32 = 5
|
var numReplicas int32 = 5
|
||||||
expectedLabelKey := "key1"
|
expectedLabelKey := "key1"
|
||||||
|
|
Loading…
Reference in New Issue