mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00
Merge pull request #15979 from ygalblum/play_kube_volume_import
play kube: Allow the user to import the contents of a tar file into a volume
This commit is contained in:
@ -46,6 +46,9 @@ A Kubernetes PersistentVolumeClaim represents a Podman named volume. Only the Pe
|
|||||||
- volume.podman.io/uid
|
- volume.podman.io/uid
|
||||||
- volume.podman.io/gid
|
- volume.podman.io/gid
|
||||||
- volume.podman.io/mount-options
|
- volume.podman.io/mount-options
|
||||||
|
- volume.podman.io/import-source
|
||||||
|
|
||||||
|
Use `volume.podman.io/import-source` to import the contents of the tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) specified in the annotation's value into the created Podman volume
|
||||||
|
|
||||||
Kube play is capable of building images on the fly given the correct directory layout and Containerfiles. This
|
Kube play is capable of building images on the fly given the correct directory layout and Containerfiles. This
|
||||||
option is not available for remote clients, including Mac and Windows (excluding WSL2) machines, yet. Consider the following excerpt from a YAML file:
|
option is not available for remote clients, including Mac and Windows (excluding WSL2) machines, yet. Consider the following excerpt from a YAML file:
|
||||||
|
@ -280,3 +280,7 @@ func (v *Volume) Unmount() error {
|
|||||||
defer v.lock.Unlock()
|
defer v.lock.Unlock()
|
||||||
return v.unmount(false)
|
return v.unmount(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Volume) NeedsMount() bool {
|
||||||
|
return v.needsMount()
|
||||||
|
}
|
||||||
|
@ -93,6 +93,7 @@ func KubePlay(w http.ResponseWriter, r *http.Request) {
|
|||||||
LogOptions: query.LogOptions,
|
LogOptions: query.LogOptions,
|
||||||
StaticIPs: staticIPs,
|
StaticIPs: staticIPs,
|
||||||
StaticMACs: staticMACs,
|
StaticMACs: staticMACs,
|
||||||
|
IsRemote: true,
|
||||||
}
|
}
|
||||||
if _, found := r.URL.Query()["tlsVerify"]; found {
|
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||||
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||||
|
@ -58,6 +58,8 @@ type PlayKubeOptions struct {
|
|||||||
ServiceContainer bool
|
ServiceContainer bool
|
||||||
// Userns - define the user namespace to use.
|
// Userns - define the user namespace to use.
|
||||||
Userns string
|
Userns string
|
||||||
|
// IsRemote - was the request triggered by running podman-remote
|
||||||
|
IsRemote bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayKubePod represents a single pod and associated containers created by play kube
|
// PlayKubePod represents a single pod and associated containers created by play kube
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/containers/common/pkg/config"
|
"github.com/containers/common/pkg/config"
|
||||||
"github.com/containers/common/pkg/secrets"
|
"github.com/containers/common/pkg/secrets"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
|
"github.com/containers/podman/v4/cmd/podman/parse"
|
||||||
"github.com/containers/podman/v4/libpod"
|
"github.com/containers/podman/v4/libpod"
|
||||||
"github.com/containers/podman/v4/libpod/define"
|
"github.com/containers/podman/v4/libpod/define"
|
||||||
"github.com/containers/podman/v4/pkg/domain/entities"
|
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||||
@ -29,6 +30,7 @@ import (
|
|||||||
"github.com/containers/podman/v4/pkg/specgenutil"
|
"github.com/containers/podman/v4/pkg/specgenutil"
|
||||||
"github.com/containers/podman/v4/pkg/systemd/notifyproxy"
|
"github.com/containers/podman/v4/pkg/systemd/notifyproxy"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
|
"github.com/containers/podman/v4/utils"
|
||||||
"github.com/coreos/go-systemd/v22/daemon"
|
"github.com/coreos/go-systemd/v22/daemon"
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
@ -233,6 +235,19 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
|
|||||||
return nil, fmt.Errorf("unable to read YAML as Kube PersistentVolumeClaim: %w", err)
|
return nil, fmt.Errorf("unable to read YAML as Kube PersistentVolumeClaim: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for name, val := range options.Annotations {
|
||||||
|
if pvcYAML.Annotations == nil {
|
||||||
|
pvcYAML.Annotations = make(map[string]string)
|
||||||
|
}
|
||||||
|
pvcYAML.Annotations[name] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.IsRemote {
|
||||||
|
if _, ok := pvcYAML.Annotations[util.VolumeImportSourceAnnotation]; ok {
|
||||||
|
return nil, fmt.Errorf("importing volumes is not supported for remote requests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r, err := ic.playKubePVC(ctx, &pvcYAML)
|
r, err := ic.playKubePVC(ctx, &pvcYAML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -859,6 +874,7 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
|
|||||||
// Get pvc annotations and create remaining podman volume options if available.
|
// Get pvc annotations and create remaining podman volume options if available.
|
||||||
// These are podman volume options that do not match any of the persistent volume claim
|
// These are podman volume options that do not match any of the persistent volume claim
|
||||||
// attributes, so they can be configured using annotations since they will not affect k8s.
|
// attributes, so they can be configured using annotations since they will not affect k8s.
|
||||||
|
var importFrom string
|
||||||
for k, v := range pvcYAML.Annotations {
|
for k, v := range pvcYAML.Annotations {
|
||||||
switch k {
|
switch k {
|
||||||
case util.VolumeDriverAnnotation:
|
case util.VolumeDriverAnnotation:
|
||||||
@ -883,16 +899,45 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
|
|||||||
opts["GID"] = v
|
opts["GID"] = v
|
||||||
case util.VolumeMountOptsAnnotation:
|
case util.VolumeMountOptsAnnotation:
|
||||||
opts["o"] = v
|
opts["o"] = v
|
||||||
|
case util.VolumeImportSourceAnnotation:
|
||||||
|
importFrom = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
volOptions = append(volOptions, libpod.WithVolumeOptions(opts))
|
volOptions = append(volOptions, libpod.WithVolumeOptions(opts))
|
||||||
|
|
||||||
|
// Validate the file and open it before creating the volume for fast-fail
|
||||||
|
var tarFile *os.File
|
||||||
|
if len(importFrom) > 0 {
|
||||||
|
err := parse.ValidateFileName(importFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// open tar file
|
||||||
|
tarFile, err = os.Open(importFrom)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer tarFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
// Create volume.
|
// Create volume.
|
||||||
vol, err := ic.Libpod.NewVolume(ctx, volOptions...)
|
vol, err := ic.Libpod.NewVolume(ctx, volOptions...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tarFile != nil {
|
||||||
|
err = ic.importVolume(ctx, vol, tarFile)
|
||||||
|
if err != nil {
|
||||||
|
// Remove the volume to avoid partial success
|
||||||
|
if rmErr := ic.Libpod.RemoveVolume(ctx, vol, true, nil); rmErr != nil {
|
||||||
|
logrus.Debug(rmErr)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
report.Volumes = append(report.Volumes, entities.PlayKubeVolume{
|
report.Volumes = append(report.Volumes, entities.PlayKubeVolume{
|
||||||
Name: vol.Name(),
|
Name: vol.Name(),
|
||||||
})
|
})
|
||||||
@ -900,6 +945,42 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
|
|||||||
return &report, nil
|
return &report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ic *ContainerEngine) importVolume(ctx context.Context, vol *libpod.Volume, tarFile *os.File) error {
|
||||||
|
volumeConfig, err := vol.Config()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
mountPoint := volumeConfig.MountPoint
|
||||||
|
if len(mountPoint) == 0 {
|
||||||
|
return errors.New("volume is not mounted anywhere on host")
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := volumeConfig.Driver
|
||||||
|
volumeOptions := volumeConfig.Options
|
||||||
|
volumeMountStatus, err := ic.VolumeMounted(ctx, vol.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if volume needs a mount and export only if volume is mounted
|
||||||
|
if vol.NeedsMount() && !volumeMountStatus.Value {
|
||||||
|
return fmt.Errorf("volume needs to be mounted but is not mounted on %s", mountPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if volume is using `local` driver and has mount options type other than tmpfs
|
||||||
|
if len(driver) == 0 || driver == define.VolumeDriverLocal {
|
||||||
|
if mountOptionType, ok := volumeOptions["type"]; ok {
|
||||||
|
if mountOptionType != "tmpfs" && !volumeMountStatus.Value {
|
||||||
|
return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dont care if volume is mounted or not we are gonna import everything to mountPoint
|
||||||
|
return utils.UntarToFileSystem(mountPoint, tarFile, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag
|
// readConfigMapFromFile returns a kubernetes configMap obtained from --configmap flag
|
||||||
func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
|
func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
|
||||||
var cm v1.ConfigMap
|
var cm v1.ConfigMap
|
||||||
|
@ -13,4 +13,6 @@ const (
|
|||||||
VolumeGIDAnnotation = "volume.podman.io/gid"
|
VolumeGIDAnnotation = "volume.podman.io/gid"
|
||||||
// Kube annotation for podman volume mount options.
|
// Kube annotation for podman volume mount options.
|
||||||
VolumeMountOptsAnnotation = "volume.podman.io/mount-options"
|
VolumeMountOptsAnnotation = "volume.podman.io/mount-options"
|
||||||
|
// Kube annotation for podman volume import source.
|
||||||
|
VolumeImportSourceAnnotation = "volume.podman.io/import-source"
|
||||||
)
|
)
|
||||||
|
@ -3,6 +3,7 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -19,6 +20,7 @@ import (
|
|||||||
"github.com/containers/podman/v4/pkg/bindings/play"
|
"github.com/containers/podman/v4/pkg/bindings/play"
|
||||||
"github.com/containers/podman/v4/pkg/util"
|
"github.com/containers/podman/v4/pkg/util"
|
||||||
. "github.com/containers/podman/v4/test/utils"
|
. "github.com/containers/podman/v4/test/utils"
|
||||||
|
"github.com/containers/podman/v4/utils"
|
||||||
"github.com/containers/storage/pkg/stringid"
|
"github.com/containers/storage/pkg/stringid"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
@ -1326,6 +1328,36 @@ func milliCPUToQuota(milliCPU string) int {
|
|||||||
return milli * defaultCPUPeriod
|
return milli * defaultCPUPeriod
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createSourceTarFile(fileName, fileContent, tarFilePath string) error {
|
||||||
|
dir, err := os.MkdirTemp("", "podmanTest")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filepath.Join(dir, fileName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = file.Write([]byte(fileContent))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tarFile, err := os.Create(tarFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tarFile.Close()
|
||||||
|
|
||||||
|
return utils.TarToFilesystem(dir, tarFile)
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Describe("Podman play kube", func() {
|
var _ = Describe("Podman play kube", func() {
|
||||||
var (
|
var (
|
||||||
tempdir string
|
tempdir string
|
||||||
@ -3075,6 +3107,46 @@ o: {{ .Options.o }}`})
|
|||||||
Expect(inspect.OutputToString()).To(ContainSubstring("o: " + volOpts))
|
Expect(inspect.OutputToString()).To(ContainSubstring("o: " + volOpts))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman play kube persistentVolumeClaim with source", func() {
|
||||||
|
fileName := "data"
|
||||||
|
expectedFileContent := "Test"
|
||||||
|
tarFilePath := filepath.Join(os.TempDir(), "podmanVolumeSource.tgz")
|
||||||
|
err := createSourceTarFile(fileName, expectedFileContent, tarFilePath)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
volName := "myVolWithStorage"
|
||||||
|
pvc := getPVC(withPVCName(volName),
|
||||||
|
withPVCAnnotations(util.VolumeImportSourceAnnotation, tarFilePath),
|
||||||
|
)
|
||||||
|
err = generateKubeYaml("persistentVolumeClaim", pvc, kubeYaml)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
|
||||||
|
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
|
||||||
|
kube.WaitWithDefaultTimeout()
|
||||||
|
if IsRemote() {
|
||||||
|
Expect(kube).Error()
|
||||||
|
Expect(kube.ErrorToString()).To(ContainSubstring("importing volumes is not supported for remote requests"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Expect(kube).Should(Exit(0))
|
||||||
|
|
||||||
|
inspect := podmanTest.Podman([]string{"inspect", volName, "--format", `
|
||||||
|
{
|
||||||
|
"Name": "{{ .Name }}",
|
||||||
|
"Mountpoint": "{{ .Mountpoint }}"
|
||||||
|
}`})
|
||||||
|
inspect.WaitWithDefaultTimeout()
|
||||||
|
Expect(inspect).Should(Exit(0))
|
||||||
|
mp := make(map[string]string)
|
||||||
|
err = json.Unmarshal([]byte(inspect.OutputToString()), &mp)
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(mp["Name"]).To(Equal(volName))
|
||||||
|
files, err := os.ReadDir(mp["Mountpoint"])
|
||||||
|
Expect(err).To(BeNil())
|
||||||
|
Expect(len(files)).To(Equal(1))
|
||||||
|
Expect(files[0].Name()).To(Equal(fileName))
|
||||||
|
})
|
||||||
|
|
||||||
// Multi doc related tests
|
// Multi doc related tests
|
||||||
It("podman play kube multi doc yaml with persistentVolumeClaim, service and deployment", func() {
|
It("podman play kube multi doc yaml with persistentVolumeClaim, service and deployment", func() {
|
||||||
yamlDocs := []string{}
|
yamlDocs := []string{}
|
||||||
|
Reference in New Issue
Block a user