Skip to content

Commit

Permalink
Merge pull request containers#12440 from umohnani8/cm
Browse files Browse the repository at this point in the history
Add support for configmap volumes to play kube
  • Loading branch information
openshift-merge-robot authored Dec 3, 2021
2 parents 999fe0d + 7d331d3 commit dd109da
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 35 deletions.
71 changes: 50 additions & 21 deletions pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
}
Expand Down
64 changes: 57 additions & 7 deletions pkg/specgen/generate/kube/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type KubeVolumeType int
const (
KubeVolumeTypeBindMount KubeVolumeType = iota
KubeVolumeTypeNamed KubeVolumeType = iota
KubeVolumeTypeConfigMap KubeVolumeType = iota
)

// nolint:golint
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down
116 changes: 109 additions & 7 deletions test/e2e/play_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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{},
}
Expand All @@ -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
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit dd109da

Please sign in to comment.