Prepare support in kube play for other volume types than hostPath

Replace the simple map of names to paths with a map of names to a struct
to allow passing more parameters. Also move the code to parse the volumes
to its own file to avoid making the playKubePod() function overly complex.
Finally rework the kube volumes test to also be ready to support more
volume types.

Signed-off-by: Alban Bedel <albeu@free.fr>
This commit is contained in:
Alban Bedel
2020-11-18 20:42:37 +01:00
parent ad2439264d
commit b84304da5e
4 changed files with 163 additions and 96 deletions

View File

@ -8,7 +8,6 @@ import (
"os"
"strings"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/image"
@ -24,13 +23,6 @@ import (
v1 "k8s.io/api/core/v1"
)
const (
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeDirectoryPermission = 0755
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeFilePermission = 0644
)
func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
var (
kubeObject v1.ObjectReference
@ -168,62 +160,9 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
// map from name to mount point
volumes := make(map[string]string)
for _, volume := range podYAML.Spec.Volumes {
hostPath := volume.VolumeSource.HostPath
if hostPath == nil {
return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
}
if hostPath.Type != nil {
switch *hostPath.Type {
case v1.HostPathDirectoryOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil {
return nil, err
}
}
// Label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path)
}
case v1.HostPathFileOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission)
if err != nil {
return nil, errors.Wrap(err, "error creating HostPath")
}
if err := f.Close(); err != nil {
logrus.Warnf("Error in closing newly created HostPath file: %v", err)
}
}
// unconditionally label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path)
}
case v1.HostPathSocket:
st, err := os.Stat(hostPath.Path)
if err != nil {
return nil, errors.Wrap(err, "error checking HostPathSocket")
}
if st.Mode()&os.ModeSocket != os.ModeSocket {
return nil, errors.Errorf("error checking HostPathSocket: path %s is not a socket", hostPath.Path)
}
case v1.HostPathDirectory:
case v1.HostPathFile:
case v1.HostPathUnset:
// do nothing here because we will verify the path exists in validateVolumeHostDir
break
default:
return nil, errors.Errorf("Invalid HostPath type %v", hostPath.Type)
}
}
if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error in parsing HostPath in YAML")
}
volumes[volume.Name] = hostPath.Path
volumes, err := kube.InitializeVolumes(podYAML.Spec.Volumes)
if err != nil {
return nil, err
}
seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)

View File

@ -47,7 +47,7 @@ func ToPodGen(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec)
return p, nil
}
func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]string, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) {
func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newImage *image.Image, volumes map[string]*KubeVolume, podID, podName, infraID string, configMaps []v1.ConfigMap, seccompPaths *KubeSeccompPaths, restartPolicy string) (*specgen.SpecGenerator, error) {
s := specgen.NewSpecGenerator(iid, false)
// podName should be non-empty for Deployment objects to be able to create
@ -163,22 +163,27 @@ func ToSpecGen(ctx context.Context, containerYAML v1.Container, iid string, newI
s.Env = envs
for _, volume := range containerYAML.VolumeMounts {
hostPath, exists := volumes[volume.Name]
volumeSource, exists := volumes[volume.Name]
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
}
if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
switch volumeSource.Type {
case KubeVolumeTypeBindMount:
if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
}
mount := spec.Mount{
Destination: volume.MountPath,
Source: volumeSource.Source,
Type: "bind",
}
if volume.ReadOnly {
mount.Options = []string{"ro"}
}
s.Mounts = append(s.Mounts, mount)
default:
return nil, errors.Errorf("Unsupported volume source type")
}
mount := spec.Mount{
Destination: volume.MountPath,
Source: hostPath,
Type: "bind",
}
if volume.ReadOnly {
mount.Options = []string{"ro"}
}
s.Mounts = append(s.Mounts, mount)
}
s.RestartPolicy = restartPolicy

View File

@ -0,0 +1,113 @@
package kube
import (
"os"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/podman/v2/libpod"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
)
const (
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeDirectoryPermission = 0755
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeFilePermission = 0644
)
type KubeVolumeType int
const (
KubeVolumeTypeBindMount KubeVolumeType = iota
)
type KubeVolume struct {
// Type of volume to create
Type KubeVolumeType
// Path for bind mount
Source string
}
// Create a KubeVolume from an HostPathVolumeSource
func VolumeFromHostPath(hostPath *v1.HostPathVolumeSource) (*KubeVolume, error) {
if hostPath.Type != nil {
switch *hostPath.Type {
case v1.HostPathDirectoryOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil {
return nil, err
}
}
// Label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path)
}
case v1.HostPathFileOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission)
if err != nil {
return nil, errors.Wrap(err, "error creating HostPath")
}
if err := f.Close(); err != nil {
logrus.Warnf("Error in closing newly created HostPath file: %v", err)
}
}
// unconditionally label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error giving %s a label", hostPath.Path)
}
case v1.HostPathSocket:
st, err := os.Stat(hostPath.Path)
if err != nil {
return nil, errors.Wrap(err, "error checking HostPathSocket")
}
if st.Mode()&os.ModeSocket != os.ModeSocket {
return nil, errors.Errorf("error checking HostPathSocket: path %s is not a socket", hostPath.Path)
}
case v1.HostPathDirectory:
case v1.HostPathFile:
case v1.HostPathUnset:
// do nothing here because we will verify the path exists in validateVolumeHostDir
break
default:
return nil, errors.Errorf("Invalid HostPath type %v", hostPath.Type)
}
}
if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "error in parsing HostPath in YAML")
}
return &KubeVolume{
Type: KubeVolumeTypeBindMount,
Source: hostPath.Path,
}, 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 {
return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
}
}
// Create a map of volume name to KubeVolume
func InitializeVolumes(specVolumes []v1.Volume) (map[string]*KubeVolume, error) {
volumes := make(map[string]*KubeVolume)
for _, specVolume := range specVolumes {
volume, err := VolumeFromSource(specVolume.VolumeSource)
if err != nil {
return nil, err
}
volumes[specVolume.Name] = volume
}
return volumes, nil
}

View File

@ -164,9 +164,11 @@ spec:
volumes:
{{ range . }}
- name: {{ .Name }}
{{- if (eq .VolumeType "HostPath") }}
hostPath:
path: {{ .Path }}
type: {{ .Type }}
path: {{ .HostPath.Path }}
type: {{ .HostPath.Type }}
{{- end }}
{{ end }}
{{ end }}
status: {}
@ -692,19 +694,27 @@ func getCtrNameInPod(pod *Pod) string {
return fmt.Sprintf("%s-%s", pod.Name, defaultCtrName)
}
type Volume struct {
Name string
type HostPath struct {
Path string
Type string
}
// getVolume takes a type and a location for a volume
// giving it a default name of volName
func getVolume(vType, vPath string) *Volume {
type Volume struct {
VolumeType string
Name string
HostPath
}
// getHostPathVolume takes a type and a location for a HostPath
// volume giving it a default name of volName
func getHostPathVolume(vType, vPath string) *Volume {
return &Volume{
Name: defaultVolName,
Path: vPath,
Type: vType,
VolumeType: "HostPath",
Name: defaultVolName,
HostPath: HostPath{
Path: vPath,
Type: vType,
},
}
}
@ -1257,7 +1267,7 @@ spec:
It("podman play kube test with non-existent empty HostPath type volume", func() {
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume(`""`, hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume(`""`, hostPathLocation)))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1272,7 +1282,7 @@ spec:
Expect(err).To(BeNil())
f.Close()
pod := getPod(withVolume(getVolume(`""`, hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume(`""`, hostPathLocation)))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1284,7 +1294,7 @@ spec:
It("podman play kube test with non-existent File HostPath type volume", func() {
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("File", hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation)))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1299,7 +1309,7 @@ spec:
Expect(err).To(BeNil())
f.Close()
pod := getPod(withVolume(getVolume("File", hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation)))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1311,7 +1321,7 @@ spec:
It("podman play kube test with FileOrCreate HostPath type volume", func() {
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("FileOrCreate", hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume("FileOrCreate", hostPathLocation)))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1327,7 +1337,7 @@ spec:
It("podman play kube test with DirectoryOrCreate HostPath type volume", func() {
hostPathLocation := filepath.Join(tempdir, "file")
pod := getPod(withVolume(getVolume("DirectoryOrCreate", hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume("DirectoryOrCreate", hostPathLocation)))
err := generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1347,7 +1357,7 @@ spec:
Expect(err).To(BeNil())
f.Close()
pod := getPod(withVolume(getVolume("Socket", hostPathLocation)))
pod := getPod(withVolume(getHostPathVolume("Socket", hostPathLocation)))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())
@ -1356,14 +1366,14 @@ spec:
Expect(kube.ExitCode()).NotTo(Equal(0))
})
It("podman play kube test with read only volume", func() {
It("podman play kube test with read only HostPath volume", func() {
hostPathLocation := filepath.Join(tempdir, "file")
f, err := os.Create(hostPathLocation)
Expect(err).To(BeNil())
f.Close()
ctr := getCtr(withVolumeMount(hostPathLocation, true), withImage(BB))
pod := getPod(withVolume(getVolume("File", hostPathLocation)), withCtr(ctr))
pod := getPod(withVolume(getHostPathVolume("File", hostPathLocation)), withCtr(ctr))
err = generateKubeYaml("pod", pod, kubeYaml)
Expect(err).To(BeNil())