diff --git a/cmd/root.go b/cmd/root.go index 599779333..62282d398 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -166,6 +166,14 @@ var veleroCommandPattern = regexp.MustCompile(`(?m)(?:^|[\s\x60])velero\s+(?:` + `(?:version|install|uninstall|plugin|snapshot-location|backup-location|restic|repo|client|completion|bug|debug|datamover)` + `)`) +// veleroCreateVerbFirstPattern matches upstream Velero's "velero create " example +// text (verb-first), which some commands hardcode instead of the actual resource-first +// invocation. For example, velero's schedule create command is invoked as +// "velero schedule create NAME", but its upstream Example text says "velero create schedule +// NAME". veleroCommandPattern above doesn't catch this ordering, so it's normalized to +// resource-first form here before the general replacement runs. +var veleroCreateVerbFirstPattern = regexp.MustCompile(`(^|[\s\x60\n])velero\s+create\s+(backup|restore|schedule)\b`) + // replaceVeleroCommandWithOADP performs context-aware replacement of "velero" with "oadp". // It only replaces "velero" when it's being used as a CLI command, not when referring to // the Velero project, server, or components. @@ -174,6 +182,11 @@ func replaceVeleroCommandWithOADP(text string) string { // Use "oc" as the CLI prefix since OADP is primarily used on OpenShift cliPrefix := "oc" + // Normalize verb-first "velero create " examples to resource-first + // "oc oadp create" before the general pattern runs, since the actual + // command is always invoked resource-first. + text = veleroCreateVerbFirstPattern.ReplaceAllString(text, "${1}"+cliPrefix+" oadp ${2} create") + // Replace "velero " patterns with "oc/kubectl oadp " result := veleroCommandPattern.ReplaceAllStringFunc(text, func(match string) string { // Preserve leading whitespace or backtick diff --git a/cmd/root_test.go b/cmd/root_test.go index 511ed9a32..ad37ff598 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -219,6 +219,34 @@ Use velero backup logs to check status`, } } +// TestReplaceVeleroWithOADP_ScheduleCreateVerbFirst tests that upstream Velero's +// verb-first "velero create schedule NAME" example text (a known upstream quirk in +// schedule/create.go) is normalized to the resource-first form that actually matches +// how the command is invoked, consistent with "backup create" and "restore create". +func TestReplaceVeleroWithOADP_ScheduleCreateVerbFirst(t *testing.T) { + cmd := &cobra.Command{ + Use: "test", + Example: ` # Create a backup every 6 hours. + velero create schedule NAME --schedule="0 */6 * * *" + + # Create a backup every 6 hours with the @every notation. + velero create schedule NAME --schedule="@every 6h"`, + } + + replaceVeleroWithOADP(cmd) + + if strings.Contains(cmd.Example, "velero create schedule") { + t.Errorf("Expected verb-first 'velero create schedule' to be normalized, got: %s", cmd.Example) + } + if strings.Contains(cmd.Example, "velero") { + t.Errorf("Expected 'velero' to be fully replaced, got: %s", cmd.Example) + } + wantCount := strings.Count(cmd.Example, "oc oadp schedule create NAME") + if wantCount != 2 { + t.Errorf("Expected 2 occurrences of 'oc oadp schedule create NAME', got %d\nActual output:\n%s", wantCount, cmd.Example) + } +} + // TestReplaceVeleroWithOADP_RunFunctionWrapper tests stdout capture and replacement func TestReplaceVeleroWithOADP_RunFunctionWrapper(t *testing.T) { outputCaptured := false