mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 19:56:06 +00:00
Print n/a for unset timestamps in backup and restore get (#10206)
velero backup get prints <nil> in CREATED for a backup that never started, and velero restore get prints it in both STARTED and COMPLETED. The timestamps are *metav1.Time and are appended to the row unformatted, so a nil pointer reaches the user as Go's nil literal. This is reachable in ordinary use. A backup that fails validation never starts, so StartTimestamp is never set, and a restore that fails validation gets neither timestamp. formatTimestamp returns n/a for an unset value, matching humanReadableTimeFromNow, which already handles a zero expiration in the same row. A set timestamp is unchanged. Adds tests for both printers, which had no row-level coverage. Signed-off-by: saral <ilovegojo2580@gmail.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Show n/a instead of <nil> for unset timestamps in velero backup get and velero restore get
|
||||
@@ -107,7 +107,7 @@ func printBackup(backup *velerov1api.Backup) []metav1.TableRow {
|
||||
status,
|
||||
backup.Status.Errors,
|
||||
backup.Status.Warnings,
|
||||
backup.Status.StartTimestamp,
|
||||
formatTimestamp(backup.Status.StartTimestamp),
|
||||
humanReadableTimeFromNow(expiration),
|
||||
backup.Spec.StorageLocation,
|
||||
queuePosition(backup.Status.QueuePosition),
|
||||
|
||||
@@ -248,3 +248,17 @@ func NewPrinter(cmd *cobra.Command) (printers.ResourcePrinter, error) {
|
||||
|
||||
return printer, nil
|
||||
}
|
||||
|
||||
// formatTimestamp renders an optional timestamp for a table cell.
|
||||
//
|
||||
// Appending a nil *metav1.Time to a row prints "<nil>", which reaches the user
|
||||
// for any object that has not reached the phase that sets the field: a backup
|
||||
// that failed validation never gets a start time, and a restore that failed
|
||||
// validation gets neither a start nor a completion time. An unset timestamp
|
||||
// shows as "n/a" instead, matching humanReadableTimeFromNow in the same row.
|
||||
func formatTimestamp(t *metav1.Time) string {
|
||||
if t == nil || t.IsZero() {
|
||||
return "n/a"
|
||||
}
|
||||
return t.String()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Copyright the Velero 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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
||||
)
|
||||
|
||||
func TestFormatTimestamp(t *testing.T) {
|
||||
set := metav1.NewTime(time.Date(2026, 8, 8, 21, 6, 28, 0, time.UTC))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input *metav1.Time
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "nil renders as n/a",
|
||||
input: nil,
|
||||
want: "n/a",
|
||||
},
|
||||
{
|
||||
name: "zero value renders as n/a",
|
||||
input: &metav1.Time{},
|
||||
want: "n/a",
|
||||
},
|
||||
{
|
||||
name: "a set timestamp is unchanged",
|
||||
input: &set,
|
||||
want: set.String(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.want, formatTimestamp(tc.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// A backup that fails validation never starts, so StartTimestamp stays nil.
|
||||
func TestPrintBackupWithoutStartTimestamp(t *testing.T) {
|
||||
backup := &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "failed-validation"},
|
||||
Status: velerov1api.BackupStatus{
|
||||
Phase: velerov1api.BackupPhaseFailedValidation,
|
||||
},
|
||||
}
|
||||
|
||||
rows := printBackup(backup)
|
||||
require.Len(t, rows, 1)
|
||||
|
||||
// Name, Status, Errors, Warnings, Created, ...
|
||||
assert.Equal(t, "n/a", rows[0].Cells[4], "unset start time should not print as <nil>")
|
||||
assert.Equal(t, string(velerov1api.BackupPhaseFailedValidation), rows[0].Cells[1])
|
||||
}
|
||||
|
||||
func TestPrintBackupWithStartTimestamp(t *testing.T) {
|
||||
started := metav1.NewTime(time.Date(2026, 8, 8, 21, 6, 28, 0, time.UTC))
|
||||
backup := &velerov1api.Backup{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed"},
|
||||
Status: velerov1api.BackupStatus{
|
||||
Phase: velerov1api.BackupPhaseCompleted,
|
||||
StartTimestamp: &started,
|
||||
},
|
||||
}
|
||||
|
||||
rows := printBackup(backup)
|
||||
require.Len(t, rows, 1)
|
||||
assert.Equal(t, started.String(), rows[0].Cells[4])
|
||||
}
|
||||
|
||||
// A restore that fails validation gets neither timestamp.
|
||||
func TestPrintRestoreWithoutTimestamps(t *testing.T) {
|
||||
restore := &velerov1api.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "failed-validation"},
|
||||
Spec: velerov1api.RestoreSpec{BackupName: "does-not-exist"},
|
||||
Status: velerov1api.RestoreStatus{
|
||||
Phase: velerov1api.RestorePhaseFailedValidation,
|
||||
},
|
||||
}
|
||||
|
||||
rows := printRestore(restore)
|
||||
require.Len(t, rows, 1)
|
||||
|
||||
// Name, Backup, Status, Started, Completed, ...
|
||||
assert.Equal(t, "n/a", rows[0].Cells[3], "unset start time should not print as <nil>")
|
||||
assert.Equal(t, "n/a", rows[0].Cells[4], "unset completion time should not print as <nil>")
|
||||
}
|
||||
|
||||
func TestPrintRestoreWithTimestamps(t *testing.T) {
|
||||
started := metav1.NewTime(time.Date(2026, 8, 8, 21, 9, 40, 0, time.UTC))
|
||||
completed := metav1.NewTime(time.Date(2026, 8, 8, 21, 9, 41, 0, time.UTC))
|
||||
|
||||
restore := &velerov1api.Restore{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "completed"},
|
||||
Spec: velerov1api.RestoreSpec{BackupName: "nightly-1"},
|
||||
Status: velerov1api.RestoreStatus{
|
||||
Phase: velerov1api.RestorePhaseCompleted,
|
||||
StartTimestamp: &started,
|
||||
CompletionTimestamp: &completed,
|
||||
},
|
||||
}
|
||||
|
||||
rows := printRestore(restore)
|
||||
require.Len(t, rows, 1)
|
||||
assert.Equal(t, started.String(), rows[0].Cells[3])
|
||||
assert.Equal(t, completed.String(), rows[0].Cells[4])
|
||||
}
|
||||
@@ -62,8 +62,8 @@ func printRestore(restore *v1.Restore) []metav1.TableRow {
|
||||
restore.Name,
|
||||
restore.Spec.BackupName,
|
||||
status,
|
||||
restore.Status.StartTimestamp,
|
||||
restore.Status.CompletionTimestamp,
|
||||
formatTimestamp(restore.Status.StartTimestamp),
|
||||
formatTimestamp(restore.Status.CompletionTimestamp),
|
||||
restore.Status.Errors,
|
||||
restore.Status.Warnings,
|
||||
restore.CreationTimestamp.Time,
|
||||
|
||||
Reference in New Issue
Block a user