diff --git a/pkg/cmd/util/output/backup_describer.go b/pkg/cmd/util/output/backup_describer.go index 93ca28c7c..454af0cf8 100644 --- a/pkg/cmd/util/output/backup_describer.go +++ b/pkg/cmd/util/output/backup_describer.go @@ -154,68 +154,26 @@ func DescribeUploaderConfigForBackup(d *Describer, spec velerov1api.BackupSpec) // DescribeBackupSpec describes a backup spec in human-readable format. func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) { - // TODO make a helper for this and use it in all the describers. d.Printf("Namespaces:\n") - var s string - if len(spec.IncludedNamespaces) == 0 { - s = "*" - } else { - s = strings.Join(spec.IncludedNamespaces, ", ") - } - d.Printf("\tIncluded:\t%s\n", s) - if len(spec.ExcludedNamespaces) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedNamespaces, ", ") - } - d.Printf("\tExcluded:\t%s\n", s) + d.Printf("\tIncluded:\t%s\n", JoinStringWithFallback(spec.IncludedNamespaces, "*")) + d.Printf("\tExcluded:\t%s\n", JoinStringWithFallback(spec.ExcludedNamespaces, emptyDisplay)) d.Println() d.Printf("Resources:\n") if collections.UseOldResourceFilters(spec) { - if len(spec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(spec.IncludedResources, ", ") - } - d.Printf("\tIncluded:\t%s\n", s) - if len(spec.ExcludedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedResources, ", ") - } - d.Printf("\tExcluded:\t%s\n", s) + d.Printf("\tIncluded:\t%s\n", JoinStringWithFallback(spec.IncludedResources, "*")) + d.Printf("\tExcluded:\t%s\n", JoinStringWithFallback(spec.ExcludedResources, emptyDisplay)) d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(spec.IncludeClusterResources, "excluded", "included", "auto")) } else { - if len(spec.IncludedClusterScopedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.IncludedClusterScopedResources, ", ") - } - d.Printf("\tIncluded cluster-scoped:\t%s\n", s) - if len(spec.ExcludedClusterScopedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedClusterScopedResources, ", ") - } - d.Printf("\tExcluded cluster-scoped:\t%s\n", s) + d.Printf("\tIncluded cluster-scoped:\t%s\n", JoinStringWithFallback(spec.IncludedClusterScopedResources, emptyDisplay)) + d.Printf("\tExcluded cluster-scoped:\t%s\n", JoinStringWithFallback(spec.ExcludedClusterScopedResources, emptyDisplay)) - if len(spec.IncludedNamespaceScopedResources) == 0 { - s = "*" - } else { - s = strings.Join(spec.IncludedNamespaceScopedResources, ", ") - } - d.Printf("\tIncluded namespace-scoped:\t%s\n", s) - if len(spec.ExcludedNamespaceScopedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedNamespaceScopedResources, ", ") - } - d.Printf("\tExcluded namespace-scoped:\t%s\n", s) + d.Printf("\tIncluded namespace-scoped:\t%s\n", JoinStringWithFallback(spec.IncludedNamespaceScopedResources, "*")) + d.Printf("\tExcluded namespace-scoped:\t%s\n", JoinStringWithFallback(spec.ExcludedNamespaceScopedResources, emptyDisplay)) } d.Println() - s = emptyDisplay + s := emptyDisplay if spec.LabelSelector != nil { s = metav1.FormatLabelSelector(spec.LabelSelector) } @@ -269,34 +227,13 @@ func DescribeBackupSpec(d *Describer, spec velerov1api.BackupSpec) { for _, backupResourceHookSpec := range spec.Hooks.Resources { d.Printf("\t\t%s:\n", backupResourceHookSpec.Name) d.Printf("\t\t\tNamespaces:\n") - var s string - if len(backupResourceHookSpec.IncludedNamespaces) == 0 { - s = "*" - } else { - s = strings.Join(backupResourceHookSpec.IncludedNamespaces, ", ") - } - d.Printf("\t\t\t\tIncluded:\t%s\n", s) - if len(backupResourceHookSpec.ExcludedNamespaces) == 0 { - s = emptyDisplay - } else { - s = strings.Join(backupResourceHookSpec.ExcludedNamespaces, ", ") - } - d.Printf("\t\t\t\tExcluded:\t%s\n", s) + d.Printf("\t\t\t\tIncluded:\t%s\n", JoinStringWithFallback(backupResourceHookSpec.IncludedNamespaces, "*")) + d.Printf("\t\t\t\tExcluded:\t%s\n", JoinStringWithFallback(backupResourceHookSpec.ExcludedNamespaces, emptyDisplay)) d.Println() d.Printf("\t\t\tResources:\n") - if len(backupResourceHookSpec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(backupResourceHookSpec.IncludedResources, ", ") - } - d.Printf("\t\t\t\tIncluded:\t%s\n", s) - if len(backupResourceHookSpec.ExcludedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(backupResourceHookSpec.ExcludedResources, ", ") - } - d.Printf("\t\t\t\tExcluded:\t%s\n", s) + d.Printf("\t\t\t\tIncluded:\t%s\n", JoinStringWithFallback(backupResourceHookSpec.IncludedResources, "*")) + d.Printf("\t\t\t\tExcluded:\t%s\n", JoinStringWithFallback(backupResourceHookSpec.ExcludedResources, emptyDisplay)) d.Println() s = emptyDisplay diff --git a/pkg/cmd/util/output/backup_structured_describer.go b/pkg/cmd/util/output/backup_structured_describer.go index ef5ed4269..e96f9b788 100644 --- a/pkg/cmd/util/output/backup_structured_describer.go +++ b/pkg/cmd/util/output/backup_structured_describer.go @@ -83,35 +83,14 @@ func DescribeBackupSpecInSF(d *StructuredDescriber, spec velerov1api.BackupSpec) // describe namespaces namespaceInfo := make(map[string]any) - if len(spec.IncludedNamespaces) == 0 { - s = "*" - } else { - s = strings.Join(spec.IncludedNamespaces, ", ") - } - namespaceInfo["included"] = s - if len(spec.ExcludedNamespaces) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedNamespaces, ", ") - } - namespaceInfo["excluded"] = s + namespaceInfo["included"] = JoinStringWithFallback(spec.IncludedNamespaces, "*") + namespaceInfo["excluded"] = JoinStringWithFallback(spec.ExcludedNamespaces, emptyDisplay) backupSpecInfo["namespaces"] = namespaceInfo // describe resources resourcesInfo := make(map[string]string) - if len(spec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(spec.IncludedResources, ", ") - } - resourcesInfo["included"] = s - - if len(spec.ExcludedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(spec.ExcludedResources, ", ") - } - resourcesInfo["excluded"] = s + resourcesInfo["included"] = JoinStringWithFallback(spec.IncludedResources, "*") + resourcesInfo["excluded"] = JoinStringWithFallback(spec.ExcludedResources, emptyDisplay) resourcesInfo["clusterScoped"] = BoolPointerString(spec.IncludeClusterResources, "excluded", "included", "auto") backupSpecInfo["resources"] = resourcesInfo @@ -153,33 +132,13 @@ func DescribeBackupSpecInSF(d *StructuredDescriber, spec velerov1api.BackupSpec) ResourceDetails := make(map[string]any) var s string namespaceInfo := make(map[string]string) - if len(backupResourceHookSpec.IncludedNamespaces) == 0 { - s = "*" - } else { - s = strings.Join(backupResourceHookSpec.IncludedNamespaces, ", ") - } - namespaceInfo["included"] = s - if len(backupResourceHookSpec.ExcludedNamespaces) == 0 { - s = emptyDisplay - } else { - s = strings.Join(backupResourceHookSpec.ExcludedNamespaces, ", ") - } - namespaceInfo["excluded"] = s + namespaceInfo["included"] = JoinStringWithFallback(backupResourceHookSpec.IncludedNamespaces, "*") + namespaceInfo["excluded"] = JoinStringWithFallback(backupResourceHookSpec.ExcludedNamespaces, emptyDisplay) ResourceDetails["namespaces"] = namespaceInfo resourcesInfo := make(map[string]string) - if len(backupResourceHookSpec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(backupResourceHookSpec.IncludedResources, ", ") - } - resourcesInfo["included"] = s - if len(backupResourceHookSpec.ExcludedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(backupResourceHookSpec.ExcludedResources, ", ") - } - resourcesInfo["excluded"] = s + resourcesInfo["included"] = JoinStringWithFallback(backupResourceHookSpec.IncludedResources, "*") + resourcesInfo["excluded"] = JoinStringWithFallback(backupResourceHookSpec.ExcludedResources, emptyDisplay) ResourceDetails["resources"] = resourcesInfo s = emptyDisplay diff --git a/pkg/cmd/util/output/describe.go b/pkg/cmd/util/output/describe.go index 049308a03..147d57df8 100644 --- a/pkg/cmd/util/output/describe.go +++ b/pkg/cmd/util/output/describe.go @@ -110,6 +110,14 @@ func (d *Describer) DescribeSlice(preindent int, name string, s []string) { } } +// JoinStringWithFallback returns a comma-separated string of the slice, or the fallback string if the slice is empty. +func JoinStringWithFallback(s []string, fallback string) string { + if len(s) == 0 { + return fallback + } + return strings.Join(s, ", ") +} + // BoolPointerString returns the appropriate string based on the bool pointer's value. func BoolPointerString(b *bool, falseString, trueString, nilString string) string { if b == nil { diff --git a/pkg/cmd/util/output/describe_test.go b/pkg/cmd/util/output/describe_test.go index 02e8cd53e..294129571 100644 --- a/pkg/cmd/util/output/describe_test.go +++ b/pkg/cmd/util/output/describe_test.go @@ -166,3 +166,43 @@ func TestStructuredDescriber_DescribeMetadata(t *testing.T) { assert.True(t, reflect.DeepEqual(expect, d.output)) } + +func TestJoinStringWithFallback(t *testing.T) { + testcases := []struct { + name string + input []string + fallback string + expect string + }{ + { + name: "empty slice", + input: []string{}, + fallback: "fallback-str", + expect: "fallback-str", + }, + { + name: "nil slice", + input: nil, + fallback: "fallback-str", + expect: "fallback-str", + }, + { + name: "single element", + input: []string{"a"}, + fallback: "fallback-str", + expect: "a", + }, + { + name: "multiple elements", + input: []string{"a", "b", "c"}, + fallback: "fallback-str", + expect: "a, b, c", + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got := JoinStringWithFallback(tc.input, tc.fallback) + assert.Equal(t, tc.expect, got) + }) + } +} diff --git a/pkg/cmd/util/output/restore_describer.go b/pkg/cmd/util/output/restore_describer.go index 0258a7fc0..e12647d2b 100644 --- a/pkg/cmd/util/output/restore_describer.go +++ b/pkg/cmd/util/output/restore_describer.go @@ -118,35 +118,17 @@ func DescribeRestore( d.Println() d.Printf("Namespaces:\n") var s string - if len(restore.Spec.IncludedNamespaces) == 0 { + s = JoinStringWithFallback(restore.Spec.IncludedNamespaces, "all namespaces found in the backup") + if s == "*" { s = "all namespaces found in the backup" - } else if len(restore.Spec.IncludedNamespaces) == 1 && restore.Spec.IncludedNamespaces[0] == "*" { - s = "all namespaces found in the backup" - } else { - s = strings.Join(restore.Spec.IncludedNamespaces, ", ") } d.Printf("\tIncluded:\t%s\n", s) - if len(restore.Spec.ExcludedNamespaces) == 0 { - s = emptyDisplay - } else { - s = strings.Join(restore.Spec.ExcludedNamespaces, ", ") - } - d.Printf("\tExcluded:\t%s\n", s) + d.Printf("\tExcluded:\t%s\n", JoinStringWithFallback(restore.Spec.ExcludedNamespaces, emptyDisplay)) d.Println() d.Printf("Resources:\n") - if len(restore.Spec.IncludedResources) == 0 { - s = "*" - } else { - s = strings.Join(restore.Spec.IncludedResources, ", ") - } - d.Printf("\tIncluded:\t%s\n", s) - if len(restore.Spec.ExcludedResources) == 0 { - s = emptyDisplay - } else { - s = strings.Join(restore.Spec.ExcludedResources, ", ") - } - d.Printf("\tExcluded:\t%s\n", s) + d.Printf("\tIncluded:\t%s\n", JoinStringWithFallback(restore.Spec.IncludedResources, "*")) + d.Printf("\tExcluded:\t%s\n", JoinStringWithFallback(restore.Spec.ExcludedResources, emptyDisplay)) d.Printf("\tCluster-scoped:\t%s\n", BoolPointerString(restore.Spec.IncludeClusterResources, "excluded", "included", "auto"))