Merge pull request #196 from ncdc/describe

Add describers for backups, schedules
This commit is contained in:
Andy Goldstein
2017-11-27 16:29:30 -05:00
committed by GitHub
21 changed files with 680 additions and 179 deletions
+1 -4
View File
@@ -137,15 +137,12 @@ ifneq ($(SKIP_TESTS), 1)
@$(MAKE) shell CMD="-c 'hack/test.sh $(SRC_DIRS)'"
endif
fmt:
@$(MAKE) shell CMD="-c 'hack/update-fmt.sh'"
verify:
ifneq ($(SKIP_TESTS), 1)
@$(MAKE) shell CMD="-c 'hack/verify-all.sh'"
endif
update: fmt
update:
@$(MAKE) shell CMD="-c 'hack/update-all.sh'"
release: all-tar-bin checksum
+1
View File
@@ -29,6 +29,7 @@ Work with backups
### SEE ALSO
* [ark](ark.md) - Back up and restore Kubernetes cluster resources.
* [ark backup create](ark_backup_create.md) - Create a backup
* [ark backup describe](ark_backup_describe.md) - Describe backups
* [ark backup download](ark_backup_download.md) - Download a backup
* [ark backup get](ark_backup_get.md) - Get backups
* [ark backup logs](ark_backup_logs.md) - Get backup logs
+36
View File
@@ -0,0 +1,36 @@
## ark backup describe
Describe backups
### Synopsis
Describe backups
```
ark backup describe [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for describe
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark backup](ark_backup.md) - Work with backups
+2
View File
@@ -28,5 +28,7 @@ Describe ark resources
### SEE ALSO
* [ark](ark.md) - Back up and restore Kubernetes cluster resources.
* [ark describe backups](ark_describe_backups.md) - Describe backups
* [ark describe restores](ark_describe_restores.md) - Describe restores
* [ark describe schedules](ark_describe_schedules.md) - Describe schedules
@@ -0,0 +1,36 @@
## ark describe backups
Describe backups
### Synopsis
Describe backups
```
ark describe backups [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for backups
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark describe](ark_describe.md) - Describe ark resources
+3 -6
View File
@@ -8,17 +8,14 @@ Describe restores
Describe restores
```
ark describe restores [flags]
ark describe restores [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for restores
--label-columns stringArray a comma-separated list of labels to be displayed as columns
-o, --output string Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'. (default "table")
-l, --selector string only show items matching this label selector
--show-labels show labels in the last column
-h, --help help for restores
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
@@ -0,0 +1,36 @@
## ark describe schedules
Describe schedules
### Synopsis
Describe schedules
```
ark describe schedules [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for schedules
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark describe](ark_describe.md) - Describe ark resources
+3 -6
View File
@@ -8,17 +8,14 @@ Describe restores
Describe restores
```
ark restore describe [flags]
ark restore describe [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for describe
--label-columns stringArray a comma-separated list of labels to be displayed as columns
-o, --output string Output display format. For create commands, display the object but do not send it to the server. Valid formats are 'table', 'json', and 'yaml'. (default "table")
-l, --selector string only show items matching this label selector
--show-labels show labels in the last column
-h, --help help for describe
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
+1
View File
@@ -30,5 +30,6 @@ Work with schedules
* [ark](ark.md) - Back up and restore Kubernetes cluster resources.
* [ark schedule create](ark_schedule_create.md) - Create a schedule
* [ark schedule delete](ark_schedule_delete.md) - Delete a schedule
* [ark schedule describe](ark_schedule_describe.md) - Describe schedules
* [ark schedule get](ark_schedule_get.md) - Get schedules
@@ -0,0 +1,36 @@
## ark schedule describe
Describe schedules
### Synopsis
Describe schedules
```
ark schedule describe [NAME1] [NAME2] [NAME...] [flags]
```
### Options
```
-h, --help help for describe
-l, --selector string only show items matching this label selector
```
### Options inherited from parent commands
```
--alsologtostderr log to standard error as well as files
--kubeconfig string Path to the kubeconfig file to use to talk to the Kubernetes apiserver. If unset, try the environment variable KUBECONFIG, as well as in-cluster configuration
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--logtostderr log to standard error instead of files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
-v, --v Level log level for V logs
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
### SEE ALSO
* [ark schedule](ark_schedule.md) - Work with schedules
+1 -1
View File
@@ -24,7 +24,7 @@ backupStorageProvider:
config:
region: minio
s3ForcePathStyle: "true"
s3Url: http://minio:9000
s3Url: http://minio.heptio-ark.svc:9000
backupSyncPeriod: 1m
gcSyncPeriod: 1m
scheduleSyncPeriod: 1m
+1 -3
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),
+41 -4
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
}
+8 -6
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,
)
+2 -120
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)
}
}
}
+41 -4
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
}
+1 -2
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),
)
+186
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)
}
}
}
+38 -23
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)
}
}
+138
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)
}
}
}
+68
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)
}