diff options
-rw-r--r-- | cmd/podman/volumes/import.go | 97 | ||||
-rw-r--r-- | docs/source/markdown/podman-volume-export.1.md | 2 | ||||
-rw-r--r-- | docs/source/markdown/podman-volume-import.1.md | 35 | ||||
-rw-r--r-- | docs/source/markdown/podman-volume.1.md | 1 | ||||
-rw-r--r-- | docs/source/volume.rst | 2 | ||||
-rw-r--r-- | test/e2e/volume_create_test.go | 44 | ||||
-rw-r--r-- | test/system/160-volumes.bats | 16 |
7 files changed, 196 insertions, 1 deletions
diff --git a/cmd/podman/volumes/import.go b/cmd/podman/volumes/import.go new file mode 100644 index 000000000..441bd0fe4 --- /dev/null +++ b/cmd/podman/volumes/import.go @@ -0,0 +1,97 @@ +package volumes + +import ( + "fmt" + "os" + + "github.com/containers/podman/v3/cmd/podman/common" + "github.com/containers/podman/v3/cmd/podman/inspect" + "github.com/containers/podman/v3/cmd/podman/parse" + "github.com/containers/podman/v3/cmd/podman/registry" + "github.com/containers/podman/v3/pkg/domain/entities" + "github.com/containers/podman/v3/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + importDescription = `Imports contents into a podman volume from specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz).` + importCommand = &cobra.Command{ + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Use: "import VOLUME [SOURCE]", + Short: "Import a tarball contents into a podman volume", + Long: importDescription, + RunE: importVol, + Args: cobra.ExactArgs(2), + ValidArgsFunction: common.AutocompleteVolumes, + Example: `podman volume import my_vol /home/user/import.tar + cat ctr.tar | podman import volume my_vol -`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: importCommand, + Parent: volumeCmd, + }) +} + +func importVol(cmd *cobra.Command, args []string) error { + var inspectOpts entities.InspectOptions + var tarFile *os.File + containerEngine := registry.ContainerEngine() + ctx := registry.Context() + // create a slice of volumes since inspect expects slice as arg + volumes := []string{args[0]} + tarPath := args[1] + + if tarPath != "-" { + err := parse.ValidateFileName(tarPath) + if err != nil { + return err + } + + // open tar file + tarFile, err = os.Open(tarPath) + if err != nil { + return err + } + } else { + tarFile = os.Stdin + } + + inspectOpts.Type = inspect.VolumeType + volumeData, _, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) + if err != nil { + return err + } + if len(volumeData) < 1 { + return errors.New("no volume data found") + } + mountPoint := volumeData[0].VolumeConfigResponse.Mountpoint + driver := volumeData[0].VolumeConfigResponse.Driver + volumeOptions := volumeData[0].VolumeConfigResponse.Options + volumeMountStatus, err := containerEngine.VolumeMounted(ctx, args[0]) + if err != nil { + return err + } + if mountPoint == "" { + return errors.New("volume is not mounted anywhere on host") + } + // Check if volume is using external plugin and export only if volume is mounted + if driver != "" && driver != "local" { + if !volumeMountStatus.Value { + return fmt.Errorf("volume is using a driver %s and volume is not mounted on %s", driver, mountPoint) + } + } + // Check if volume is using `local` driver and has mount options type other than tmpfs + if driver == "local" { + 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) +} diff --git a/docs/source/markdown/podman-volume-export.1.md b/docs/source/markdown/podman-volume-export.1.md index caaa37652..7db1e421d 100644 --- a/docs/source/markdown/podman-volume-export.1.md +++ b/docs/source/markdown/podman-volume-export.1.md @@ -35,4 +35,4 @@ $ podman volume export myvol --output myvol.tar ``` ## SEE ALSO -podman-volume(1) +podman-volume(1), podman-volume-import(1) diff --git a/docs/source/markdown/podman-volume-import.1.md b/docs/source/markdown/podman-volume-import.1.md new file mode 100644 index 000000000..6bb868774 --- /dev/null +++ b/docs/source/markdown/podman-volume-import.1.md @@ -0,0 +1,35 @@ +% podman-volume-import(1) + +## NAME +podman\-volume\-import - Import tarball contents into a podman volume + +## SYNOPSIS +**podman volume import** *volume* [*source*] + +## DESCRIPTION + +**podman volume import** imports the contents of a tarball into the podman volume's mount point. +**podman volume import** can consume piped input when using `-` as source path. + +Note: Following command is not supported by podman-remote. + +**podman volume import VOLUME [SOURCE]** + +#### **--help** + +Print usage statement + +## EXAMPLES + +``` +$ gunzip -c hellow.tar.gz | podman volume import myvol - +``` +``` +$ podman volume import myvol test.tar +``` +``` +$ podman volume export myvol | podman volume import oldmyvol - +``` + +## SEE ALSO +podman-volume(1), podman-volume-export(1) diff --git a/docs/source/markdown/podman-volume.1.md b/docs/source/markdown/podman-volume.1.md index 20319ccf7..64b37c28c 100644 --- a/docs/source/markdown/podman-volume.1.md +++ b/docs/source/markdown/podman-volume.1.md @@ -16,6 +16,7 @@ podman volume is a set of subcommands that manage volumes. | create | [podman-volume-create(1)](podman-volume-create.1.md) | Create a new volume. | | exists | [podman-volume-exists(1)](podman-volume-exists.1.md) | Check if the given volume exists. | | export | [podman-volume-export(1)](podman-volume-export.1.md) | Exports volume to external tar. | +| import | [podman-volume-import(1)](podman-volume-import.1.md) | Import tarball contents into a podman volume. | | inspect | [podman-volume-inspect(1)](podman-volume-inspect.1.md) | Get detailed information on one or more volumes. | | ls | [podman-volume-ls(1)](podman-volume-ls.1.md) | List all the available volumes. | | prune | [podman-volume-prune(1)](podman-volume-prune.1.md) | Remove all unused volumes. | diff --git a/docs/source/volume.rst b/docs/source/volume.rst index fb607cc2b..af81f39bc 100644 --- a/docs/source/volume.rst +++ b/docs/source/volume.rst @@ -6,6 +6,8 @@ Volume :doc:`export <markdown/podman-volume-export.1>` Exports volume to external tar +:doc:`import <markdown/podman-volume-import.1>` Import tarball contents into a podman volume + :doc:`inspect <markdown/podman-volume-inspect.1>` Display detailed information on one or more volumes :doc:`ls <markdown/podman-volume-ls.1>` List volumes diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index d9c805f46..3be1486d8 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -79,6 +79,50 @@ var _ = Describe("Podman volume create", func() { Expect(check.OutputToString()).To(ContainSubstring("hello")) }) + It("podman create and import volume", func() { + if podmanTest.RemoteTest { + Skip("Volume export check does not work with a remote client") + } + + session := podmanTest.Podman([]string{"volume", "create", "my_vol"}) + session.WaitWithDefaultTimeout() + volName := session.OutputToString() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--volume", volName + ":/data", ALPINE, "sh", "-c", "echo hello >> " + "/data/test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "export", volName, "--output=hello.tar"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "create", "my_vol2"}) + session.WaitWithDefaultTimeout() + volName = session.OutputToString() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"volume", "import", "my_vol2", "hello.tar"}) + session.WaitWithDefaultTimeout() + volName = session.OutputToString() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"run", "--volume", "my_vol2:/data", ALPINE, "cat", "/data/test"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("hello")) + }) + + It("podman import volume should fail", func() { + // try import on volume or source which does not exists + if podmanTest.RemoteTest { + Skip("Volume export check does not work with a remote client") + } + + session := podmanTest.Podman([]string{"volume", "import", "notfound", "notfound.tar"}) + session.WaitWithDefaultTimeout() + Expect(session).To(ExitWithError()) + }) + It("podman create volume with bad volume option", func() { session := podmanTest.Podman([]string{"volume", "create", "--opt", "badOpt=bad"}) session.WaitWithDefaultTimeout() diff --git a/test/system/160-volumes.bats b/test/system/160-volumes.bats index 9a852db89..f6dc3f0af 100644 --- a/test/system/160-volumes.bats +++ b/test/system/160-volumes.bats @@ -186,6 +186,22 @@ EOF } +# Podman volume import test +@test "podman volume import test" { + skip_if_remote "volumes import is not applicable on podman-remote" + run_podman volume create my_vol + run_podman run --rm -v my_vol:/data $IMAGE sh -c "echo hello >> /data/test" + run_podman volume create my_vol2 + run_podman volume export my_vol --output=hello.tar + # we want to use `run_podman volume export my_vol` but run_podman is wrapping EOF + cat hello.tar | run_podman volume import my_vol2 - + run_podman run --rm -v my_vol2:/data $IMAGE sh -c "cat /data/test" + is "$output" "hello" "output from second container" + run_podman volume rm my_vol + run_podman volume rm my_vol2 +} + + # Confirm that container sees the correct id @test "podman volume with --userns=keep-id" { is_rootless || skip "only meaningful when run rootless" |