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:
OpenShift Merge Robot
2022-10-25 10:05:45 -04:00
committed by GitHub
7 changed files with 165 additions and 0 deletions

View File

@ -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:

View 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()
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"
)

View File

@ -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{}