feat(tht): wire restore command
This commit is contained in:
@@ -46,6 +46,8 @@ Commands:
|
||||
update --check-only Validate the current installation without changing containers.
|
||||
backup [--output PATH] [--include-secrets --yes] [--drain]
|
||||
Create one transactional installation backup.
|
||||
restore ARCHIVE --yes [--drain]
|
||||
Restore one validated installation backup transactionally.
|
||||
sessions migrate --yes
|
||||
Run only the server session migrator and verify pending=[] and drifted=[].
|
||||
remove Display exact stopped app container IDs without mutation.
|
||||
@@ -175,6 +177,8 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) int {
|
||||
result, err = runner.Run(ctx, installation.ComposeArgs("config", "--quiet"), nil)
|
||||
case "backup":
|
||||
return backupCommand(ctx, installation, commandArgs, stdout, stderr)
|
||||
case "restore":
|
||||
return restoreCommand(ctx, installation, commandArgs, stdout, stderr)
|
||||
case "doctor":
|
||||
return doctorCommand(ctx, installation, runner, commandArgs, stdout, stderr)
|
||||
case "pi":
|
||||
@@ -881,6 +885,81 @@ func parseBackupArgs(args []string) (backup.CreateRequest, error) {
|
||||
return request, nil
|
||||
}
|
||||
|
||||
type restoreExecutor func(context.Context, config.Installation, backup.RestoreRequest) (backup.RestoreResult, error)
|
||||
|
||||
func restoreCommand(ctx context.Context, installation config.Installation, args []string, stdout, stderr io.Writer) int {
|
||||
return restoreCommandWith(ctx, installation, args, backup.Restore, stdout, stderr)
|
||||
}
|
||||
|
||||
func restoreCommandWith(
|
||||
ctx context.Context,
|
||||
installation config.Installation,
|
||||
args []string,
|
||||
execute restoreExecutor,
|
||||
stdout, stderr io.Writer,
|
||||
) int {
|
||||
request, err := parseRestoreArgs(args)
|
||||
if err != nil {
|
||||
return commandUsageError(stderr, err.Error())
|
||||
}
|
||||
if execute == nil {
|
||||
return commandUsageError(stderr, "restore executor is unavailable")
|
||||
}
|
||||
result, err := execute(ctx, installation, request)
|
||||
if result.Checkpoint != "" {
|
||||
checkpoint := output.Sanitize(result.Checkpoint, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "Recovery checkpoint: %s\n", checkpoint)
|
||||
} else {
|
||||
fmt.Fprintf(stdout, "Recovery checkpoint: %s\n", checkpoint)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "tht: %s\n", output.Sanitize(err.Error(), nil))
|
||||
if errors.Is(err, backup.ErrRestoreConfirmationRequired) || errors.Is(err, backup.ErrActiveSessions) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stdout, "Restore completed and verified.")
|
||||
return 0
|
||||
}
|
||||
|
||||
func parseRestoreArgs(args []string) (backup.RestoreRequest, error) {
|
||||
request := backup.RestoreRequest{}
|
||||
for len(args) > 0 {
|
||||
argument := args[0]
|
||||
args = args[1:]
|
||||
switch argument {
|
||||
case "--yes":
|
||||
if request.Confirm {
|
||||
return backup.RestoreRequest{}, errors.New("--yes may be supplied once")
|
||||
}
|
||||
request.Confirm = true
|
||||
case "--drain":
|
||||
if request.Drain {
|
||||
return backup.RestoreRequest{}, errors.New("--drain may be supplied once")
|
||||
}
|
||||
request.Drain = true
|
||||
default:
|
||||
if strings.HasPrefix(argument, "-") {
|
||||
return backup.RestoreRequest{}, fmt.Errorf("unknown restore option %q", argument)
|
||||
}
|
||||
if request.Archive != "" {
|
||||
return backup.RestoreRequest{}, errors.New("restore accepts exactly one archive")
|
||||
}
|
||||
request.Archive = argument
|
||||
}
|
||||
}
|
||||
if request.Archive == "" {
|
||||
return backup.RestoreRequest{}, errors.New("restore archive is required")
|
||||
}
|
||||
if !request.Confirm {
|
||||
return backup.RestoreRequest{}, errors.New("restore requires --yes")
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func piMutationRequiresLifecycleLock(args []string) bool {
|
||||
if len(args) == 0 {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user