diff --git a/changelogs/unreleased/1337-skriss b/changelogs/unreleased/1337-skriss new file mode 100644 index 000000000..225da5a29 --- /dev/null +++ b/changelogs/unreleased/1337-skriss @@ -0,0 +1 @@ +velero backup logs & velero restore logs: show helpful error message if backup/restore does not exist or is not finished processing diff --git a/pkg/cmd/cli/backup/logs.go b/pkg/cmd/cli/backup/logs.go index 32e14ec8c..960c785a8 100644 --- a/pkg/cmd/cli/backup/logs.go +++ b/pkg/cmd/cli/backup/logs.go @@ -21,6 +21,8 @@ import ( "time" "github.com/spf13/cobra" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "github.com/heptio/velero/pkg/apis/velero/v1" "github.com/heptio/velero/pkg/client" @@ -36,10 +38,24 @@ func NewLogsCommand(f client.Factory) *cobra.Command { Short: "Get backup logs", Args: cobra.ExactArgs(1), Run: func(c *cobra.Command, args []string) { + backupName := args[0] + veleroClient, err := f.Client() cmd.CheckError(err) - err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), args[0], v1.DownloadTargetKindBackupLog, os.Stdout, timeout) + backup, err := veleroClient.VeleroV1().Backups(f.Namespace()).Get(backupName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + cmd.Exit("Backup %q does not exist.", backupName) + } else if err != nil { + cmd.Exit("Error checking for backup %q: %v", backupName, err) + } + + if backup.Status.Phase != v1.BackupPhaseCompleted && backup.Status.Phase != v1.BackupPhaseFailed { + cmd.Exit("Logs for backup %q are not available until it's finished processing. Please wait "+ + "until the backup has a phase of Completed or Failed and try again.", backupName) + } + + err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), backupName, v1.DownloadTargetKindBackupLog, os.Stdout, timeout) cmd.CheckError(err) }, } diff --git a/pkg/cmd/cli/restore/logs.go b/pkg/cmd/cli/restore/logs.go index 24f19d3b6..ae40440cc 100644 --- a/pkg/cmd/cli/restore/logs.go +++ b/pkg/cmd/cli/restore/logs.go @@ -20,15 +20,14 @@ import ( "os" "time" - "github.com/pkg/errors" "github.com/spf13/cobra" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "github.com/heptio/velero/pkg/apis/velero/v1" "github.com/heptio/velero/pkg/client" "github.com/heptio/velero/pkg/cmd" "github.com/heptio/velero/pkg/cmd/util/downloadrequest" - veleroclient "github.com/heptio/velero/pkg/generated/clientset/versioned" ) func NewLogsCommand(f client.Factory) *cobra.Command { @@ -39,12 +38,24 @@ func NewLogsCommand(f client.Factory) *cobra.Command { Short: "Get restore logs", Args: cobra.ExactArgs(1), Run: func(c *cobra.Command, args []string) { - l := NewLogsOptions() - cmd.CheckError(l.Complete(args)) - cmd.CheckError(l.Validate(f)) + restoreName := args[0] + veleroClient, err := f.Client() cmd.CheckError(err) - err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), args[0], v1.DownloadTargetKindRestoreLog, os.Stdout, timeout) + + restore, err := veleroClient.VeleroV1().Restores(f.Namespace()).Get(restoreName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + cmd.Exit("Restore %q does not exist.", restoreName) + } else if err != nil { + cmd.Exit("Error checking for restore %q: %v", restoreName, err) + } + + if restore.Status.Phase != v1.RestorePhaseCompleted && restore.Status.Phase != v1.RestorePhaseFailed { + cmd.Exit("Logs for restore %q are not available until it's finished processing. Please wait "+ + "until the restore has a phase of Completed or Failed and try again.", restoreName) + } + + err = downloadrequest.Stream(veleroClient.VeleroV1(), f.Namespace(), restoreName, v1.DownloadTargetKindRestoreLog, os.Stdout, timeout) cmd.CheckError(err) }, } @@ -53,41 +64,3 @@ func NewLogsCommand(f client.Factory) *cobra.Command { return c } - -// LogsOptions contains the fields required to retrieve logs of a restore -type LogsOptions struct { - RestoreName string - - client veleroclient.Interface -} - -// NewLogsOptions returns a new instance of LogsOptions -func NewLogsOptions() *LogsOptions { - return &LogsOptions{} -} - -// Complete fills in LogsOptions with the given parameters, like populating the -// restore name from the input args -func (l *LogsOptions) Complete(args []string) error { - l.RestoreName = args[0] - return nil -} - -// Validate validates the LogsOptions against the cluster, like validating if -// the given restore exists in the cluster or not -func (l *LogsOptions) Validate(f client.Factory) error { - c, err := f.Client() - if err != nil { - return err - } - l.client = c - - r, err := l.client.VeleroV1().Restores(f.Namespace()).Get(l.RestoreName, metav1.GetOptions{}) - if err != nil { - return err - } - if r.Status.Phase != v1.RestorePhaseCompleted { - return errors.Errorf("unable to retrieve logs because restore is not complete") - } - return nil -} diff --git a/pkg/cmd/errors.go b/pkg/cmd/errors.go index be28d981f..d6ff984b6 100644 --- a/pkg/cmd/errors.go +++ b/pkg/cmd/errors.go @@ -32,3 +32,9 @@ func CheckError(err error) { os.Exit(1) } } + +// Exit prints msg (with optional args), plus a newline, to stderr and exits with code 1. +func Exit(msg string, args ...interface{}) { + fmt.Fprintf(os.Stderr, msg+"\n", args...) + os.Exit(1) +}