Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 185 additions & 3 deletions internal/cmd/credentialcmd/credentialcmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9866,8 +9866,11 @@ func TestHuhInitKeyringBackendPrompterAccessibleShowsField(t *testing.T) {
if len(rows) == 0 {
t.Fatal("initSecretsManagementInventoryRows returned no rows")
}
if rows[0].Title != "Default credential store (Automatic OS default)" {
t.Fatalf("first row title = %q, want default credential-store row first", rows[0].Title)
if rows[0].Title != "Fallback credential store: "+initAutomaticOSDefaultSecretsBackendLabel() {
t.Fatalf("first row title = %q, want fallback credential-store row first", rows[0].Title)
}
if !strings.Contains(rows[0].Description, "Legacy fallback credential store") {
t.Fatalf("first row description = %q, want legacy fallback wording", rows[0].Description)
}
var foundConfigure bool
for _, row := range rows {
Expand All @@ -9883,6 +9886,29 @@ func TestHuhInitKeyringBackendPrompterAccessibleShowsField(t *testing.T) {
if len(options) == 0 {
t.Fatal("initLegacySecretsBackendOptions returned no options")
}
if options[0].Key != initAutomaticOSDefaultSecretsBackendLabel() {
t.Fatalf("first legacy backend option = %q, want platform-aware automatic OS default", options[0].Key)
}
}

func TestInitAutomaticOSDefaultSecretsBackendLabelForGOOS(t *testing.T) {
tests := []struct {
goos string
want string
}{
{goos: "darwin", want: "Automatic OS default (macOS Keychain)"},
{goos: "windows", want: "Automatic OS default (Windows Credential Manager)"},
{goos: "linux", want: "Automatic OS default (Linux Secret Service)"},
{goos: "plan9", want: "Automatic OS default"},
}

for _, tt := range tests {
t.Run(tt.goos, func(t *testing.T) {
if got := initAutomaticOSDefaultSecretsBackendLabelForGOOS(tt.goos); got != tt.want {
t.Fatalf("initAutomaticOSDefaultSecretsBackendLabelForGOOS(%q) = %q, want %q", tt.goos, got, tt.want)
}
})
}
}

func TestInitSecretsManagementInventoryRowsUsePreferredBackendOrder(t *testing.T) {
Expand All @@ -9892,7 +9918,7 @@ func TestInitSecretsManagementInventoryRowsUsePreferredBackendOrder(t *testing.T
titles = append(titles, row.Title)
}
assertContentOrder(t, strings.Join(titles, "\n"),
"Default credential store",
"Fallback credential store",
"Configure new macos keychain profile",
"Configure new pass password store profile",
"Configure new encrypted file profile",
Expand Down Expand Up @@ -10529,6 +10555,114 @@ func TestHuhInitKeyringBackendPrompterStagesNewSecretsProfileEndToEnd(t *testing
}
}

func TestHuhInitKeyringBackendPrompterAccessibleLegacyFallbackFormShowsFallbackCopy(t *testing.T) {
t.Setenv("TERM", "dumb")
var stderr bytes.Buffer
prompter := huhInitKeyringBackendPrompter{
stdin: strings.NewReader(strings.Join([]string{
"", // keep selected memory backend
"", // stage fallback settings
"",
}, "\n")),
stderr: &stderr,
}

edit, err := prompter.editLegacySecretsManagement(string(credstore.BackendMemory))
if err != nil {
t.Fatalf("editLegacySecretsManagement: %v", err)
}
if !edit.Apply || edit.Backend != string(credstore.BackendMemory) {
t.Fatalf("edit = %#v, want staged memory fallback backend", edit)
}
out := stderr.String()
for _, want := range []string{
"Legacy fallback credential store backend",
"Fallback credential-store action",
"Stage fallback credential-store settings",
initAutomaticOSDefaultSecretsBackendLabel(),
"In-memory store",
} {
if !strings.Contains(out, want) {
t.Fatalf("legacy fallback form output missing %q:\n%s", want, out)
}
}
for _, stale := range []string{
"Default Credential Store",
"Default credential store backend",
"Default credential-store action",
"Stage default credential-store settings",
} {
if strings.Contains(out, stale) {
t.Fatalf("legacy fallback form output contains stale copy %q:\n%s", stale, out)
}
}
}

func TestHuhInitKeyringBackendPrompterInventoryLegacyFallbackStagesBackend(t *testing.T) {
t.Setenv("TERM", "dumb")
memoryChoice := 0
for index, option := range initLegacySecretsBackendOptions("") {
if option.Value == string(credstore.BackendMemory) {
memoryChoice = index + 1
break
}
}
if memoryChoice == 0 {
t.Fatal("memory backend option missing from legacy fallback backend options")
}
var stderr bytes.Buffer
callCount := 0
prompter := huhInitKeyringBackendPrompter{
stdin: strings.NewReader(fmt.Sprintf("%d\n1\n", memoryChoice)),
stderr: &stderr,
inventoryRunner: func(_ initInventoryPrompt, _ io.Reader, _ io.Writer) (initInventoryResult, error) {
callCount++
switch callCount {
case 1:
return initInventoryResult{
Action: initInventoryActionCommand,
Row: initInventoryRow{
ID: initSecretsManagementLegacySelection,
Title: initLegacySecretsManagementInventoryTitle(config.File{}),
},
}, nil
case 2:
return initInventoryResult{
Action: initInventoryActionBack,
Row: initInventoryRow{ID: initBackSelection},
}, nil
default:
t.Fatalf("unexpected inventory call %d", callCount)
return initInventoryResult{}, nil
}
},
}

edit, err := prompter.EditKeyringBackend(initKeyringBackendPrompt{
Config: config.File{Profiles: map[string]config.Profile{"default": basicProfile("default")}, DefaultProfile: "default"},
})
if err != nil {
t.Fatalf("EditKeyringBackend: %v", err)
}
if !edit.Apply || !edit.HasConfigEdit {
t.Fatalf("edit = %#v, want staged config edit", edit)
}
if got := edit.Config.Keyring.Backend; got != string(credstore.BackendMemory) {
t.Fatalf("keyring.backend = %q, want memory after legacy fallback inventory path", got)
}
out := stderr.String()
for _, want := range []string{
"Legacy fallback credential store backend",
"Fallback credential-store action",
"Stage fallback credential-store settings",
"In-memory store",
} {
if !strings.Contains(out, want) {
t.Fatalf("legacy fallback inventory path output missing %q:\n%s", want, out)
}
}
}

func TestHuhInitKeyringBackendPrompterDefaultUsesLinearSecretsManagementFlow(t *testing.T) {
var stderr bytes.Buffer
prompter := huhInitKeyringBackendPrompter{
Expand Down Expand Up @@ -10587,6 +10721,54 @@ func TestHuhInitKeyringBackendPrompterDefaultUsesLinearSecretsManagementFlow(t *
}
}

func TestInitSecretsManagementLinearEditorShowsLegacyFallbackWording(t *testing.T) {
cfg := config.File{Profiles: map[string]config.Profile{"default": basicProfile("default")}, DefaultProfile: "default"}
editor := initSecretsManagementLinearEditor(cfg)
model := newInitLinearEditorModel(editor, 180, 32)
out := model.layout.Content
for _, want := range []string{
"Secrets-management target",
"Fallback credential store: " + initAutomaticOSDefaultSecretsBackendLabel(),
"Legacy fallback credential store",
"Compatibility path for profiles that do not choose a named secrets-management profile.",
"Fallback persistent backend",
initAutomaticOSDefaultSecretsBackendLabel(),
} {
if !strings.Contains(out, want) {
t.Fatalf("initial linear editor output missing %q:\n%s", want, out)
}
}
if strings.Contains(out, "Default credential store") || strings.Contains(out, "Legacy persistent backend") {
t.Fatalf("initial linear editor output contains redundant legacy copy:\n%s", out)
}

model = selectInitLinearFieldValue(t, model, initSecretsManagementFieldLegacyBackend, string(credstore.BackendMemory))
index := model.document.fieldIndexByID(initSecretsManagementFieldLegacyBackend)
if index < 0 {
t.Fatal("legacy backend field missing")
}
description := model.document[index].Description
for _, want := range []string{
"Ephemeral",
"tests or CI",
"not normal local use",
"legacy fallback backend",
} {
if !strings.Contains(description, want) {
t.Fatalf("legacy backend description = %q, want %q", description, want)
}
}
for _, want := range []string{
"Keep credentials in memory only for this process.",
"Ephemeral; best suited for tests or CI, not normal local use.",
"[x] In-memory store",
} {
if !strings.Contains(model.layout.Content, want) {
t.Fatalf("memory-selected linear editor output missing %q:\n%s", want, model.layout.Content)
}
}
}

func TestHuhInitKeyringBackendPrompterLinearCanDeleteConfiguredSecretsProfile(t *testing.T) {
cfg := config.File{
Profiles: map[string]config.Profile{"default": basicProfile("default")},
Expand Down
37 changes: 27 additions & 10 deletions internal/cmd/credentialcmd/init_secrets_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func initSecretsBackendDescription(kind config.SecretsBackendKind) string {
case config.SecretsBackendKind(credstore.BackendOPDesktop):
return "Use the local 1Password desktop app integration. Most common for local use; best for interactive developer machines with an unlocked desktop app; requires a vault name or id and can optionally pin a desktop account id."
case config.SecretsBackendKind(credstore.BackendMemory):
return "Keep credentials in memory only. Best suited for tests or CI."
return "Keep credentials in memory only for this process. Ephemeral; best suited for tests or CI, not normal local use."
default:
return ""
}
Expand Down Expand Up @@ -147,6 +147,23 @@ func initSecretsManagementInventoryDescription() string {
return "Choose how cr should store credentials. Secrets-management profiles are reusable store definitions that review profiles can choose later."
}

func initAutomaticOSDefaultSecretsBackendLabel() string {
return initAutomaticOSDefaultSecretsBackendLabelForGOOS(runtime.GOOS)
}

func initAutomaticOSDefaultSecretsBackendLabelForGOOS(goos string) string {
switch goos {
case "darwin":
return "Automatic OS default (macOS Keychain)"
case "windows":
return "Automatic OS default (Windows Credential Manager)"
case "linux":
return "Automatic OS default (Linux Secret Service)"
default:
return "Automatic OS default"
}
}

func initSecretsManagementInventoryRows(cfg config.File) []initInventoryRow {
effective := config.EffectiveSecretsProfiles(cfg)
rows := make([]initInventoryRow, 0, len(effective)+len(initSecretsBackendCatalog())+2)
Expand All @@ -166,11 +183,11 @@ func initSecretsManagementInventoryRows(cfg config.File) []initInventoryRow {
rows = append(rows, initInventoryRow{
ID: initSecretsManagementLegacySelection,
Title: initLegacySecretsManagementInventoryTitle(cfg),
Description: "Global fallback credential store used by profiles that do not choose a named secrets-management profile.",
Description: "Legacy fallback credential store used only by profiles that do not choose a named secrets-management profile.",
Kind: initInventoryRowKindActive,
PrimaryAction: initInventoryActionCommand,
Selectable: true,
FilterValue: strings.TrimSpace(strings.Join([]string{"default credential store global fallback keyring backend", strings.TrimSpace(cfg.Keyring.Backend)}, " ")),
FilterValue: strings.TrimSpace(strings.Join([]string{"fallback compatibility legacy credential store keyring backend", strings.TrimSpace(cfg.Keyring.Backend)}, " ")),
})

for _, backend := range initSecretsBackendCatalog() {
Expand Down Expand Up @@ -223,9 +240,9 @@ func initLegacySecretsManagementInventoryTitle(cfg config.File) string {
}
}
if backend == config.ProjectedLegacySecretsBackendKind {
backendLabel = "Automatic OS default"
backendLabel = initAutomaticOSDefaultSecretsBackendLabel()
}
return fmt.Sprintf("Default credential store (%s)", backendLabel)
return fmt.Sprintf("Fallback credential store: %s", backendLabel)
}

func initSecretsProfileDisplayName(id string, label string) string {
Expand Down Expand Up @@ -289,7 +306,7 @@ func initSecretsProfileBackendOptions(current config.SecretsBackendKind) []huh.O

func initLegacySecretsBackendOptions(current string) []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Automatic OS default", ""),
huh.NewOption(initAutomaticOSDefaultSecretsBackendLabel(), ""),
}
for _, backend := range initSecretsBackendCatalog() {
if !backend.LegacyCompatible {
Expand Down Expand Up @@ -703,17 +720,17 @@ func (p huhInitKeyringBackendPrompter) editLegacySecretsManagement(currentBacken
form := huh.NewForm(
huh.NewGroup(
huh.NewSelect[string]().
Title("Default credential store backend").
Title("Legacy fallback credential store backend").
Options(initLegacySecretsBackendOptions(currentBackend)...).
Value(&backend),
huh.NewSelect[string]().
Title("Default credential-store action").
Title("Fallback credential-store action").
Options(
huh.NewOption("Stage default credential-store settings", initDetailActionEdit),
huh.NewOption("Stage fallback credential-store settings", initDetailActionEdit),
huh.NewOption("Back without staging", initDetailActionBack),
).
Value(&action),
).Title("Default Credential Store"),
).Title("Legacy Fallback Credential Store"),
)
back, err := runBackableInitForm(form, p.stdin, p.stderr)
if err != nil {
Expand Down
21 changes: 19 additions & 2 deletions internal/cmd/credentialcmd/init_secrets_management_editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ func initSecretsManagementLinearEditorWithPendingOrder(cfg config.File, pendingD
var document initLinearDocument
document.addSection("Secrets management", initSecretsManagementInventoryDescription())
document.addEditableSelect(initSecretsManagementFieldTarget, "Secrets-management target", "", targetOptions, selectedTarget)
document.addSectionField(initSecretsManagementSectionLegacy, "Default credential store", "Global fallback credential store used by profiles that do not choose a named secrets-management profile.")
document.addEditableSelect(initSecretsManagementFieldLegacyBackend, "Legacy persistent backend", "", initLegacySecretsBackendOptions(cfg.Keyring.Backend), strings.TrimSpace(cfg.Keyring.Backend))
document.addSectionField(initSecretsManagementSectionLegacy, "Legacy fallback credential store", "Compatibility path for profiles that do not choose a named secrets-management profile.")
document.addEditableSelect(initSecretsManagementFieldLegacyBackend, "Fallback persistent backend", initLegacySecretsBackendFieldDescription(cfg.Keyring.Backend), initLegacySecretsBackendOptions(cfg.Keyring.Backend), strings.TrimSpace(cfg.Keyring.Backend))
document.addSectionField(initSecretsManagementSectionProfile, "Secrets-management profile", "Secrets-management profiles are reusable credential-store definitions that review profiles can choose later.")
document.addEditableInput(
initSecretsManagementFieldLabel,
Expand Down Expand Up @@ -179,6 +179,10 @@ func initSecretsManagementLinearEditorWithPendingOrder(cfg config.File, pendingD
}
if id == initSecretsManagementFieldBackend {
initSecretsManagementSyncLinearFields(model, cfg, pendingDeletes, pendingDeleteOrder, false)
return
}
if id == initSecretsManagementFieldLegacyBackend {
initSecretsManagementSyncLinearFields(model, cfg, pendingDeletes, pendingDeleteOrder, false)
}
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-self-documenting-code-reviewer): The new initSecretsManagementFieldLegacyBackend handler omits the explicit return that initSecretsManagementFieldBackend has after calling initSecretsManagementSyncLinearFields. If the two paths should behave identically, add return; if the legacy path intentionally falls through to later OnChange logic, a short comment explaining why would eliminate the ambiguity.

Reply to this thread when addressed.

OnEnter: func(model *initLinearEditorModel) (bool, tea.Cmd) {
Expand Down Expand Up @@ -336,6 +340,7 @@ func initSecretsManagementSyncLinearFields(model *initLinearEditorModel, cfg con
model.setFieldHidden(initSecretsManagementFieldDefault, !profileVisible)
initSecretsManagementSetActionOptions(model, !state.Creating && !state.Legacy && !state.Pending)
if state.Legacy {
model.setFieldDescription(initSecretsManagementFieldLegacyBackend, initLegacySecretsBackendFieldDescription(model.document.selectedValue(initSecretsManagementFieldLegacyBackend)))
initSecretsManagementSetOnePasswordHidden(model, true, true, true, true)
return
}
Expand Down Expand Up @@ -501,6 +506,18 @@ func initSecretsManagementBackendFieldDescription(kind config.SecretsBackendKind
return strings.TrimSpace(description + " Use Up/Down here to change the backend for this configured secrets-management profile.")
}

func initLegacySecretsBackendFieldDescription(backend string) string {
trimmed := strings.TrimSpace(backend)
if trimmed == "" {
return "Use the platform credential store chosen by the OS, such as macOS Keychain on macOS, Windows Credential Manager on Windows, or Linux Secret Service on Linux."
}
description := strings.TrimSpace(initSecretsBackendDescription(config.SecretsBackendKind(trimmed)))
if description == "" {
return "Compatibility backend used only by profiles that do not choose a named secrets-management profile."
}
return strings.TrimSpace(description + " This is the legacy fallback backend for profiles without a named secrets-management profile.")
}

func initSecretsManagementSelectedOptionLabel(document initLinearDocument, id initLinearFieldID) string {
index := document.fieldIndexByID(id)
if index < 0 {
Expand Down
Loading