diff --git a/docs/source/markdown/podman-kube-play.1.md.in b/docs/source/markdown/podman-kube-play.1.md.in index e9f847abc9..bcd5687ca6 100644 --- a/docs/source/markdown/podman-kube-play.1.md.in +++ b/docs/source/markdown/podman-kube-play.1.md.in @@ -21,7 +21,7 @@ Currently, the supported Kubernetes kinds are: `Kubernetes Pods or Deployments` -Only two volume types are supported by kube play, the *hostPath* and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. +Only three volume types are supported by kube play, the *hostPath*, *emptyDir*, and *persistentVolumeClaim* volume types. For the *hostPath* volume type, only the *default (empty)*, *DirectoryOrCreate*, *Directory*, *FileOrCreate*, *File*, *Socket*, *CharDevice* and *BlockDevice* subtypes are supported. Podman interprets the value of *hostPath* *path* as a file path when it contains at least one forward slash, otherwise Podman treats the value as the name of a named volume. When using a *persistentVolumeClaim*, the value for *claimName* is the name for the Podman named volume. When using an *emptyDir* volume, podman creates an anonymous volume that is attached the containers running inside the pod and is deleted once the pod is removed. Note: When playing a kube YAML with init containers, the init container will be created with init type value `once`. To change the default type, use the `io.podman.annotations.init.container.type` annotation to set the type to `always`. diff --git a/libpod/container.go b/libpod/container.go index 6c05b10848..44a8669fd6 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -237,6 +237,9 @@ type ContainerNamedVolume struct { Dest string `json:"dest"` // Options are fstab style mount options Options []string `json:"options,omitempty"` + // IsAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + IsAnonymous bool `json:"setAnonymous,omitempty"` } // ContainerOverlayVolume is a overlay volume that will be mounted into the diff --git a/libpod/options.go b/libpod/options.go index d317410949..56d5265d23 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1413,9 +1413,10 @@ func WithNamedVolumes(volumes []*ContainerNamedVolume) CtrCreateOption { } ctr.config.NamedVolumes = append(ctr.config.NamedVolumes, &ContainerNamedVolume{ - Name: vol.Name, - Dest: vol.Dest, - Options: mountOpts, + Name: vol.Name, + Dest: vol.Dest, + Options: mountOpts, + IsAnonymous: vol.IsAnonymous, }) } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 703ae5cbe6..b43114fab6 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -474,6 +474,11 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai return nil, fmt.Errorf("error retrieving named volume %s for new container: %w", vol.Name, err) } } + if vol.IsAnonymous { + // If SetAnonymous is true, make this an anonymous volume + // this is needed for emptyDir volumes from kube yamls + isAnonymous = true + } logrus.Debugf("Creating new volume %s for container", vol.Name) @@ -814,11 +819,11 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force, remo // Ignore error, since podman will report original error volumesFrom, _ := c.volumesFrom() if len(volumesFrom) > 0 { - logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v) + logrus.Debugf("Cleaning up volume not possible since volume is in use (%s)", v.Name) continue } } - logrus.Errorf("Cleaning up volume (%s): %v", v, err) + logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err) } } } @@ -968,7 +973,7 @@ func (r *Runtime) evictContainer(ctx context.Context, idOrName string, removeVol continue } if err := r.removeVolume(ctx, volume, false, timeout, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed { - logrus.Errorf("Cleaning up volume (%s): %v", v, err) + logrus.Errorf("Cleaning up volume (%s): %v", v.Name, err) } } } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index faa89cc269..6ea20a4f28 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -436,7 +436,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } // Go through the volumes and create a podman volume for all volumes that have been - // defined by a configmap + // defined by a configmap or secret for _, v := range volumes { if (v.Type == kube.KubeVolumeTypeConfigMap || v.Type == kube.KubeVolumeTypeSecret) && !v.Optional { vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVolumeName(v.Source)) diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index 384965769b..d471788782 100644 --- a/pkg/k8s.io/api/core/v1/types.go +++ b/pkg/k8s.io/api/core/v1/types.go @@ -58,6 +58,10 @@ type VolumeSource struct { ConfigMap *ConfigMapVolumeSource `json:"configMap,omitempty"` // Secret represents a secret that should be mounted as a volume Secret *SecretVolumeSource `json:"secret,omitempty"` + // emptyDir represents a temporary directory that shares a pod's lifetime. + // More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir + // +optional + EmptyDir *EmptyDirVolumeSource `json:"emptyDir,omitempty"` } // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index e9cec28739..819800176e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -387,9 +387,10 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l var vols []*libpod.ContainerNamedVolume for _, v := range volumes { vols = append(vols, &libpod.ContainerNamedVolume{ - Name: v.Name, - Dest: v.Dest, - Options: v.Options, + Name: v.Name, + Dest: v.Dest, + Options: v.Options, + IsAnonymous: v.IsAnonymous, }) } options = append(options, libpod.WithNamedVolumes(vols)) diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index e9abf419b7..375b719d38 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -406,8 +406,15 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener Name: volumeSource.Source, Options: options, } - s.Volumes = append(s.Volumes, &secretVolume) + case KubeVolumeTypeEmptyDir: + emptyDirVolume := specgen.NamedVolume{ + Dest: volume.MountPath, + Name: volumeSource.Source, + Options: options, + IsAnonymous: true, + } + s.Volumes = append(s.Volumes, &emptyDirVolume) default: return nil, errors.New("unsupported volume source type") } diff --git a/pkg/specgen/generate/kube/volume.go b/pkg/specgen/generate/kube/volume.go index c12adadd80..230521ec6a 100644 --- a/pkg/specgen/generate/kube/volume.go +++ b/pkg/specgen/generate/kube/volume.go @@ -32,6 +32,7 @@ const ( KubeVolumeTypeBlockDevice KubeVolumeTypeCharDevice KubeVolumeTypeSecret + KubeVolumeTypeEmptyDir ) //nolint:revive @@ -219,8 +220,13 @@ func VolumeFromConfigMap(configMapVolumeSource *v1.ConfigMapVolumeSource, config return kv, nil } +// Create a kubeVolume for an emptyDir volume +func VolumeFromEmptyDir(emptyDirVolumeSource *v1.EmptyDirVolumeSource, name string) (*KubeVolume, error) { + return &KubeVolume{Type: KubeVolumeTypeEmptyDir, Source: name}, nil +} + // Create a KubeVolume from one of the supported VolumeSource -func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager) (*KubeVolume, error) { +func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, secretsManager *secrets.SecretsManager, volName string) (*KubeVolume, error) { switch { case volumeSource.HostPath != nil: return VolumeFromHostPath(volumeSource.HostPath) @@ -230,8 +236,10 @@ func VolumeFromSource(volumeSource v1.VolumeSource, configMaps []v1.ConfigMap, s return VolumeFromConfigMap(volumeSource.ConfigMap, configMaps) case volumeSource.Secret != nil: return VolumeFromSecret(volumeSource.Secret, secretsManager) + case volumeSource.EmptyDir != nil: + return VolumeFromEmptyDir(volumeSource.EmptyDir, volName) default: - return nil, errors.New("HostPath, ConfigMap, and PersistentVolumeClaim are currently the only supported VolumeSource") + return nil, errors.New("HostPath, ConfigMap, EmptyDir, and PersistentVolumeClaim are currently the only supported VolumeSource") } } @@ -240,7 +248,7 @@ func InitializeVolumes(specVolumes []v1.Volume, configMaps []v1.ConfigMap, secre volumes := make(map[string]*KubeVolume) for _, specVolume := range specVolumes { - volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager) + volume, err := VolumeFromSource(specVolume.VolumeSource, configMaps, secretsManager, specVolume.Name) if err != nil { return nil, fmt.Errorf("failed to create volume %q: %w", specVolume.Name, err) } diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index 84de4fdd18..e70ed5b135 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -23,6 +23,9 @@ type NamedVolume struct { Dest string // Options are options that the named volume will be mounted with. Options []string + // IsAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + IsAnonymous bool } // OverlayVolume holds information about a overlay volume that will be mounted into diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go index 7d3a2224c1..d1eb960cd7 100644 --- a/test/e2e/play_kube_test.go +++ b/test/e2e/play_kube_test.go @@ -509,6 +509,9 @@ spec: volumes: {{ range . }} - name: {{ .Name }} + {{- if (eq .VolumeType "EmptyDir") }} + emptyDir: {} + {{- end }} {{- if (eq .VolumeType "HostPath") }} hostPath: path: {{ .HostPath.Path }} @@ -1242,12 +1245,15 @@ type ConfigMap struct { Optional bool } +type EmptyDir struct{} + type Volume struct { VolumeType string Name string HostPath PersistentVolumeClaim ConfigMap + EmptyDir } // getHostPathVolume takes a type and a location for a HostPath @@ -1289,6 +1295,14 @@ func getConfigMapVolume(vName string, items []map[string]string, optional bool) } } +func getEmptyDirVolume() *Volume { + return &Volume{ + VolumeType: "EmptyDir", + Name: defaultVolName, + EmptyDir: EmptyDir{}, + } +} + type Env struct { Name string Value string @@ -2762,6 +2776,43 @@ VOLUME %s`, ALPINE, hostPathDir+"/") Expect(kube).Should(Exit(0)) }) + It("podman play kube with emptyDir volume", func() { + podName := "test-pod" + ctrName1 := "vol-test-ctr" + ctrName2 := "vol-test-ctr-2" + ctr1 := getCtr(withVolumeMount("/test-emptydir", false), withImage(BB), withName(ctrName1)) + ctr2 := getCtr(withVolumeMount("/test-emptydir-2", false), withImage(BB), withName(ctrName2)) + pod := getPod(withPodName(podName), withVolume(getEmptyDirVolume()), withCtr(ctr1), withCtr(ctr2)) + err = generateKubeYaml("pod", pod, kubeYaml) + Expect(err).To(BeNil()) + + kube := podmanTest.Podman([]string{"play", "kube", kubeYaml}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + emptyDirCheck1 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName1, "ls", "/test-emptydir"}) + emptyDirCheck1.WaitWithDefaultTimeout() + Expect(emptyDirCheck1).Should(Exit(0)) + + emptyDirCheck2 := podmanTest.Podman([]string{"exec", podName + "-" + ctrName2, "ls", "/test-emptydir-2"}) + emptyDirCheck2.WaitWithDefaultTimeout() + Expect(emptyDirCheck2).Should(Exit(0)) + + volList1 := podmanTest.Podman([]string{"volume", "ls", "-q"}) + volList1.WaitWithDefaultTimeout() + Expect(volList1).Should(Exit(0)) + Expect(volList1.OutputToString()).To(Equal(defaultVolName)) + + remove := podmanTest.Podman([]string{"pod", "rm", "-f", podName}) + remove.WaitWithDefaultTimeout() + Expect(remove).Should(Exit(0)) + + volList2 := podmanTest.Podman([]string{"volume", "ls", "-q"}) + volList2.WaitWithDefaultTimeout() + Expect(volList2).Should(Exit(0)) + Expect(volList2.OutputToString()).To(Equal("")) + }) + It("podman play kube applies labels to pods", func() { var numReplicas int32 = 5 expectedLabelKey := "key1"