mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
213 lines
5.7 KiB
Go
213 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
incus "github.com/lxc/incus/v7/client"
|
|
"github.com/lxc/incus/v7/cmd/incus/color"
|
|
u "github.com/lxc/incus/v7/cmd/incus/usage"
|
|
"github.com/lxc/incus/v7/internal/i18n"
|
|
"github.com/lxc/incus/v7/shared/api"
|
|
"github.com/lxc/incus/v7/shared/archive"
|
|
cli "github.com/lxc/incus/v7/shared/cmd"
|
|
"github.com/lxc/incus/v7/shared/logger"
|
|
"github.com/lxc/incus/v7/shared/util"
|
|
)
|
|
|
|
type cmdExport struct {
|
|
global *cmdGlobal
|
|
|
|
flagInstanceOnly bool
|
|
flagRootOnly bool
|
|
flagOptimizedStorage bool
|
|
flagCompressionAlgorithm string
|
|
flagForce bool
|
|
}
|
|
|
|
var cmdExportUsage = u.Usage{u.Instance.Remote(), u.Target(u.File).Optional()}
|
|
|
|
func (c *cmdExport) command() *cobra.Command {
|
|
cmd := &cobra.Command{}
|
|
cmd.Use = cli.U("export", cmdExportUsage...)
|
|
cmd.Short = i18n.G("Export instance backups")
|
|
cmd.Long = cli.FormatSection(color.DescriptionPrefix, i18n.G(
|
|
`Export instances as backup tarballs.`,
|
|
))
|
|
cmd.Example = cli.FormatSection("", i18n.G(
|
|
`incus export u1 backup0.tar.gz
|
|
Download a backup tarball of the u1 instance.
|
|
|
|
incus export u1 -
|
|
Download a backup tarball with it written to the standard output.`,
|
|
))
|
|
|
|
cmd.RunE = c.run
|
|
cli.AddBoolFlag(cmd.Flags(), &c.flagInstanceOnly, "instance-only", i18n.G("Whether or not to only backup the instance (without snapshots)"))
|
|
cli.AddBoolFlag(cmd.Flags(), &c.flagRootOnly, "root-only", i18n.G("Whether or not to only backup the instance (without dependent volumes)"))
|
|
cli.AddBoolFlag(cmd.Flags(), &c.flagOptimizedStorage, "optimized-storage", i18n.G("Use storage driver optimized format (can only be restored on a similar pool)"))
|
|
cli.AddStringFlag(cmd.Flags(), &c.flagCompressionAlgorithm, "compression", "", "", i18n.G("Compression algorithm to use (none for uncompressed)"))
|
|
cli.AddBoolFlag(cmd.Flags(), &c.flagForce, "force|f", i18n.G("Force overwriting existing backup file"))
|
|
|
|
return cmd
|
|
}
|
|
|
|
func (c *cmdExport) run(cmd *cobra.Command, args []string) error {
|
|
parsed, err := c.global.Parse(cmdExportUsage, cmd, args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d := parsed[0].RemoteServer
|
|
instanceName := parsed[0].RemoteObject.String
|
|
hasTarget := !parsed[1].Skipped
|
|
targetName := parsed[1].Get("." + instanceName + ".backup")
|
|
|
|
if isStdout(targetName) {
|
|
// If outputting to stdout, quiesce the output.
|
|
c.global.flagQuiet = true
|
|
} else if hasTarget && !c.flagForce && util.PathExists(targetName) {
|
|
// Check if the target path already exists.
|
|
return fmt.Errorf(i18n.G("Target path %q already exists"), targetName)
|
|
}
|
|
|
|
instanceOnly := c.flagInstanceOnly
|
|
|
|
req := api.InstanceBackupsPost{
|
|
Name: "",
|
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
InstanceOnly: instanceOnly,
|
|
RootOnly: c.flagRootOnly,
|
|
OptimizedStorage: c.flagOptimizedStorage,
|
|
CompressionAlgorithm: c.flagCompressionAlgorithm,
|
|
}
|
|
|
|
var getter func(backupReq *incus.BackupFileRequest) error
|
|
|
|
if d.HasExtension("direct_backup") {
|
|
getter = func(backupReq *incus.BackupFileRequest) error {
|
|
return d.CreateInstanceBackupStream(instanceName, req, backupReq)
|
|
}
|
|
} else {
|
|
// Send the request.
|
|
op, err := d.CreateInstanceBackup(instanceName, req)
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.G("Create instance backup: %w"), err)
|
|
}
|
|
|
|
// Watch the background operation.
|
|
progress := cli.ProgressRenderer{
|
|
Format: i18n.G("Backing up instance: %s"),
|
|
Quiet: c.global.flagQuiet,
|
|
}
|
|
|
|
_, err = op.AddHandler(progress.UpdateOp)
|
|
if err != nil {
|
|
progress.Done("")
|
|
return err
|
|
}
|
|
|
|
// Wait until backup is done.
|
|
err = cli.CancelableWait(op, &progress)
|
|
if err != nil {
|
|
progress.Done("")
|
|
return err
|
|
}
|
|
|
|
progress.Done("")
|
|
|
|
err = op.Wait()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Get name of backup.
|
|
uStr := op.Get().Resources["backups"][0]
|
|
uri, err := url.Parse(uStr)
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.G("Invalid URL %q: %w"), uStr, err)
|
|
}
|
|
|
|
backupName, err := url.PathUnescape(path.Base(uri.EscapedPath()))
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.G("Invalid backup name segment in path %q: %w"), uri.EscapedPath(), err)
|
|
}
|
|
|
|
defer func() {
|
|
// Delete backup after we're done.
|
|
op, err = d.DeleteInstanceBackup(instanceName, backupName)
|
|
if err == nil {
|
|
_ = op.Wait()
|
|
}
|
|
}()
|
|
|
|
getter = func(backupReq *incus.BackupFileRequest) error {
|
|
_, err := d.GetInstanceBackupFile(instanceName, backupName, backupReq)
|
|
return err
|
|
}
|
|
}
|
|
|
|
var target *os.File
|
|
if isStdout(targetName) {
|
|
target = os.Stdout
|
|
} else {
|
|
target, err = os.Create(targetName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer logger.WarnOnErrorExcept(target.Close, []error{os.ErrClosed}, "Failed to close target file")
|
|
}
|
|
|
|
// Prepare the download request.
|
|
progress := cli.ProgressRenderer{
|
|
Format: i18n.G("Exporting the backup: %s"),
|
|
Quiet: c.global.flagQuiet,
|
|
}
|
|
|
|
backupFileRequest := incus.BackupFileRequest{
|
|
BackupFile: io.WriteSeeker(target),
|
|
ProgressHandler: progress.UpdateProgress,
|
|
}
|
|
|
|
// Export tarball.
|
|
err = getter(&backupFileRequest)
|
|
if err != nil {
|
|
_ = os.Remove(targetName)
|
|
progress.Done("")
|
|
return fmt.Errorf(i18n.G("Fetch instance backup file: %w"), err)
|
|
}
|
|
|
|
// Detect backup file type and rename file accordingly.
|
|
if !hasTarget {
|
|
_, err := target.Seek(0, io.SeekStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, ext, _, err := archive.DetectCompressionFile(target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.Rename(targetName, instanceName+ext)
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.G("Failed to rename export file: %w"), err)
|
|
}
|
|
}
|
|
|
|
err = target.Close()
|
|
if err != nil {
|
|
return fmt.Errorf(i18n.G("Failed to close export file: %w"), err)
|
|
}
|
|
|
|
progress.Done(i18n.G("Backup exported successfully!"))
|
|
return nil
|
|
}
|