add pod volume backups/restores to ark backup/restore describe output
Signed-off-by: Steve Kriss <steve@heptio.com>pull/550/head
parent
a70456f5ee
commit
42f2891485
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue