-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Add remote support for podman volume import
and podman volume export
#26434
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,20 @@ | ||
package volumes | ||
|
||
import ( | ||
"errors" | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/containers/podman/v5/cmd/podman/common" | ||
"github.com/containers/podman/v5/cmd/podman/parse" | ||
"github.com/containers/podman/v5/cmd/podman/registry" | ||
"github.com/containers/podman/v5/pkg/domain/entities" | ||
"github.com/containers/podman/v5/pkg/errorhandling" | ||
"github.com/containers/podman/v5/utils" | ||
"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, | ||
|
@@ -37,65 +34,29 @@ func init() { | |
} | ||
|
||
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] | ||
opts := entities.VolumeImportOptions{} | ||
|
||
if tarPath != "-" { | ||
err := parse.ValidateFileName(tarPath) | ||
if err != nil { | ||
filepath := args[1] | ||
if filepath == "-" { | ||
opts.Input = os.Stdin | ||
} else { | ||
if err := parse.ValidateFileName(filepath); err != nil { | ||
return err | ||
} | ||
|
||
// open tar file | ||
tarFile, err = os.Open(tarPath) | ||
targetFile, err := os.Open(filepath) | ||
if err != nil { | ||
return err | ||
return fmt.Errorf("unable open input file: %w", err) | ||
} | ||
} else { | ||
tarFile = os.Stdin | ||
defer targetFile.Close() | ||
opts.Input = targetFile | ||
} | ||
|
||
inspectOpts.Type = common.VolumeType | ||
inspectOpts.Type = common.VolumeType | ||
volumeData, errs, err := containerEngine.VolumeInspect(ctx, volumes, inspectOpts) | ||
if err != nil { | ||
return err | ||
} | ||
if len(errs) > 0 { | ||
return errorhandling.JoinErrors(errs) | ||
} | ||
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 { | ||
containerEngine := registry.ContainerEngine() | ||
ctx := context.Background() | ||
|
||
if err := containerEngine.VolumeImport(ctx, args[0], opts); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: same thing could return directly with the 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) | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,17 @@ | |
package libpod | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"time" | ||
|
||
"github.com/containers/podman/v5/libpod/define" | ||
"github.com/containers/podman/v5/libpod/lock" | ||
"github.com/containers/podman/v5/libpod/plugin" | ||
"github.com/containers/podman/v5/utils" | ||
"github.com/containers/storage/pkg/archive" | ||
"github.com/containers/storage/pkg/directory" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// Volume is a libpod named volume. | ||
|
@@ -294,3 +299,55 @@ func (v *Volume) Unmount() error { | |
func (v *Volume) NeedsMount() bool { | ||
return v.needsMount() | ||
} | ||
|
||
// Export volume to tar. | ||
// Returns a ReadCloser which points to a tar of all the volume's contents. | ||
func (v *Volume) Export() (io.ReadCloser, error) { | ||
v.lock.Lock() | ||
err := v.mount() | ||
mountPoint := v.mountPoint() | ||
v.lock.Unlock() | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer func() { | ||
v.lock.Lock() | ||
defer v.lock.Unlock() | ||
|
||
if err := v.unmount(false); err != nil { | ||
logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) | ||
} | ||
}() | ||
Comment on lines
+307
to
+320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we actually want to unlock, I realize this is what the code did before but like in what world is it sane to allow volume rm while export/import is running There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We unlock for commands like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mhh, I guess fair enough as we don't want things like a volume ls being blocked for a long time but when I make the listing parts not holding the lock I like to revalue. |
||
|
||
volContents, err := utils.TarWithChroot(mountPoint) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating tar of volume %s contents: %w", v.Name(), err) | ||
} | ||
|
||
return volContents, nil | ||
} | ||
|
||
// Import a volume from a tar file, provided as an io.Reader. | ||
func (v *Volume) Import(r io.Reader) error { | ||
v.lock.Lock() | ||
err := v.mount() | ||
mountPoint := v.mountPoint() | ||
v.lock.Unlock() | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
v.lock.Lock() | ||
defer v.lock.Unlock() | ||
|
||
if err := v.unmount(false); err != nil { | ||
logrus.Errorf("Error unmounting volume %s: %v", v.Name(), err) | ||
} | ||
}() | ||
Comment on lines
+332
to
+346
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same thing here |
||
|
||
if err := archive.Untar(r, mountPoint, nil); err != nil { | ||
return fmt.Errorf("extracting into volume %s: %w", v.Name(), err) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit might as well just return directly here