add pod volume backups/restores to ark backup/restore describe output

Signed-off-by: Steve Kriss <steve@heptio.com>
pull/550/head
Steve Kriss 2018-06-13 16:40:18 -07:00
parent a70456f5ee
commit 42f2891485
9 changed files with 234 additions and 7 deletions

View File

@ -16,6 +16,7 @@ ark backup describe [NAME1] [NAME2] [NAME...] [flags]
```
-h, --help help for describe
-l, --selector string only show items matching this label selector
--volume-details display details of restic volume backups
```
### Options inherited from parent commands

View File

@ -16,6 +16,7 @@ ark describe backups [NAME1] [NAME2] [NAME...] [flags]
```
-h, --help help for backups
-l, --selector string only show items matching this label selector
--volume-details display details of restic volume backups
```
### Options inherited from parent commands

View File

@ -16,6 +16,7 @@ ark describe restores [NAME1] [NAME2] [NAME...] [flags]
```
-h, --help help for restores
-l, --selector string only show items matching this label selector
--volume-details display details of restic volume restores
```
### Options inherited from parent commands

View File

@ -16,6 +16,7 @@ ark restore describe [NAME1] [NAME2] [NAME...] [flags]
```
-h, --help help for describe
-l, --selector string only show items matching this label selector
--volume-details display details of restic volume restores
```
### Options inherited from parent commands

View File

@ -20,18 +20,22 @@ import (
"fmt"
"os"
pkgbackup "github.com/heptio/ark/pkg/backup"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/heptio/ark/pkg/apis/ark/v1"
pkgbackup "github.com/heptio/ark/pkg/backup"
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/cmd/util/output"
"github.com/heptio/ark/pkg/restic"
)
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
var listOptions metav1.ListOptions
var (
listOptions metav1.ListOptions
volumeDetails bool
)
c := &cobra.Command{
Use: use + " [NAME1] [NAME2] [NAME...]",
@ -61,7 +65,13 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
fmt.Fprintf(os.Stderr, "error getting DeleteBackupRequests for backup %s: %v\n", backup.Name, err)
}
s := output.DescribeBackup(&backup, deleteRequestList.Items)
opts := restic.NewPodVolumeBackupListOptions(backup.Name, string(backup.UID))
podVolumeBackupList, err := arkClient.ArkV1().PodVolumeBackups(f.Namespace()).List(opts)
if err != nil {
fmt.Fprintf(os.Stderr, "error getting PodVolumeBackups for backup %s: %v\n", backup.Name, err)
}
s := output.DescribeBackup(&backup, deleteRequestList.Items, podVolumeBackupList.Items, volumeDetails)
if first {
first = false
fmt.Print(s)
@ -74,6 +84,7 @@ 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")
c.Flags().BoolVar(&volumeDetails, "volume-details", volumeDetails, "display details of restic volume backups")
return c
}

View File

@ -18,6 +18,7 @@ package restore
import (
"fmt"
"os"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -26,10 +27,14 @@ import (
"github.com/heptio/ark/pkg/client"
"github.com/heptio/ark/pkg/cmd"
"github.com/heptio/ark/pkg/cmd/util/output"
"github.com/heptio/ark/pkg/restic"
)
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
var listOptions metav1.ListOptions
var (
listOptions metav1.ListOptions
volumeDetails bool
)
c := &cobra.Command{
Use: use + " [NAME1] [NAME2] [NAME...]",
@ -53,7 +58,13 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
first := true
for _, restore := range restores.Items {
s := output.DescribeRestore(&restore, arkClient)
opts := restic.NewPodVolumeRestoreListOptions(restore.Name, string(restore.UID))
podvolumeRestoreList, err := arkClient.ArkV1().PodVolumeRestores(f.Namespace()).List(opts)
if err != nil {
fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err)
}
s := output.DescribeRestore(&restore, podvolumeRestoreList.Items, volumeDetails, arkClient)
if first {
first = false
fmt.Print(s)
@ -66,6 +77,7 @@ 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")
c.Flags().BoolVar(&volumeDetails, "volume-details", volumeDetails, "display details of restic volume restores")
return c
}

View File

@ -18,6 +18,7 @@ package output
import (
"fmt"
"sort"
"strings"
"github.com/heptio/ark/pkg/apis/ark/v1"
@ -25,7 +26,7 @@ import (
)
// DescribeBackup describes a backup in human-readable format.
func DescribeBackup(backup *v1.Backup, deleteRequests []v1.DeleteBackupRequest) string {
func DescribeBackup(backup *v1.Backup, deleteRequests []v1.DeleteBackupRequest, podVolumeBackups []v1.PodVolumeBackup, volumeDetails bool) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(backup.ObjectMeta)
@ -46,6 +47,11 @@ func DescribeBackup(backup *v1.Backup, deleteRequests []v1.DeleteBackupRequest)
d.Println()
DescribeDeleteBackupRequests(d, deleteRequests)
}
if len(podVolumeBackups) > 0 {
d.Println()
DescribePodVolumeBackups(d, podVolumeBackups, volumeDetails)
}
})
}
@ -241,3 +247,111 @@ func failedDeletionCount(requests []v1.DeleteBackupRequest) int {
}
return count
}
// DescribePodVolumeBackups describes pod volume backups in human-readable format.
func DescribePodVolumeBackups(d *Describer, backups []v1.PodVolumeBackup, details bool) {
if details {
d.Printf("Restic Backups:\n")
} else {
d.Printf("Restic Backups (specify --volume-details for more information):\n")
}
// separate backups by phase (combining <none> and New into a single group)
backupsByPhase := groupByPhase(backups)
// go through phases in a specific order
for _, phase := range []string{
string(v1.PodVolumeBackupPhaseCompleted),
string(v1.PodVolumeBackupPhaseFailed),
"In Progress",
string(v1.PodVolumeBackupPhaseNew),
} {
if len(backupsByPhase[phase]) == 0 {
continue
}
// if we're not printing details, just report the phase and count
if !details {
d.Printf("\t%s:\t%d\n", phase, len(backupsByPhase[phase]))
continue
}
// group the backups in the current phase by pod (i.e. "ns/name")
backupsByPod := new(volumesByPod)
for _, backup := range backupsByPhase[phase] {
backupsByPod.Add(backup.Spec.Pod.Namespace, backup.Spec.Pod.Name, backup.Spec.Volume)
}
d.Printf("\t%s:\n", phase)
for _, backupGroup := range backupsByPod.Sorted() {
sort.Strings(backupGroup.volumes)
// print volumes backed up for this pod
d.Printf("\t\t%s: %s\n", backupGroup.label, strings.Join(backupGroup.volumes, ", "))
}
}
}
func groupByPhase(backups []v1.PodVolumeBackup) map[string][]v1.PodVolumeBackup {
backupsByPhase := make(map[string][]v1.PodVolumeBackup)
phaseToGroup := map[v1.PodVolumeBackupPhase]string{
v1.PodVolumeBackupPhaseCompleted: string(v1.PodVolumeBackupPhaseCompleted),
v1.PodVolumeBackupPhaseFailed: string(v1.PodVolumeBackupPhaseFailed),
v1.PodVolumeBackupPhaseInProgress: "In Progress",
v1.PodVolumeBackupPhaseNew: string(v1.PodVolumeBackupPhaseNew),
"": string(v1.PodVolumeBackupPhaseNew),
}
for _, backup := range backups {
group := phaseToGroup[backup.Status.Phase]
backupsByPhase[group] = append(backupsByPhase[group], backup)
}
return backupsByPhase
}
type podVolumeGroup struct {
label string
volumes []string
}
// volumesByPod stores podVolumeGroups, where the grouping
// label is "namespace/name".
type volumesByPod struct {
volumesByPodMap map[string]*podVolumeGroup
volumesByPodSlice []*podVolumeGroup
}
// Add adds a pod volume with the specified pod namespace, name
// and volume to the appropriate group.
func (v *volumesByPod) Add(namespace, name, volume string) {
if v.volumesByPodMap == nil {
v.volumesByPodMap = make(map[string]*podVolumeGroup)
}
key := fmt.Sprintf("%s/%s", namespace, name)
if group, ok := v.volumesByPodMap[key]; !ok {
group := &podVolumeGroup{
label: key,
volumes: []string{volume},
}
v.volumesByPodMap[key] = group
v.volumesByPodSlice = append(v.volumesByPodSlice, group)
} else {
group.volumes = append(group.volumes, volume)
}
}
// Sorted returns a slice of all pod volume groups, ordered by
// label.
func (v *volumesByPod) Sorted() []*podVolumeGroup {
sort.Slice(v.volumesByPodSlice, func(i, j int) bool {
return v.volumesByPodSlice[i].label <= v.volumesByPodSlice[j].label
})
return v.volumesByPodSlice
}

View File

@ -19,6 +19,7 @@ package output
import (
"bytes"
"encoding/json"
"sort"
"strings"
"time"
@ -28,7 +29,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func DescribeRestore(restore *v1.Restore, arkClient clientset.Interface) string {
func DescribeRestore(restore *v1.Restore, podVolumeRestores []v1.PodVolumeRestore, volumeDetails bool, arkClient clientset.Interface) string {
return Describe(func(d *Describer) {
d.DescribeMetadata(restore.ObjectMeta)
@ -96,6 +97,11 @@ func DescribeRestore(restore *v1.Restore, arkClient clientset.Interface) string
d.Println()
describeRestoreResults(d, restore, arkClient)
if len(podVolumeRestores) > 0 {
d.Println()
describePodVolumeRestores(d, podVolumeRestores, volumeDetails)
}
})
}
@ -136,3 +142,67 @@ func describeRestoreResult(d *Describer, name string, result v1.RestoreResult) {
}
}
}
// describePodVolumeRestores describes pod volume restores in human-readable format.
func describePodVolumeRestores(d *Describer, restores []v1.PodVolumeRestore, details bool) {
if details {
d.Printf("Restic Restores:\n")
} else {
d.Printf("Restic Restores (specify --volume-details for more information):\n")
}
// separate restores by phase (combining <none> and New into a single group)
restoresByPhase := groupRestoresByPhase(restores)
// go through phases in a specific order
for _, phase := range []string{
string(v1.PodVolumeRestorePhaseCompleted),
string(v1.PodVolumeRestorePhaseFailed),
"In Progress",
string(v1.PodVolumeRestorePhaseNew),
} {
if len(restoresByPhase[phase]) == 0 {
continue
}
// if we're not printing details, just report the phase and count
if !details {
d.Printf("\t%s:\t%d\n", phase, len(restoresByPhase[phase]))
continue
}
// group the restores in the current phase by pod (i.e. "ns/name")
restoresByPod := new(volumesByPod)
for _, restore := range restoresByPhase[phase] {
restoresByPod.Add(restore.Spec.Pod.Namespace, restore.Spec.Pod.Name, restore.Spec.Volume)
}
d.Printf("\t%s:\n", phase)
for _, restoreGroup := range restoresByPod.Sorted() {
sort.Strings(restoreGroup.volumes)
// print volumes restored up for this pod
d.Printf("\t\t%s: %s\n", restoreGroup.label, strings.Join(restoreGroup.volumes, ", "))
}
}
}
func groupRestoresByPhase(restores []v1.PodVolumeRestore) map[string][]v1.PodVolumeRestore {
restoresByPhase := make(map[string][]v1.PodVolumeRestore)
phaseToGroup := map[v1.PodVolumeRestorePhase]string{
v1.PodVolumeRestorePhaseCompleted: string(v1.PodVolumeRestorePhaseCompleted),
v1.PodVolumeRestorePhaseFailed: string(v1.PodVolumeRestorePhaseFailed),
v1.PodVolumeRestorePhaseInProgress: "In Progress",
v1.PodVolumeRestorePhaseNew: string(v1.PodVolumeRestorePhaseNew),
"": string(v1.PodVolumeRestorePhaseNew),
}
for _, restore := range restores {
group := phaseToGroup[restore.Status.Phase]
restoresByPhase[group] = append(restoresByPhase[group], restore)
}
return restoresByPhase
}

View File

@ -176,3 +176,19 @@ func TempCredentialsFile(secretLister corev1listers.SecretLister, arkNamespace,
return name, nil
}
// NewPodVolumeBackupListOptions creates a ListOptions with a label selector configured to
// find PodVolumeBackups for the backup identified by name and uid.
func NewPodVolumeBackupListOptions(name, uid string) metav1.ListOptions {
return metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s,%s=%s", arkv1api.BackupNameLabel, name, arkv1api.BackupUIDLabel, uid),
}
}
// NewPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to
// find PodVolumeRestores for the restore identified by name and uid.
func NewPodVolumeRestoreListOptions(name, uid string) metav1.ListOptions {
return metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s,%s=%s", arkv1api.RestoreNameLabel, name, arkv1api.RestoreUIDLabel, uid),
}
}