diff --git a/docs/source/markdown/podman-kube-play.1.md.in b/docs/source/markdown/podman-kube-play.1.md.in index f0b404057f62..5f2aa009e7c7 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 6c05b1084802..8e1eca891331 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"` + // SetAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + SetAnonymous bool `json:"setAnonymous"` } // ContainerOverlayVolume is a overlay volume that will be mounted into the diff --git a/libpod/options.go b/libpod/options.go index d31741094998..27258efc3a89 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, + SetAnonymous: vol.SetAnonymous, }) } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 703ae5cbe633..603445250f05 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.SetAnonymous { + // 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) diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index faa89cc26915..9cc09e4a42c0 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)) @@ -470,6 +470,9 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } } } + // if v.Type == kube.KubeVolumeTypeTmpfs { + // vol, err := ic.Libpod.NewVolume(ctx, libpod.WithVol) + // } } seccompPaths, err := kube.InitializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot) diff --git a/pkg/k8s.io/api/core/v1/types.go b/pkg/k8s.io/api/core/v1/types.go index 384965769b2c..d47178878276 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 e9cec2873969..67248c669d67 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, + SetAnonymous: v.SetAnonymous, }) } options = append(options, libpod.WithNamedVolumes(vols)) diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index e9abf419b7c9..c4b14aa4ae1d 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, + SetAnonymous: 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 c12adadd80ec..230521ec6acc 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 84de4fdd18ed..1b4526aa42d0 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 + // SetAnonymous sets the named volume as anonymous even if it has a name + // This is used for emptyDir volumes from a kube yaml + SetAnonymous 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 1b4eefd45fd6..8fb7cd5f063a 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(vName string) *Volume { + return &Volume{ + VolumeType: "EmptyDir", + Name: defaultVolName, + EmptyDir: EmptyDir{}, + } +} + type Env struct { Name string Value string @@ -2762,6 +2776,44 @@ VOLUME %s`, ALPINE, hostPathDir+"/") Expect(kube).Should(Exit(0)) }) + It("podman play kube with emptyDir volume", func() { + podName := "test-pod" + volName := "emptydir-test" + 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(volName)), 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", ctrName1, "ls", "/test-emptydir"}) + emptyDirCheck1.WaitWithDefaultTimeout() + Expect(emptyDirCheck1).Should(Exit(0)) + + emptyDirCheck2 := podmanTest.Podman([]string{"exec", 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(volName)) + + 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"