diff --git a/changelog/14301.txt b/changelog/14301.txt new file mode 100644 index 000000000..9f5e4ba1c --- /dev/null +++ b/changelog/14301.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/kv: add full secret path output to table-formatted responses +``` diff --git a/command/kv_get.go b/command/kv_get.go index 3a7130626..14202d764 100644 --- a/command/kv_get.go +++ b/command/kv_get.go @@ -158,6 +158,10 @@ func (c *KVGetCommand) Run(args []string) int { tf.printWarnings(c.UI, secret) } + if v2 { + outputPath(c.UI, path, "Secret Path") + } + if metadata, ok := secret.Data["metadata"]; ok && metadata != nil { c.UI.Info(getHeaderForMap("Metadata", metadata.(map[string]interface{}))) OutputData(c.UI, metadata) diff --git a/command/kv_helpers.go b/command/kv_helpers.go index 1442be6ed..7a2bf9f8d 100644 --- a/command/kv_helpers.go +++ b/command/kv_helpers.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/vault/api" + "github.com/mitchellh/cli" ) func kvReadRequest(client *api.Client, path string, params map[string]string) (*api.Secret, error) { @@ -141,6 +142,26 @@ func getHeaderForMap(header string, data map[string]interface{}) string { // 4 for the column spaces and 5 for the len("value") totalLen := maxKey + 4 + 5 + return padEqualSigns(header, totalLen) +} + +func kvParseVersionsFlags(versions []string) []string { + versionsOut := make([]string, 0, len(versions)) + for _, v := range versions { + versionsOut = append(versionsOut, strutil.ParseStringSlice(v, ",")...) + } + + return versionsOut +} + +func outputPath(ui cli.Ui, path string, title string) { + ui.Info(padEqualSigns(title, len(path))) + ui.Info(path) + ui.Info("") +} + +// Pad the table header with equal signs on each side +func padEqualSigns(header string, totalLen int) string { equalSigns := totalLen - (len(header) + 2) // If we have zero or fewer equal signs bump it back up to two on either @@ -156,12 +177,3 @@ func getHeaderForMap(header string, data map[string]interface{}) string { return fmt.Sprintf("%s %s %s", strings.Repeat("=", equalSigns/2), header, strings.Repeat("=", equalSigns/2)) } - -func kvParseVersionsFlags(versions []string) []string { - versionsOut := make([]string, 0, len(versions)) - for _, v := range versions { - versionsOut = append(versionsOut, strutil.ParseStringSlice(v, ",")...) - } - - return versionsOut -} diff --git a/command/kv_metadata_get.go b/command/kv_metadata_get.go index c37e0db65..61abf3a57 100644 --- a/command/kv_metadata_get.go +++ b/command/kv_metadata_get.go @@ -117,6 +117,8 @@ func (c *KVMetadataGetCommand) Run(args []string) int { delete(secret.Data, "versions") + outputPath(c.UI, path, "Metadata Path") + c.UI.Info(getHeaderForMap("Metadata", secret.Data)) OutputSecret(c.UI, secret) diff --git a/command/kv_patch.go b/command/kv_patch.go index 7b54fb64f..334ba6f46 100644 --- a/command/kv_patch.go +++ b/command/kv_patch.go @@ -185,6 +185,13 @@ func (c *KVPatchCommand) Run(args []string) int { return code } + if Format(c.UI) == "table" { + outputPath(c.UI, path, "Secret Path") + metadata := secret.Data + c.UI.Info(getHeaderForMap("Metadata", metadata)) + return OutputData(c.UI, metadata) + } + return OutputSecret(c.UI, secret) } diff --git a/command/kv_put.go b/command/kv_put.go index 90349f6f3..eba8cff04 100644 --- a/command/kv_put.go +++ b/command/kv_put.go @@ -161,5 +161,24 @@ func (c *KVPutCommand) Run(args []string) int { return PrintRawField(c.UI, secret, c.flagField) } + // if Format(c.UI) != "table" { + // return OutputSecret(c.UI, secret) + // } + + // outputPath(c.UI, path, "Secret Path") + + // metadata := secret.Data + // c.UI.Info(getHeaderForMap("Metadata", metadata)) + // OutputData(c.UI, metadata) + + // return 0 + + if Format(c.UI) == "table" { + outputPath(c.UI, path, "Secret Path") + metadata := secret.Data + c.UI.Info(getHeaderForMap("Metadata", metadata)) + return OutputData(c.UI, metadata) + } + return OutputSecret(c.UI, secret) } diff --git a/command/kv_test.go b/command/kv_test.go index 41a6e68e2..fd5303db0 100644 --- a/command/kv_test.go +++ b/command/kv_test.go @@ -134,6 +134,12 @@ func TestKVPutCommand(t *testing.T) { v2ExpectedFields, 0, }, + { + "v2_secret_path", + []string{"kv/write/foo", "foo=bar"}, + []string{"== Secret Path ==", "kv/data/write/foo"}, + 0, + }, } for _, tc := range cases { @@ -872,6 +878,13 @@ func TestKVPatchCommand_RWMethodSucceeds(t *testing.T) { } } + // Test that full path was output + for _, str := range []string{"== Secret Path ==", "kv/data/patch/foo"} { + if !strings.Contains(combined, str) { + t.Errorf("expected %q to contain %q", combined, str) + } + } + // Test multi value args = []string{"-method", "rw", "kv/patch/foo", "foo=aaa", "bar=bbb"} code, combined = kvPatchWithRetry(t, client, args, nil) @@ -1106,28 +1119,6 @@ func TestKVPatchCommand_403Fallback(t *testing.T) { } } -func createTokenForPolicy(t *testing.T, client *api.Client, policy string) (*api.SecretAuth, error) { - t.Helper() - - if err := client.Sys().PutPolicy("policy", policy); err != nil { - return nil, err - } - - secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ - Policies: []string{"policy"}, - TTL: "30m", - }) - if err != nil { - return nil, err - } - - if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" { - return nil, fmt.Errorf("missing auth data: %#v", secret) - } - - return secret.Auth, err -} - func TestKVPatchCommand_RWMethodPolicyVariations(t *testing.T) { cases := []struct { name string @@ -1205,3 +1196,73 @@ func TestKVPatchCommand_RWMethodPolicyVariations(t *testing.T) { }) } } + +func TestPadEqualSigns(t *testing.T) { + t.Parallel() + + header := "Test Header" + + cases := []struct { + name string + totalPathLen int + expectedCount int + }{ + { + name: "path with even length", + totalPathLen: 20, + expectedCount: 4, + }, + { + name: "path with odd length", + totalPathLen: 19, + expectedCount: 3, + }, + { + name: "smallest possible path", + totalPathLen: 8, + expectedCount: 2, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + padded := padEqualSigns(header, tc.totalPathLen) + + signs := strings.Split(padded, fmt.Sprintf(" %s ", header)) + if len(signs[0]) != len(signs[1]) { + t.Fatalf("expected an equal number of equal signs on both sides") + } + for _, sign := range signs { + count := strings.Count(sign, "=") + if count != tc.expectedCount { + t.Fatalf("expected %d equal signs but there were %d", tc.expectedCount, count) + } + } + }) + } +} + +func createTokenForPolicy(t *testing.T, client *api.Client, policy string) (*api.SecretAuth, error) { + t.Helper() + + if err := client.Sys().PutPolicy("policy", policy); err != nil { + return nil, err + } + + secret, err := client.Auth().Token().Create(&api.TokenCreateRequest{ + Policies: []string{"policy"}, + TTL: "30m", + }) + if err != nil { + return nil, err + } + + if secret == nil || secret.Auth == nil || secret.Auth.ClientToken == "" { + return nil, fmt.Errorf("missing auth data: %#v", secret) + } + + return secret.Auth, err +}