Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

volumes: Add support for volume import which allows importing contents of external tarballs into podman volumes. #11307

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions cmd/podman/volumes/import.go
Original file line number Diff line number Diff line change
@@ -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 {
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
return errors.New("no volume data found")
}
mountPoint := volumeData[0].VolumeConfigResponse.Mountpoint
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
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)
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion docs/source/markdown/podman-volume-export.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ $ podman volume export myvol --output myvol.tar
```

## SEE ALSO
podman-volume(1)
podman-volume(1), podman-volume-import(1)
35 changes: 35 additions & 0 deletions docs/source/markdown/podman-volume-import.1.md
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions docs/source/markdown/podman-volume.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
2 changes: 2 additions & 0 deletions docs/source/volume.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 44 additions & 0 deletions test/e2e/volume_create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
flouthoc marked this conversation as resolved.
Show resolved Hide resolved
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"))
})

flouthoc marked this conversation as resolved.
Show resolved Hide resolved
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()
Expand Down
16 changes: 16 additions & 0 deletions test/system/160-volumes.bats
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ EOF
}


# Podman volume import test
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice touch!

@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"
Expand Down