Skip to content

Commit

Permalink
return information about combined backup on backup page (#5087)
Browse files Browse the repository at this point in the history
* return information about combined backup on backup page

* fix unit tests

* reuse ListInstanceBackups code

* undo changes to ListBackupsForApp

* whitespace
  • Loading branch information
laverya authored Jan 10, 2025
1 parent 3531b8d commit 984c729
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 46 deletions.
28 changes: 28 additions & 0 deletions pkg/handlers/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ func (h *Handler) ListInstanceBackups(w http.ResponseWriter, r *http.Request) {

type GetBackupResponse struct {
BackupDetails []snapshottypes.BackupDetail `json:"backupDetails"`
Backup *snapshottypes.Backup `json:"backup"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
Expand All @@ -147,6 +148,33 @@ func (h *Handler) GetBackup(w http.ResponseWriter, r *http.Request) {
}
getBackupResponse.BackupDetails = backups

thisBackup, err := snapshot.GetInstanceBackup(r.Context(), util.PodNamespace, mux.Vars(r)["snapshotName"])
if err != nil {
if errors.Is(err, snapshot.BackupNotFoundError{}) {
// attempt to get the backup by name directly
rawBackup, err := snapshot.GetBackup(r.Context(), util.PodNamespace, mux.Vars(r)["snapshotName"])
if err != nil {
logger.Error(err)
getBackupResponse.Error = "failed to get backup"
JSON(w, 500, getBackupResponse)
return
}
thisBackup, err = snapshot.ParseVeleroBackup(r.Context(), *rawBackup)
if err != nil {
logger.Error(err)
getBackupResponse.Error = "failed to parse backup"
JSON(w, 500, getBackupResponse)
return
}
} else {
logger.Error(err)
getBackupResponse.Error = "failed to get backup"
JSON(w, 500, getBackupResponse)
return
}

}
getBackupResponse.Backup = thisBackup
getBackupResponse.Success = true

JSON(w, http.StatusOK, getBackupResponse)
Expand Down
122 changes: 76 additions & 46 deletions pkg/kotsadmsnapshot/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -743,63 +743,71 @@ func ListBackupsForApp(ctx context.Context, kotsadmNamespace string, appID strin
continue
}

backup := types.Backup{
Name: veleroBackup.Name,
Status: types.GetStatusFromBackupPhase(veleroBackup.Status.Phase),
AppID: appID,
back, err := ParseVeleroBackup(ctx, veleroBackup)
if err != nil {
return nil, errors.Wrap(err, "failed to parse velero backup")
}

if veleroBackup.Status.StartTimestamp != nil {
startedAt := veleroBackup.Status.StartTimestamp.Time.UTC()
backup.StartedAt = &startedAt
}
if veleroBackup.Status.CompletionTimestamp != nil {
finishedAt := veleroBackup.Status.CompletionTimestamp.Time.UTC()
backup.FinishedAt = &finishedAt
}
if veleroBackup.Status.Expiration != nil {
expiresAt := veleroBackup.Status.Expiration.Time.UTC()
backup.ExpiresAt = &expiresAt
}
sequence, ok := veleroBackup.Annotations["kots.io/app-sequence"]
if ok {
s, err := strconv.ParseInt(sequence, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse app sequence")
}
back.AppID = appID
backups = append(backups, back)
}

backup.Sequence = s
}
if backup.Status == "" {
backup.Status = types.BackupStatusInProgress
}
return backups, nil
}

trigger, ok := veleroBackup.Annotations[types.BackupTriggerAnnotation]
if ok {
backup.Trigger = trigger
}
func ParseVeleroBackup(ctx context.Context, veleroBackup velerov1.Backup) (*types.Backup, error) {
backup := types.Backup{
Name: veleroBackup.Name,
Status: types.GetStatusFromBackupPhase(veleroBackup.Status.Phase),
}

supportBundleID, ok := veleroBackup.Annotations["kots.io/support-bundle-id"]
if ok {
backup.SupportBundleID = supportBundleID
if veleroBackup.Status.StartTimestamp != nil {
startedAt := veleroBackup.Status.StartTimestamp.Time.UTC()
backup.StartedAt = &startedAt
}
if veleroBackup.Status.CompletionTimestamp != nil {
finishedAt := veleroBackup.Status.CompletionTimestamp.Time.UTC()
backup.FinishedAt = &finishedAt
}
if veleroBackup.Status.Expiration != nil {
expiresAt := veleroBackup.Status.Expiration.Time.UTC()
backup.ExpiresAt = &expiresAt
}
sequence, ok := veleroBackup.Annotations["kots.io/app-sequence"]
if ok {
s, err := strconv.ParseInt(sequence, 10, 64)
if err != nil {
return nil, errors.Wrap(err, "failed to parse app sequence")
}

if backup.Status != types.BackupStatusInProgress {
volumeSummary, err := getSnapshotVolumeSummary(ctx, &veleroBackup)
if err != nil {
return nil, errors.Wrap(err, "failed to get volume summary")
}
backup.Sequence = s
}
if backup.Status == "" {
backup.Status = types.BackupStatusInProgress
}

backup.VolumeCount = volumeSummary.VolumeCount
backup.VolumeSuccessCount = volumeSummary.VolumeSuccessCount
backup.VolumeBytes = volumeSummary.VolumeBytes
backup.VolumeSizeHuman = volumeSummary.VolumeSizeHuman
}
trigger, ok := veleroBackup.Annotations[types.BackupTriggerAnnotation]
if ok {
backup.Trigger = trigger
}

backups = append(backups, &backup)
supportBundleID, ok := veleroBackup.Annotations["kots.io/support-bundle-id"]
if ok {
backup.SupportBundleID = supportBundleID
}

return backups, nil
if backup.Status != types.BackupStatusInProgress {
volumeSummary, err := getSnapshotVolumeSummary(ctx, &veleroBackup)
if err != nil {
return nil, errors.Wrap(err, "failed to get volume summary")
}

backup.VolumeCount = volumeSummary.VolumeCount
backup.VolumeSuccessCount = volumeSummary.VolumeSuccessCount
backup.VolumeBytes = volumeSummary.VolumeBytes
backup.VolumeSizeHuman = volumeSummary.VolumeSizeHuman
}
return &backup, nil
}

func ListInstanceBackups(ctx context.Context, kotsadmNamespace string) ([]*types.Backup, error) {
Expand Down Expand Up @@ -915,6 +923,28 @@ func getBackupsFromVeleroBackups(ctx context.Context, veleroBackups []velerov1.B
return backups, nil
}

type BackupNotFoundError struct{}

func (e BackupNotFoundError) Error() string {
return "backup not found"
}

// GetInstanceBackup calls ListInstanceBackups and returns the first backup with the given name.
// This is done because multiple velero backup objects are combined into a single kots backup object,
// and the easiest way to do that is to generate the entire set of kots backup objects again.
func GetInstanceBackup(ctx context.Context, namespace string, backupName string) (*types.Backup, error) {
allBackups, err := ListInstanceBackups(ctx, namespace)
if err != nil {
return nil, err
}
for _, backup := range allBackups {
if backup.Name == backupName {
return backup, nil
}
}
return nil, BackupNotFoundError{}
}

// getAppsFromAppSequences returns a list of `App` structs from the backup sequence annotation.
func getAppsFromAppSequences(veleroBackup velerov1.Backup) ([]types.App, error) {
apps := []types.App{}
Expand Down

0 comments on commit 984c729

Please sign in to comment.