Add backup & schedule describers

Signed-off-by: Andy Goldstein <andy.goldstein@gmail.com>
pull/196/head
Andy Goldstein 2017-11-13 09:50:49 -05:00
parent 062a5d7557
commit c2dc41efd8
10 changed files with 524 additions and 162 deletions

View File

@ -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),

View File

@ -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
}

View File

@ -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,
)

View File

@ -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 = "<none>"
} 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 = "<none>"
} 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 = "<none>"
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<none>\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<none>\nErrors:\t<none>\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<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
return
}
if err := json.NewDecoder(&buf).Decode(&resultMap); err != nil {
fmt.Fprintf(out, "Warnings:\t<error decoding warnings: %v>\n\nErrors:\t<error decoding errors: %v>\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: <none>\n")
} else {
fmt.Fprintf(out, "\tNamespaces:\n")
for ns, warnings := range result.Namespaces {
output.DescribeSlice(out, 2, ns, warnings)
}
}
}

View File

@ -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
}

View File

@ -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),
)

View File

@ -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 = "<none>"
} 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 = "<none>"
} 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 = "<none>"
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<none>\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 = "<none>"
} 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 = "<none>"
} else {
s = strings.Join(spec.ExcludedResources, ", ")
}
d.Printf("\t\t\t\tExcluded:\t%s\n", s)
d.Println()
s = "<none>"
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<none>\n")
} else {
for _, ve := range status.ValidationErrors {
d.Printf("\t%s\n", ve)
}
}
d.Println()
if len(status.VolumeBackups) == 0 {
d.Printf("Persistent Volumes: <none included>\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 := "<N/A>"
if info.Iops != nil {
iops = fmt.Sprintf("%d", *info.Iops)
}
d.Printf("\t\tIOPS:\t%s\n", iops)
}
}
}

View File

@ -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, "<none>\n")
d.Printf("<none>\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<none>\n", pretab)
d.Printf("%s<none>\n", pretab)
}
}

View File

@ -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 = "<none>"
} 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 = "<none>"
} 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 = "<none>"
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<none>\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<none>\nErrors:\t<none>\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<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
return
}
if err := json.NewDecoder(&buf).Decode(&resultMap); err != nil {
d.Printf("Warnings:\t<error decoding warnings: %v>\n\nErrors:\t<error decoding errors: %v>\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: <none>\n")
} else {
d.Printf("\tNamespaces:\n")
for ns, warnings := range result.Namespaces {
d.DescribeSlice(2, ns, warnings)
}
}
}

View File

@ -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<none>\n")
} else {
for _, ve := range status.ValidationErrors {
d.Printf("\t%s\n", ve)
}
}
d.Println()
lastBackup := "<never>"
if !status.LastBackup.Time.IsZero() {
lastBackup = fmt.Sprintf("%v", status.LastBackup.Time)
}
d.Printf("Last Backup:\t%s\n", lastBackup)
}