mirror of
https://github.com/containers/podman.git
synced 2025-08-06 11:32:07 +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/gid
|
||||
- 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
|
||||
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()
|
||||
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,
|
||||
StaticIPs: staticIPs,
|
||||
StaticMACs: staticMACs,
|
||||
IsRemote: true,
|
||||
}
|
||||
if _, found := r.URL.Query()["tlsVerify"]; found {
|
||||
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
|
||||
|
@ -58,6 +58,8 @@ type PlayKubeOptions struct {
|
||||
ServiceContainer bool
|
||||
// Userns - define the user namespace to use.
|
||||
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
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/common/pkg/secrets"
|
||||
"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/define"
|
||||
"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/systemd/notifyproxy"
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
"github.com/containers/podman/v4/utils"
|
||||
"github.com/coreos/go-systemd/v22/daemon"
|
||||
"github.com/ghodss/yaml"
|
||||
"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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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.
|
||||
// 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.
|
||||
var importFrom string
|
||||
for k, v := range pvcYAML.Annotations {
|
||||
switch k {
|
||||
case util.VolumeDriverAnnotation:
|
||||
@ -883,16 +899,45 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
|
||||
opts["GID"] = v
|
||||
case util.VolumeMountOptsAnnotation:
|
||||
opts["o"] = v
|
||||
case util.VolumeImportSourceAnnotation:
|
||||
importFrom = v
|
||||
}
|
||||
}
|
||||
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.
|
||||
vol, err := ic.Libpod.NewVolume(ctx, volOptions...)
|
||||
if err != nil {
|
||||
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{
|
||||
Name: vol.Name(),
|
||||
})
|
||||
@ -900,6 +945,42 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste
|
||||
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
|
||||
func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) {
|
||||
var cm v1.ConfigMap
|
||||
|
@ -13,4 +13,6 @@ const (
|
||||
VolumeGIDAnnotation = "volume.podman.io/gid"
|
||||
// Kube annotation for podman volume 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 (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@ -19,6 +20,7 @@ import (
|
||||
"github.com/containers/podman/v4/pkg/bindings/play"
|
||||
"github.com/containers/podman/v4/pkg/util"
|
||||
. "github.com/containers/podman/v4/test/utils"
|
||||
"github.com/containers/podman/v4/utils"
|
||||
"github.com/containers/storage/pkg/stringid"
|
||||
"github.com/google/uuid"
|
||||
. "github.com/onsi/ginkgo"
|
||||
@ -1326,6 +1328,36 @@ func milliCPUToQuota(milliCPU string) int {
|
||||
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 (
|
||||
tempdir string
|
||||
@ -3075,6 +3107,46 @@ o: {{ .Options.o }}`})
|
||||
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
|
||||
It("podman play kube multi doc yaml with persistentVolumeClaim, service and deployment", func() {
|
||||
yamlDocs := []string{}
|
||||
|
Reference in New Issue
Block a user