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
|
-h, --help help for describe
|
||||||
-l, --selector string only show items matching this label selector
|
-l, --selector string only show items matching this label selector
|
||||||
|
--volume-details display details of restic volume backups
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
|
|
@ -16,6 +16,7 @@ ark describe backups [NAME1] [NAME2] [NAME...] [flags]
|
||||||
```
|
```
|
||||||
-h, --help help for backups
|
-h, --help help for backups
|
||||||
-l, --selector string only show items matching this label selector
|
-l, --selector string only show items matching this label selector
|
||||||
|
--volume-details display details of restic volume backups
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
|
|
@ -16,6 +16,7 @@ ark describe restores [NAME1] [NAME2] [NAME...] [flags]
|
||||||
```
|
```
|
||||||
-h, --help help for restores
|
-h, --help help for restores
|
||||||
-l, --selector string only show items matching this label selector
|
-l, --selector string only show items matching this label selector
|
||||||
|
--volume-details display details of restic volume restores
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
|
|
@ -16,6 +16,7 @@ ark restore describe [NAME1] [NAME2] [NAME...] [flags]
|
||||||
```
|
```
|
||||||
-h, --help help for describe
|
-h, --help help for describe
|
||||||
-l, --selector string only show items matching this label selector
|
-l, --selector string only show items matching this label selector
|
||||||
|
--volume-details display details of restic volume restores
|
||||||
```
|
```
|
||||||
|
|
||||||
### Options inherited from parent commands
|
### Options inherited from parent commands
|
||||||
|
|
|
@ -20,18 +20,22 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
pkgbackup "github.com/heptio/ark/pkg/backup"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/apis/ark/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/client"
|
||||||
"github.com/heptio/ark/pkg/cmd"
|
"github.com/heptio/ark/pkg/cmd"
|
||||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||||
|
"github.com/heptio/ark/pkg/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||||
var listOptions metav1.ListOptions
|
var (
|
||||||
|
listOptions metav1.ListOptions
|
||||||
|
volumeDetails bool
|
||||||
|
)
|
||||||
|
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: use + " [NAME1] [NAME2] [NAME...]",
|
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)
|
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 {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
fmt.Print(s)
|
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().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
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package restore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
@ -26,10 +27,14 @@ import (
|
||||||
"github.com/heptio/ark/pkg/client"
|
"github.com/heptio/ark/pkg/client"
|
||||||
"github.com/heptio/ark/pkg/cmd"
|
"github.com/heptio/ark/pkg/cmd"
|
||||||
"github.com/heptio/ark/pkg/cmd/util/output"
|
"github.com/heptio/ark/pkg/cmd/util/output"
|
||||||
|
"github.com/heptio/ark/pkg/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||||
var listOptions metav1.ListOptions
|
var (
|
||||||
|
listOptions metav1.ListOptions
|
||||||
|
volumeDetails bool
|
||||||
|
)
|
||||||
|
|
||||||
c := &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: use + " [NAME1] [NAME2] [NAME...]",
|
Use: use + " [NAME1] [NAME2] [NAME...]",
|
||||||
|
@ -53,7 +58,13 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
|
||||||
|
|
||||||
first := true
|
first := true
|
||||||
for _, restore := range restores.Items {
|
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 {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
fmt.Print(s)
|
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().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
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package output
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||||
|
@ -25,7 +26,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// DescribeBackup describes a backup in human-readable format.
|
// 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) {
|
return Describe(func(d *Describer) {
|
||||||
d.DescribeMetadata(backup.ObjectMeta)
|
d.DescribeMetadata(backup.ObjectMeta)
|
||||||
|
|
||||||
|
@ -46,6 +47,11 @@ func DescribeBackup(backup *v1.Backup, deleteRequests []v1.DeleteBackupRequest)
|
||||||
d.Println()
|
d.Println()
|
||||||
DescribeDeleteBackupRequests(d, deleteRequests)
|
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
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -28,7 +29,7 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
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) {
|
return Describe(func(d *Describer) {
|
||||||
d.DescribeMetadata(restore.ObjectMeta)
|
d.DescribeMetadata(restore.ObjectMeta)
|
||||||
|
|
||||||
|
@ -96,6 +97,11 @@ func DescribeRestore(restore *v1.Restore, arkClient clientset.Interface) string
|
||||||
|
|
||||||
d.Println()
|
d.Println()
|
||||||
describeRestoreResults(d, restore, arkClient)
|
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
|
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