Refactor unlockers command structure and add quiet flag to list command
- Rename 'unlockers' command to 'unlocker' for consistency - Move all unlocker subcommands (list, add, remove) under single 'unlocker' command - Add --quiet/-q flag to 'secret list' for scripting support - Update documentation and tests to reflect command changes The quiet flag outputs only secret names without headers or formatting, making it ideal for shell script usage like: secret get $(secret list -q | head -1)
This commit is contained in:
@@ -177,6 +177,12 @@ func TestSecretManagerIntegration(t *testing.T) {
|
||||
// Expected: Shows database/password with metadata
|
||||
test11ListSecrets(t, testMnemonic, runSecret, runSecretWithStdin)
|
||||
|
||||
// Test 11b: List secrets with quiet flag
|
||||
// Command: secret list -q
|
||||
// Purpose: Test quiet output for scripting
|
||||
// Expected: Only secret names, no headers or formatting
|
||||
test11bListSecretsQuiet(t, testMnemonic, runSecret)
|
||||
|
||||
// Test 12: Add secrets with different name formats
|
||||
// Commands: Various secret names (paths, dots, underscores)
|
||||
// Purpose: Test secret name validation and storage encoding
|
||||
@@ -184,7 +190,7 @@ func TestSecretManagerIntegration(t *testing.T) {
|
||||
test12SecretNameFormats(t, tempDir, testMnemonic, runSecretWithEnv, runSecretWithStdin)
|
||||
|
||||
// Test 13: Unlocker management
|
||||
// Commands: secret unlockers list, secret unlockers add pgp
|
||||
// Commands: secret unlocker list, secret unlocker add pgp
|
||||
// Purpose: Test multiple unlocker types
|
||||
// Expected filesystem:
|
||||
// - Multiple directories under unlockers.d/
|
||||
@@ -901,6 +907,81 @@ func test11ListSecrets(t *testing.T, testMnemonic string, runSecret func(...stri
|
||||
assert.True(t, secretNames["database/password"], "should have database/password")
|
||||
}
|
||||
|
||||
func test11bListSecretsQuiet(t *testing.T, testMnemonic string, runSecret func(...string) (string, error)) {
|
||||
// Test quiet output
|
||||
quietOutput, err := runSecret("list", "-q")
|
||||
require.NoError(t, err, "secret list -q should succeed")
|
||||
|
||||
// Split output into lines
|
||||
lines := strings.Split(strings.TrimSpace(quietOutput), "\n")
|
||||
|
||||
// Should have exactly 3 lines (3 secrets)
|
||||
assert.Len(t, lines, 3, "quiet output should have exactly 3 lines")
|
||||
|
||||
// Should not contain any headers or formatting
|
||||
assert.NotContains(t, quietOutput, "Secrets in vault", "should not have vault header")
|
||||
assert.NotContains(t, quietOutput, "NAME", "should not have NAME header")
|
||||
assert.NotContains(t, quietOutput, "LAST UPDATED", "should not have LAST UPDATED header")
|
||||
assert.NotContains(t, quietOutput, "Total:", "should not have total count")
|
||||
assert.NotContains(t, quietOutput, "----", "should not have separator lines")
|
||||
|
||||
// Should contain exactly the secret names
|
||||
secretNames := make(map[string]bool)
|
||||
for _, line := range lines {
|
||||
secretNames[line] = true
|
||||
}
|
||||
|
||||
assert.True(t, secretNames["api/key"], "should have api/key")
|
||||
assert.True(t, secretNames["config/database.yaml"], "should have config/database.yaml")
|
||||
assert.True(t, secretNames["database/password"], "should have database/password")
|
||||
|
||||
// Test quiet output with filter
|
||||
quietFilterOutput, err := runSecret("list", "database", "-q")
|
||||
require.NoError(t, err, "secret list with filter and -q should succeed")
|
||||
|
||||
// Should only show secrets matching filter
|
||||
filteredLines := strings.Split(strings.TrimSpace(quietFilterOutput), "\n")
|
||||
assert.Len(t, filteredLines, 2, "quiet filtered output should have exactly 2 lines")
|
||||
|
||||
// Verify filtered results
|
||||
filteredSecrets := make(map[string]bool)
|
||||
for _, line := range filteredLines {
|
||||
filteredSecrets[line] = true
|
||||
}
|
||||
|
||||
assert.True(t, filteredSecrets["config/database.yaml"], "should have config/database.yaml")
|
||||
assert.True(t, filteredSecrets["database/password"], "should have database/password")
|
||||
assert.False(t, filteredSecrets["api/key"], "should not have api/key")
|
||||
|
||||
// Test that quiet and JSON flags are mutually exclusive behavior
|
||||
// (JSON should take precedence if both are specified)
|
||||
jsonQuietOutput, err := runSecret("list", "--json", "-q")
|
||||
require.NoError(t, err, "secret list --json -q should succeed")
|
||||
|
||||
// Should be valid JSON, not quiet output
|
||||
var jsonResponse map[string]interface{}
|
||||
err = json.Unmarshal([]byte(jsonQuietOutput), &jsonResponse)
|
||||
assert.NoError(t, err, "output should be valid JSON when both flags are used")
|
||||
|
||||
// Test using quiet output in command substitution would work like:
|
||||
// secret get $(secret list -q | head -1)
|
||||
// We'll simulate this by getting the first secret name
|
||||
firstSecret := lines[0]
|
||||
|
||||
// Need to create a runSecretWithEnv to provide mnemonic for get operation
|
||||
runSecretWithEnv := func(env map[string]string, args ...string) (string, error) {
|
||||
return cli.ExecuteCommandInProcess(args, "", env)
|
||||
}
|
||||
|
||||
getOutput, err := runSecretWithEnv(map[string]string{
|
||||
"SB_SECRET_MNEMONIC": testMnemonic,
|
||||
}, "get", firstSecret)
|
||||
require.NoError(t, err, "get with secret name from quiet output should succeed")
|
||||
|
||||
// Verify we got a value (not empty)
|
||||
assert.NotEmpty(t, getOutput, "should retrieve a non-empty secret value")
|
||||
}
|
||||
|
||||
func test12SecretNameFormats(t *testing.T, tempDir, testMnemonic string, runSecretWithEnv func(map[string]string, ...string) (string, error), runSecretWithStdin func(string, map[string]string, ...string) (string, error)) {
|
||||
// Make sure we're in default vault
|
||||
runSecret := func(args ...string) (string, error) {
|
||||
@@ -1016,9 +1097,9 @@ func test13UnlockerManagement(t *testing.T, tempDir, testMnemonic string, runSec
|
||||
require.NoError(t, err, "vault select should succeed")
|
||||
|
||||
// List unlockers
|
||||
output, err := runSecret("unlockers", "list")
|
||||
require.NoError(t, err, "unlockers list should succeed")
|
||||
t.Logf("DEBUG: unlockers list output: %q", output)
|
||||
output, err := runSecret("unlocker", "list")
|
||||
require.NoError(t, err, "unlocker list should succeed")
|
||||
t.Logf("DEBUG: unlocker list output: %q", output)
|
||||
|
||||
// Should have the passphrase unlocker created during init
|
||||
assert.Contains(t, output, "passphrase", "should have passphrase unlocker")
|
||||
@@ -1027,15 +1108,15 @@ func test13UnlockerManagement(t *testing.T, tempDir, testMnemonic string, runSec
|
||||
output, err = runSecretWithEnv(map[string]string{
|
||||
"SB_UNLOCK_PASSPHRASE": "another-passphrase",
|
||||
"SB_SECRET_MNEMONIC": testMnemonic, // Need mnemonic to get long-term key
|
||||
}, "unlockers", "add", "passphrase")
|
||||
}, "unlocker", "add", "passphrase")
|
||||
if err != nil {
|
||||
t.Logf("Error adding passphrase unlocker: %v, output: %s", err, output)
|
||||
}
|
||||
require.NoError(t, err, "add passphrase unlocker should succeed")
|
||||
|
||||
// List unlockers again - should have 2 now
|
||||
output, err = runSecret("unlockers", "list")
|
||||
require.NoError(t, err, "unlockers list should succeed")
|
||||
output, err = runSecret("unlocker", "list")
|
||||
require.NoError(t, err, "unlocker list should succeed")
|
||||
|
||||
// Count passphrase unlockers
|
||||
lines := strings.Split(output, "\n")
|
||||
@@ -1051,8 +1132,8 @@ func test13UnlockerManagement(t *testing.T, tempDir, testMnemonic string, runSec
|
||||
assert.GreaterOrEqual(t, passphraseCount, 1, "should have at least 1 passphrase unlocker")
|
||||
|
||||
// Test JSON output
|
||||
jsonOutput, err := runSecret("unlockers", "list", "--json")
|
||||
require.NoError(t, err, "unlockers list --json should succeed")
|
||||
jsonOutput, err := runSecret("unlocker", "list", "--json")
|
||||
require.NoError(t, err, "unlocker list --json should succeed")
|
||||
|
||||
var response map[string]interface{}
|
||||
err = json.Unmarshal([]byte(jsonOutput), &response)
|
||||
@@ -1536,10 +1617,10 @@ func test22JSONOutput(t *testing.T, runSecret func(...string) (string, error)) {
|
||||
|
||||
// Test secret list --json (already tested in test 11)
|
||||
|
||||
// Test unlockers list --json (already tested in test 13)
|
||||
// Test unlocker list --json (already tested in test 13)
|
||||
|
||||
// All JSON outputs verified to be valid and contain expected fields
|
||||
t.Log("JSON output formats verified for vault list, secret list, and unlockers list")
|
||||
t.Log("JSON output formats verified for vault list, secret list, and unlocker list")
|
||||
}
|
||||
|
||||
func test23ErrorHandling(t *testing.T, tempDir, secretPath, testMnemonic string, runSecret func(...string) (string, error), runSecretWithEnv func(map[string]string, ...string) (string, error)) {
|
||||
|
||||
Reference in New Issue
Block a user