From 0d33a88bc1cf4a0738376dc2480bc457686ca535 Mon Sep 17 00:00:00 2001 From: George Blue Date: Mon, 26 Jan 2026 22:31:26 +0000 Subject: [PATCH] feat: add --json flag to "service-key" subcommand It's very common to see code like: ``` cf service-key "$SERVICE_INSTANCE_NAME" $KEY_NAME | tail -n +2 ``` This removed the header from the output leaving just JSON. Search GitHub and you'll see pages of hits. Rather than forcing script users to chop of the header, risking issues with errors in pipes, etc... It woudld be better just to print the correct output in the first place. This commit: adds a "--json" flag which outputs JSON only. It follows the convention of "--guid" to supress warnings too (unless there an error). In practice the endpoints in question do not produce warnings, so it makes no practical difference. --- command/v7/service_key_command.go | 37 ++++++++++------ command/v7/service_key_command_test.go | 59 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/command/v7/service_key_command.go b/command/v7/service_key_command.go index 140be00ac7d..e4a129ec20e 100644 --- a/command/v7/service_key_command.go +++ b/command/v7/service_key_command.go @@ -9,6 +9,7 @@ type ServiceKeyCommand struct { RequiredArgs flag.ServiceInstanceKey `positional-args:"yes"` GUID bool `long:"guid" description:"Retrieve and display the given service-key's guid. All other output is suppressed."` + JSON bool `long:"json" description:"Output credentials as JSON. All other output is suppressed."` } func (cmd ServiceKeyCommand) Execute(args []string) error { @@ -16,9 +17,11 @@ func (cmd ServiceKeyCommand) Execute(args []string) error { return err } - switch cmd.GUID { - case true: + switch { + case cmd.GUID: return cmd.guid() + case cmd.JSON: + return cmd.json() default: return cmd.details() } @@ -38,14 +41,27 @@ func (cmd ServiceKeyCommand) guid() error { cmd.RequiredArgs.ServiceKey, cmd.Config.TargetedSpace().GUID, ) - switch err.(type) { - case nil: - cmd.UI.DisplayText(key.GUID) - return nil - default: + if err != nil { cmd.UI.DisplayWarnings(warnings) return err } + + cmd.UI.DisplayText(key.GUID) + return nil +} + +func (cmd ServiceKeyCommand) json() error { + details, warnings, err := cmd.Actor.GetServiceKeyDetailsByServiceInstanceAndName( + cmd.RequiredArgs.ServiceInstance, + cmd.RequiredArgs.ServiceKey, + cmd.Config.TargetedSpace().GUID, + ) + if err != nil { + cmd.UI.DisplayWarnings(warnings) + return err + } + + return cmd.UI.DisplayJSON("", details) } func (cmd ServiceKeyCommand) details() error { @@ -72,10 +88,5 @@ func (cmd ServiceKeyCommand) details() error { cmd.UI.DisplayNewline() - err = cmd.UI.DisplayJSON("", details) - if err != nil { - return err - } - - return nil + return cmd.UI.DisplayJSON("", details) } diff --git a/command/v7/service_key_command_test.go b/command/v7/service_key_command_test.go index 0ec7d0cc1cc..1af1393c918 100644 --- a/command/v7/service_key_command_test.go +++ b/command/v7/service_key_command_test.go @@ -64,6 +64,16 @@ var _ = Describe("service-key Command", func() { Expect(actualSpace).To(BeTrue()) }) + When("checking target fails", func() { + BeforeEach(func() { + fakeSharedActor.CheckTargetReturns(errors.New("not logged in")) + }) + + It("returns the error", func() { + Expect(executeErr).To(MatchError("not logged in")) + }) + }) + When("getting details", func() { const fakeUserName = "fake-user-name" @@ -167,4 +177,53 @@ var _ = Describe("service-key Command", func() { }) }) }) + + When("getting JSON output", func() { + BeforeEach(func() { + fakeActor.GetServiceKeyDetailsByServiceInstanceAndNameReturns( + resources.ServiceCredentialBindingDetails{ + Credentials: map[string]interface{}{"foo": "bar", "pass": "<3test"}, + }, + v7action.Warnings{"a warning"}, + nil, + ) + + setFlag(&cmd, "--json") + }) + + It("delegates to the actor", func() { + Expect(fakeActor.GetServiceKeyDetailsByServiceInstanceAndNameCallCount()).To(Equal(1)) + actualServiceInstanceName, actualKeyName, actualSpaceGUID := fakeActor.GetServiceKeyDetailsByServiceInstanceAndNameArgsForCall(0) + Expect(actualServiceInstanceName).To(Equal(fakeServiceInstanceName)) + Expect(actualKeyName).To(Equal(fakeServiceKeyName)) + Expect(actualSpaceGUID).To(Equal(fakeSpaceGUID)) + }) + + It("prints JSON without intro message or warnings", func() { + Expect(executeErr).NotTo(HaveOccurred()) + Expect(testUI.Err).NotTo(Say("a warning")) + Expect(testUI.Out).NotTo(Say("Getting key")) + Expect(testUI.Out).To(SatisfyAll( + Say(`\{\n`), + Say(` "foo": "bar",\n`), + Say(` "pass": "<3test"\n`), + Say(`\}\n`), + )) + }) + + When("actor returns an error", func() { + BeforeEach(func() { + fakeActor.GetServiceKeyDetailsByServiceInstanceAndNameReturns( + resources.ServiceCredentialBindingDetails{}, + v7action.Warnings{"a warning"}, + errors.New("bang"), + ) + }) + + It("prints warnings and returns an error", func() { + Expect(testUI.Err).To(Say("a warning")) + Expect(executeErr).To(MatchError("bang")) + }) + }) + }) })