From c2dc41efd85698a7b36c2d40379f8422017e07d5 Mon Sep 17 00:00:00 2001 From: Andy Goldstein Date: Mon, 13 Nov 2017 09:50:49 -0500 Subject: [PATCH] Add backup & schedule describers Signed-off-by: Andy Goldstein --- pkg/cmd/cli/backup/backup.go | 4 +- pkg/cmd/cli/backup/describe.go | 45 +++++- pkg/cmd/cli/describe/describe.go | 14 +- pkg/cmd/cli/restore/describe.go | 122 +------------- pkg/cmd/cli/schedule/describe.go | 45 +++++- pkg/cmd/cli/schedule/schedule.go | 3 +- pkg/cmd/util/output/backup_describer.go | 186 ++++++++++++++++++++++ pkg/cmd/util/output/describe.go | 61 ++++--- pkg/cmd/util/output/restore_describer.go | 138 ++++++++++++++++ pkg/cmd/util/output/schedule_describer.go | 68 ++++++++ 10 files changed, 524 insertions(+), 162 deletions(-) create mode 100644 pkg/cmd/util/output/backup_describer.go create mode 100644 pkg/cmd/util/output/restore_describer.go create mode 100644 pkg/cmd/util/output/schedule_describer.go diff --git a/pkg/cmd/cli/backup/backup.go b/pkg/cmd/cli/backup/backup.go index 3167c9924..eb000a326 100644 --- a/pkg/cmd/cli/backup/backup.go +++ b/pkg/cmd/cli/backup/backup.go @@ -33,11 +33,9 @@ func NewCommand(f client.Factory) *cobra.Command { NewCreateCommand(f, "create"), NewGetCommand(f, "get"), NewLogsCommand(f), + NewDescribeCommand(f, "describe"), NewDownloadCommand(f), - // Will implement describe later - // NewDescribeCommand(f), - // If you delete a backup and it still exists in object storage, the backup sync controller will // recreate it. Until we have a good UX around this, we're disabling the delete command. // NewDeleteCommand(f), diff --git a/pkg/cmd/cli/backup/describe.go b/pkg/cmd/cli/backup/describe.go index 79b59a230..8c30fae96 100644 --- a/pkg/cmd/cli/backup/describe.go +++ b/pkg/cmd/cli/backup/describe.go @@ -17,18 +17,55 @@ limitations under the License. package backup import ( - "github.com/spf13/cobra" + "fmt" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd" + "github.com/heptio/ark/pkg/cmd/util/output" ) -func NewDescribeCommand(f client.Factory) *cobra.Command { +func NewDescribeCommand(f client.Factory, use string) *cobra.Command { + var listOptions metav1.ListOptions + c := &cobra.Command{ - Use: "describe", - Short: "Describe a backup", + Use: use + " [NAME1] [NAME2] [NAME...]", + Short: "Describe backups", Run: func(c *cobra.Command, args []string) { + arkClient, err := f.Client() + cmd.CheckError(err) + + var backups *v1.BackupList + if len(args) > 0 { + backups = new(v1.BackupList) + for _, name := range args { + backup, err := arkClient.Ark().Backups(v1.DefaultNamespace).Get(name, metav1.GetOptions{}) + cmd.CheckError(err) + backups.Items = append(backups.Items, *backup) + } + } else { + backups, err = arkClient.ArkV1().Backups(v1.DefaultNamespace).List(listOptions) + cmd.CheckError(err) + } + + first := true + for _, backup := range backups.Items { + s := output.DescribeBackup(&backup) + if first { + first = false + fmt.Print(s) + } else { + fmt.Printf("\n\n%s", s) + } + } + cmd.CheckError(err) }, } + c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector") + return c } diff --git a/pkg/cmd/cli/describe/describe.go b/pkg/cmd/cli/describe/describe.go index 4ba570a8b..8290fdfd1 100644 --- a/pkg/cmd/cli/describe/describe.go +++ b/pkg/cmd/cli/describe/describe.go @@ -20,7 +20,9 @@ import ( "github.com/spf13/cobra" "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd/cli/backup" "github.com/heptio/ark/pkg/cmd/cli/restore" + "github.com/heptio/ark/pkg/cmd/cli/schedule" ) func NewCommand(f client.Factory) *cobra.Command { @@ -30,18 +32,18 @@ func NewCommand(f client.Factory) *cobra.Command { Long: "Describe ark resources", } - //backupCommand := backup.NewGetCommand(f, "backups") - //backupCommand.Aliases = []string{"backup"} + backupCommand := backup.NewDescribeCommand(f, "backups") + backupCommand.Aliases = []string{"backup"} - //scheduleCommand := schedule.NewGetCommand(f, "schedules") - //scheduleCommand.Aliases = []string{"schedule"} + scheduleCommand := schedule.NewDescribeCommand(f, "schedules") + scheduleCommand.Aliases = []string{"schedule"} restoreCommand := restore.NewDescribeCommand(f, "restores") restoreCommand.Aliases = []string{"restore"} c.AddCommand( - //backupCommand, - //scheduleCommand, + backupCommand, + scheduleCommand, restoreCommand, ) diff --git a/pkg/cmd/cli/restore/describe.go b/pkg/cmd/cli/restore/describe.go index 416ccb8b3..e0eb9d2f1 100644 --- a/pkg/cmd/cli/restore/describe.go +++ b/pkg/cmd/cli/restore/describe.go @@ -17,21 +17,14 @@ limitations under the License. package restore import ( - "bytes" - "encoding/json" "fmt" - "io" - "strings" - "time" - clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" api "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" "github.com/heptio/ark/pkg/cmd" - "github.com/heptio/ark/pkg/cmd/util/downloadrequest" "github.com/heptio/ark/pkg/cmd/util/output" ) @@ -39,7 +32,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { var listOptions metav1.ListOptions c := &cobra.Command{ - Use: use, + Use: use + " [NAME1] [NAME2] [NAME...]", Short: "Describe restores", Run: func(c *cobra.Command, args []string) { arkClient, err := f.Client() @@ -60,9 +53,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { first := true for _, restore := range restores.Items { - s := output.Describe(func(out io.Writer) { - describeRestore(out, &restore, arkClient) - }) + s := output.DescribeRestore(&restore, arkClient) if first { first = false fmt.Print(s) @@ -76,114 +67,5 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector") - output.BindFlags(c.Flags()) - return c } - -func describeRestore(out io.Writer, restore *api.Restore, arkClient clientset.Interface) { - output.DescribeMetadata(out, restore.ObjectMeta) - - fmt.Fprintln(out) - fmt.Fprintf(out, "Backup:\t%s\n", restore.Spec.BackupName) - - fmt.Fprintln(out) - fmt.Fprintf(out, "Namespaces:\n") - var s string - if len(restore.Spec.IncludedNamespaces) == 0 { - s = "*" - } else { - s = strings.Join(restore.Spec.IncludedNamespaces, ", ") - } - fmt.Fprintf(out, "\tIncluded:\t%s\n", s) - if len(restore.Spec.ExcludedNamespaces) == 0 { - s = "" - } else { - s = strings.Join(restore.Spec.ExcludedNamespaces, ", ") - } - fmt.Fprintf(out, "\tExcluded:\t%s\n", s) - - fmt.Fprintln(out) - fmt.Fprintf(out, "Resources:\n") - if len(restore.Spec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(restore.Spec.IncludedResources, ", ") - } - fmt.Fprintf(out, "\tIncluded:\t%s\n", s) - if len(restore.Spec.ExcludedResources) == 0 { - s = "" - } else { - s = strings.Join(restore.Spec.ExcludedResources, ", ") - } - fmt.Fprintf(out, "\tExcluded:\t%s\n", s) - - fmt.Fprintf(out, "\tCluster-scoped:\t%s\n", output.BoolPointerString(restore.Spec.IncludeClusterResources, "excluded", "included", "auto")) - - fmt.Fprintln(out) - output.DescribeMap(out, "Namespace mappings", restore.Spec.NamespaceMapping) - - fmt.Fprintln(out) - s = "" - if restore.Spec.LabelSelector != nil { - s = metav1.FormatLabelSelector(restore.Spec.LabelSelector) - } - fmt.Fprintf(out, "Label selector:\t%s\n", s) - - fmt.Fprintln(out) - fmt.Fprintf(out, "Restore PVs:\t%s\n", output.BoolPointerString(restore.Spec.RestorePVs, "false", "true", "auto")) - - fmt.Fprintln(out) - fmt.Fprintf(out, "Phase:\t%s\n", restore.Status.Phase) - - fmt.Fprintln(out) - fmt.Fprint(out, "Validation errors:") - if len(restore.Status.ValidationErrors) == 0 { - fmt.Fprintf(out, "\t\n") - } else { - for _, ve := range restore.Status.ValidationErrors { - fmt.Fprintf(out, "\t%s\n", ve) - } - } - - fmt.Fprintln(out) - describeRestoreResults(out, arkClient, restore) -} - -func describeRestoreResults(out io.Writer, arkClient clientset.Interface, restore *api.Restore) { - if restore.Status.Warnings == 0 && restore.Status.Errors == 0 { - fmt.Fprintf(out, "Warnings:\t\nErrors:\t\n") - return - } - - var buf bytes.Buffer - var resultMap map[string]api.RestoreResult - - if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Name, api.DownloadTargetKindRestoreResults, &buf, 30*time.Second); err != nil { - fmt.Fprintf(out, "Warnings:\t\n\nErrors:\t\n", err, err) - return - } - - if err := json.NewDecoder(&buf).Decode(&resultMap); err != nil { - fmt.Fprintf(out, "Warnings:\t\n\nErrors:\t\n", err, err) - return - } - - describeRestoreResult(out, "Warnings", resultMap["warnings"]) - fmt.Fprintln(out) - describeRestoreResult(out, "Errors", resultMap["errors"]) -} - -func describeRestoreResult(out io.Writer, name string, result api.RestoreResult) { - fmt.Fprintf(out, "%s:\n", name) - output.DescribeSlice(out, 1, "Ark", result.Ark) - output.DescribeSlice(out, 1, "Cluster", result.Cluster) - if len(result.Namespaces) == 0 { - fmt.Fprintf(out, "\tNamespaces: \n") - } else { - fmt.Fprintf(out, "\tNamespaces:\n") - for ns, warnings := range result.Namespaces { - output.DescribeSlice(out, 2, ns, warnings) - } - } -} diff --git a/pkg/cmd/cli/schedule/describe.go b/pkg/cmd/cli/schedule/describe.go index cd15d367f..80afb3804 100644 --- a/pkg/cmd/cli/schedule/describe.go +++ b/pkg/cmd/cli/schedule/describe.go @@ -17,18 +17,55 @@ limitations under the License. package schedule import ( - "github.com/spf13/cobra" + "fmt" + "github.com/spf13/cobra" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/heptio/ark/pkg/apis/ark/v1" "github.com/heptio/ark/pkg/client" + "github.com/heptio/ark/pkg/cmd" + "github.com/heptio/ark/pkg/cmd/util/output" ) -func NewDescribeCommand(f client.Factory) *cobra.Command { +func NewDescribeCommand(f client.Factory, use string) *cobra.Command { + var listOptions metav1.ListOptions + c := &cobra.Command{ - Use: "describe", - Short: "Describe a backup", + Use: use + " [NAME1] [NAME2] [NAME...]", + Short: "Describe schedules", Run: func(c *cobra.Command, args []string) { + arkClient, err := f.Client() + cmd.CheckError(err) + + var schedules *v1.ScheduleList + if len(args) > 0 { + schedules = new(v1.ScheduleList) + for _, name := range args { + schedule, err := arkClient.Ark().Schedules(v1.DefaultNamespace).Get(name, metav1.GetOptions{}) + cmd.CheckError(err) + schedules.Items = append(schedules.Items, *schedule) + } + } else { + schedules, err = arkClient.ArkV1().Schedules(v1.DefaultNamespace).List(listOptions) + cmd.CheckError(err) + } + + first := true + for _, schedule := range schedules.Items { + s := output.DescribeSchedule(&schedule) + if first { + first = false + fmt.Print(s) + } else { + fmt.Printf("\n\n%s", s) + } + } + cmd.CheckError(err) }, } + c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "only show items matching this label selector") + return c } diff --git a/pkg/cmd/cli/schedule/schedule.go b/pkg/cmd/cli/schedule/schedule.go index 9a0e99576..9a8db0ec0 100644 --- a/pkg/cmd/cli/schedule/schedule.go +++ b/pkg/cmd/cli/schedule/schedule.go @@ -32,8 +32,7 @@ func NewCommand(f client.Factory) *cobra.Command { c.AddCommand( NewCreateCommand(f, "create"), NewGetCommand(f, "get"), - // Will implement later - // NewDescribeCommand(f), + NewDescribeCommand(f, "describe"), NewDeleteCommand(f), ) diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go new file mode 100644 index 000000000..ae52c1455 --- /dev/null +++ b/pkg/cmd/util/output/backup_describer.go @@ -0,0 +1,186 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package output + +import ( + "fmt" + "strings" + + "github.com/heptio/ark/pkg/apis/ark/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func DescribeBackup(backup *v1.Backup) string { + return Describe(func(d *Describer) { + d.DescribeMetadata(backup.ObjectMeta) + + d.Println() + DescribeBackupSpec(d, backup.Spec) + + d.Println() + DescribeBackupStatus(d, backup.Status) + }) +} + +func DescribeBackupSpec(d *Describer, spec v1.BackupSpec) { + // TODO make a helper for this and use it in all the describers. + d.Printf("Namespaces:\n") + var s string + if len(spec.IncludedNamespaces) == 0 { + s = "*" + } else { + s = strings.Join(spec.IncludedNamespaces, ", ") + } + d.Printf("\tIncluded:\t%s\n", s) + if len(spec.ExcludedNamespaces) == 0 { + s = "" + } else { + s = strings.Join(spec.ExcludedNamespaces, ", ") + } + d.Printf("\tExcluded:\t%s\n", s) + + d.Println() + d.Printf("Resources:\n") + if len(spec.IncludedResources) == 0 { + s = "*" + } else { + s = strings.Join(spec.IncludedResources, ", ") + } + d.Printf("\tIncluded:\t%s\n", s) + if len(spec.ExcludedResources) == 0 { + s = "" + } else { + s = strings.Join(spec.ExcludedResources, ", ") + } + d.Printf("\tExcluded:\t%s\n", s) + + d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(spec.IncludeClusterResources, "excluded", "included", "auto")) + + d.Println() + s = "" + if spec.LabelSelector != nil { + s = metav1.FormatLabelSelector(spec.LabelSelector) + } + d.Printf("Label selector:\t%s\n", s) + + d.Println() + d.Printf("Snapshot PVs:\t%s\n", BoolPointerString(spec.SnapshotVolumes, "false", "true", "auto")) + + d.Println() + d.Printf("TTL:\t%s\n", spec.TTL.Duration) + + d.Println() + if len(spec.Hooks.Resources) == 0 { + d.Printf("Hooks:\t\n") + } else { + d.Printf("Hooks:\n") + d.Printf("\tResources:\n") + for _, backupResourceHookSpec := range spec.Hooks.Resources { + d.Printf("\t\t%s:\n", backupResourceHookSpec.Name) + d.Printf("\t\t\tNamespaces:\n") + var s string + if len(spec.IncludedNamespaces) == 0 { + s = "*" + } else { + s = strings.Join(spec.IncludedNamespaces, ", ") + } + d.Printf("\t\t\t\tIncluded:\t%s\n", s) + if len(spec.ExcludedNamespaces) == 0 { + s = "" + } else { + s = strings.Join(spec.ExcludedNamespaces, ", ") + } + d.Printf("\t\t\t\tExcluded:\t%s\n", s) + + d.Println() + d.Printf("\t\t\tResources:\n") + if len(spec.IncludedResources) == 0 { + s = "*" + } else { + s = strings.Join(spec.IncludedResources, ", ") + } + d.Printf("\t\t\t\tIncluded:\t%s\n", s) + if len(spec.ExcludedResources) == 0 { + s = "" + } else { + s = strings.Join(spec.ExcludedResources, ", ") + } + d.Printf("\t\t\t\tExcluded:\t%s\n", s) + + d.Println() + s = "" + if backupResourceHookSpec.LabelSelector != nil { + s = metav1.FormatLabelSelector(backupResourceHookSpec.LabelSelector) + } + d.Printf("\t\t\tLabel selector:\t%s\n", s) + + for _, hook := range backupResourceHookSpec.Hooks { + if hook.Exec != nil { + d.Println() + d.Printf("\t\t\tExec Hook:\n") + d.Printf("\t\t\t\tContainer:\t%s\n", hook.Exec.Container) + d.Printf("\t\t\t\tCommand:\t%s\n", strings.Join(hook.Exec.Command, " ")) + d.Printf("\t\t\t\tOn Error:\t%s\n", hook.Exec.OnError) + d.Printf("\t\t\t\tTimeout:\t%s\n", hook.Exec.Timeout.Duration) + } + } + } + } + +} + +func DescribeBackupStatus(d *Describer, status v1.BackupStatus) { + phase := status.Phase + if phase == "" { + phase = v1.BackupPhaseNew + } + d.Printf("Phase:\t%s\n", phase) + + d.Println() + d.Printf("Backup Format Version:\t%d\n", status.Version) + + d.Println() + d.Printf("Expiration:\t%s\n", status.Expiration.Time) + + d.Println() + d.Printf("Validation errors:") + if len(status.ValidationErrors) == 0 { + d.Printf("\t\n") + } else { + for _, ve := range status.ValidationErrors { + d.Printf("\t%s\n", ve) + } + } + + d.Println() + if len(status.VolumeBackups) == 0 { + d.Printf("Persistent Volumes: \n") + } else { + d.Printf("Persistent Volumes:\n") + for pvName, info := range status.VolumeBackups { + d.Printf("\t%s:\n", pvName) + d.Printf("\t\tSnapshot ID:\t%s\n", info.SnapshotID) + d.Printf("\t\tType:\t%s\n", info.Type) + d.Printf("\t\tAvailability Zone:\t%s\n", info.AvailabilityZone) + iops := "" + if info.Iops != nil { + iops = fmt.Sprintf("%d", *info.Iops) + } + d.Printf("\t\tIOPS:\t%s\n", iops) + } + } +} diff --git a/pkg/cmd/util/output/describe.go b/pkg/cmd/util/output/describe.go index bd8f77019..2477de26b 100644 --- a/pkg/cmd/util/output/describe.go +++ b/pkg/cmd/util/output/describe.go @@ -19,7 +19,6 @@ package output import ( "bytes" "fmt" - "io" "sort" "strings" "text/tabwriter" @@ -27,30 +26,46 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// Describe configures a tab writer, passing it to fn. The tab writer's output is returned to the -// caller. -func Describe(fn func(out io.Writer)) string { - out := new(tabwriter.Writer) - buf := &bytes.Buffer{} - out.Init(buf, 0, 8, 2, ' ', 0) +type Describer struct { + Prefix string + out *tabwriter.Writer + buf *bytes.Buffer +} - fn(out) +func Describe(fn func(d *Describer)) string { + d := Describer{ + out: new(tabwriter.Writer), + buf: new(bytes.Buffer), + } + d.out.Init(d.buf, 0, 8, 2, ' ', 0) - out.Flush() - return buf.String() + fn(&d) + + d.out.Flush() + return d.buf.String() +} + +func (d *Describer) Printf(msg string, args ...interface{}) { + fmt.Fprint(d.out, d.Prefix) + fmt.Fprintf(d.out, msg, args...) +} + +func (d *Describer) Println(args ...interface{}) { + fmt.Fprint(d.out, d.Prefix) + fmt.Fprintln(d.out, args...) } // DescribeMetadata describes standard object metadata in a consistent manner. -func DescribeMetadata(out io.Writer, metadata metav1.ObjectMeta) { - fmt.Fprintf(out, "Name:\t%s\n", metadata.Name) - fmt.Fprintf(out, "Namespace:\t%s\n", metadata.Namespace) - DescribeMap(out, "Labels", metadata.Labels) - DescribeMap(out, "Annotations", metadata.Annotations) +func (d *Describer) DescribeMetadata(metadata metav1.ObjectMeta) { + d.Printf("Name:\t%s\n", metadata.Name) + d.Printf("Namespace:\t%s\n", metadata.Namespace) + d.DescribeMap("Labels", metadata.Labels) + d.DescribeMap("Annotations", metadata.Annotations) } // DescribeMap describes a map of key-value pairs using name as the heading. -func DescribeMap(out io.Writer, name string, m map[string]string) { - fmt.Fprintf(out, "%s:\t", name) +func (d *Describer) DescribeMap(name string, m map[string]string) { + d.Printf("%s:\t", name) first := true prefix := "" @@ -61,35 +76,35 @@ func DescribeMap(out io.Writer, name string, m map[string]string) { } sort.Strings(keys) for _, key := range keys { - fmt.Fprintf(out, "%s%s=%s\n", prefix, key, m[key]) + d.Printf("%s%s=%s\n", prefix, key, m[key]) if first { first = false prefix = "\t" } } } else { - fmt.Fprint(out, "\n") + d.Printf("\n") } } // DescribeSlice describes a slice of strings using name as the heading. The output is prefixed by // "preindent" number of tabs. -func DescribeSlice(out io.Writer, preindent int, name string, s []string) { +func (d *Describer) DescribeSlice(preindent int, name string, s []string) { pretab := strings.Repeat("\t", preindent) - fmt.Fprintf(out, "%s%s:\t", pretab, name) + d.Printf("%s%s:\t", pretab, name) first := true prefix := "" if len(s) > 0 { for _, x := range s { - fmt.Fprintf(out, "%s%s\n", prefix, x) + d.Printf("%s%s\n", prefix, x) if first { first = false prefix = pretab + "\t" } } } else { - fmt.Fprintf(out, "%s\n", pretab) + d.Printf("%s\n", pretab) } } diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go new file mode 100644 index 000000000..ab6ad304e --- /dev/null +++ b/pkg/cmd/util/output/restore_describer.go @@ -0,0 +1,138 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package output + +import ( + "bytes" + "encoding/json" + "strings" + "time" + + "github.com/heptio/ark/pkg/apis/ark/v1" + "github.com/heptio/ark/pkg/cmd/util/downloadrequest" + clientset "github.com/heptio/ark/pkg/generated/clientset/versioned" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func DescribeRestore(restore *v1.Restore, arkClient clientset.Interface) string { + return Describe(func(d *Describer) { + d.DescribeMetadata(restore.ObjectMeta) + + d.Println() + d.Printf("Backup:\t%s\n", restore.Spec.BackupName) + + d.Println() + d.Printf("Namespaces:\n") + var s string + if len(restore.Spec.IncludedNamespaces) == 0 { + s = "*" + } else { + s = strings.Join(restore.Spec.IncludedNamespaces, ", ") + } + d.Printf("\tIncluded:\t%s\n", s) + if len(restore.Spec.ExcludedNamespaces) == 0 { + s = "" + } else { + s = strings.Join(restore.Spec.ExcludedNamespaces, ", ") + } + d.Printf("\tExcluded:\t%s\n", s) + + d.Println() + d.Printf("Resources:\n") + if len(restore.Spec.IncludedResources) == 0 { + s = "*" + } else { + s = strings.Join(restore.Spec.IncludedResources, ", ") + } + d.Printf("\tIncluded:\t%s\n", s) + if len(restore.Spec.ExcludedResources) == 0 { + s = "" + } else { + s = strings.Join(restore.Spec.ExcludedResources, ", ") + } + d.Printf("\tExcluded:\t%s\n", s) + + d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(restore.Spec.IncludeClusterResources, "excluded", "included", "auto")) + + d.Println() + d.DescribeMap("Namespace mappings", restore.Spec.NamespaceMapping) + + d.Println() + s = "" + if restore.Spec.LabelSelector != nil { + s = metav1.FormatLabelSelector(restore.Spec.LabelSelector) + } + d.Printf("Label selector:\t%s\n", s) + + d.Println() + d.Printf("Restore PVs:\t%s\n", BoolPointerString(restore.Spec.RestorePVs, "false", "true", "auto")) + + d.Println() + d.Printf("Phase:\t%s\n", restore.Status.Phase) + + d.Println() + d.Printf("Validation errors:") + if len(restore.Status.ValidationErrors) == 0 { + d.Printf("\t\n") + } else { + for _, ve := range restore.Status.ValidationErrors { + d.Printf("\t%s\n", ve) + } + } + + d.Println() + describeRestoreResults(d, restore, arkClient) + }) +} + +func describeRestoreResults(d *Describer, restore *v1.Restore, arkClient clientset.Interface) { + if restore.Status.Warnings == 0 && restore.Status.Errors == 0 { + d.Printf("Warnings:\t\nErrors:\t\n") + return + } + + var buf bytes.Buffer + var resultMap map[string]v1.RestoreResult + + if err := downloadrequest.Stream(arkClient.ArkV1(), restore.Name, v1.DownloadTargetKindRestoreResults, &buf, 30*time.Second); err != nil { + d.Printf("Warnings:\t\n\nErrors:\t\n", err, err) + return + } + + if err := json.NewDecoder(&buf).Decode(&resultMap); err != nil { + d.Printf("Warnings:\t\n\nErrors:\t\n", err, err) + return + } + + describeRestoreResult(d, "Warnings", resultMap["warnings"]) + d.Println() + describeRestoreResult(d, "Errors", resultMap["errors"]) +} + +func describeRestoreResult(d *Describer, name string, result v1.RestoreResult) { + d.Printf("%s:\n", name) + d.DescribeSlice(1, "Ark", result.Ark) + d.DescribeSlice(1, "Cluster", result.Cluster) + if len(result.Namespaces) == 0 { + d.Printf("\tNamespaces: \n") + } else { + d.Printf("\tNamespaces:\n") + for ns, warnings := range result.Namespaces { + d.DescribeSlice(2, ns, warnings) + } + } +} diff --git a/pkg/cmd/util/output/schedule_describer.go b/pkg/cmd/util/output/schedule_describer.go new file mode 100644 index 000000000..7ef811650 --- /dev/null +++ b/pkg/cmd/util/output/schedule_describer.go @@ -0,0 +1,68 @@ +/* +Copyright 2017 the Heptio Ark contributors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package output + +import ( + "fmt" + + "github.com/heptio/ark/pkg/apis/ark/v1" +) + +func DescribeSchedule(schedule *v1.Schedule) string { + return Describe(func(d *Describer) { + d.DescribeMetadata(schedule.ObjectMeta) + + d.Println() + DescribeScheduleSpec(d, schedule.Spec) + + d.Println() + DescribeScheduleStatus(d, schedule.Status) + }) +} + +func DescribeScheduleSpec(d *Describer, spec v1.ScheduleSpec) { + d.Printf("Schedule:\t%s\n", spec.Schedule) + + d.Println() + d.Println("Backup Template:") + d.Prefix = "\t" + DescribeBackupSpec(d, spec.Template) + d.Prefix = "" +} + +func DescribeScheduleStatus(d *Describer, status v1.ScheduleStatus) { + phase := status.Phase + if phase == "" { + phase = v1.SchedulePhaseNew + } + + d.Printf("Validation errors:") + if len(status.ValidationErrors) == 0 { + d.Printf("\t\n") + } else { + for _, ve := range status.ValidationErrors { + d.Printf("\t%s\n", ve) + } + } + + d.Println() + lastBackup := "" + if !status.LastBackup.Time.IsZero() { + lastBackup = fmt.Sprintf("%v", status.LastBackup.Time) + } + d.Printf("Last Backup:\t%s\n", lastBackup) +}