From 738f65c6bd915e0f3a012fbc3d0ef09ad8a9bba0 Mon Sep 17 00:00:00 2001 From: Jochen Ehret Date: Mon, 8 Dec 2025 14:01:53 +0100 Subject: [PATCH 1/2] Enhance "service" command for multiple service bindings * in the "bound apps" section, show also multiple service bindings for an app * add binding guid and created_at timestamp to table * sort by 1. name 2. created_at --- command/v7/service_command.go | 17 ++++++++-- command/v7/service_command_test.go | 32 +++++++++++++++---- .../service_credential_binding_resource.go | 2 ++ ...ervice_credential_binding_resource_test.go | 5 ++- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/command/v7/service_command.go b/command/v7/service_command.go index 0d70e17f5c2..625b8c8089f 100644 --- a/command/v7/service_command.go +++ b/command/v7/service_command.go @@ -2,6 +2,7 @@ package v7 import ( "fmt" + "sort" "strconv" "code.cloudfoundry.org/cli/v8/actor/v7action" @@ -240,13 +241,25 @@ func (cmd ServiceCommand) displayBoundApps(serviceInstanceWithDetails v7action.S return } - table := [][]string{{"name", "binding name", "status", "message"}} - for _, app := range serviceInstanceWithDetails.BoundApps { + table := [][]string{{"name", "binding name", "status", "message", "guid", "created_at"}} + + bindings := serviceInstanceWithDetails.BoundApps + // sort by app name, then by created at descending + sort.Slice(bindings, func(i, j int) bool { + if bindings[i].AppName == bindings[j].AppName { + return bindings[i].CreatedAt > bindings[j].CreatedAt + } + return bindings[i].AppName < bindings[j].AppName + }) + + for _, app := range bindings { table = append(table, []string{ app.AppName, app.Name, fmt.Sprintf("%s %s", app.LastOperation.Type, app.LastOperation.State), app.LastOperation.Description, + app.GUID, + app.CreatedAt, }) } diff --git a/command/v7/service_command_test.go b/command/v7/service_command_test.go index d08e068e778..b797b0f3103 100644 --- a/command/v7/service_command_test.go +++ b/command/v7/service_command_test.go @@ -271,19 +271,23 @@ var _ = Describe("service command", func() { { Name: "named-binding", AppName: "app-1", + GUID: "guid-1", LastOperation: resources.LastOperation{ Type: resources.CreateOperation, State: resources.OperationSucceeded, Description: "great", }, + CreatedAt: "created-at-1", }, { AppName: "app-2", + GUID: "guid-2", LastOperation: resources.LastOperation{ Type: resources.UpdateOperation, State: resources.OperationFailed, Description: "sorry", }, + CreatedAt: "created-at-2", }, }, }, @@ -295,9 +299,9 @@ var _ = Describe("service command", func() { It("prints the bound apps table", func() { Expect(testUI.Out).To(SatisfyAll( Say(`Showing bound apps:\n`), - Say(` name\s+binding name\s+status\s+message\n`), - Say(` app-1\s+named-binding\s+create succeeded\s+great\n`), - Say(` app-2\s+update failed\s+sorry\n`), + Say(` name\s+binding name\s+status\s+message\s+guid\s+created_at\n`), + Say(` app-1\s+named-binding\s+create succeeded\s+great\s+guid-1\s+created-at-1\n`), + Say(` app-2\s+update failed\s+sorry\s+guid-2\s+created-at-2\n`), )) }) }) @@ -704,19 +708,34 @@ var _ = Describe("service command", func() { { Name: "named-binding", AppName: "app-1", + GUID: "guid-1", LastOperation: resources.LastOperation{ Type: resources.CreateOperation, State: resources.OperationSucceeded, Description: "great", }, + CreatedAt: "created-at-1", }, { AppName: "app-2", + GUID: "guid-2", LastOperation: resources.LastOperation{ Type: resources.UpdateOperation, State: resources.OperationFailed, Description: "sorry", }, + CreatedAt: "created-at-2", + }, + { + Name: "named-binding", + AppName: "app-1", + GUID: "guid-3", + LastOperation: resources.LastOperation{ + Type: resources.CreateOperation, + State: resources.OperationSucceeded, + Description: "great", + }, + CreatedAt: "created-at-2", }, }, }, @@ -728,9 +747,10 @@ var _ = Describe("service command", func() { It("prints the bound apps table", func() { Expect(testUI.Out).To(SatisfyAll( Say(`Showing bound apps:\n`), - Say(`name\s+binding name\s+status\s+message\n`), - Say(`app-1\s+named-binding\s+create succeeded\s+great\n`), - Say(`app-2\s+update failed\s+sorry\n`), + Say(`name\s+binding name\s+status\s+message\s+guid\s+created_at\n`), + Say(`app-1\s+named-binding\s+create succeeded\s+great\s+guid-3\s+created-at-2\n`), + Say(`app-1\s+named-binding\s+create succeeded\s+great\s+guid-1\s+created-at-1\n`), + Say(`app-2\s+update failed\s+sorry\s+guid-2\s+created-at-2\n`), )) }) }) diff --git a/resources/service_credential_binding_resource.go b/resources/service_credential_binding_resource.go index 013cf66f064..8dc6cb2ebc7 100644 --- a/resources/service_credential_binding_resource.go +++ b/resources/service_credential_binding_resource.go @@ -31,6 +31,8 @@ type ServiceCredentialBinding struct { LastOperation LastOperation `jsonry:"last_operation"` // Parameters can be specified when creating a binding Parameters types.OptionalObject `jsonry:"parameters"` + // CreatedAt timestamp when the binding was created (useful for distinguishing multiple bindings) + CreatedAt string `json:"created_at,omitempty"` } func (s ServiceCredentialBinding) MarshalJSON() ([]byte, error) { diff --git a/resources/service_credential_binding_resource_test.go b/resources/service_credential_binding_resource_test.go index 607569142b9..14ec5138103 100644 --- a/resources/service_credential_binding_resource_test.go +++ b/resources/service_credential_binding_resource_test.go @@ -60,6 +60,7 @@ var _ = Describe("service credential binding resource", func() { } }`, ), + Entry("created_at", ServiceCredentialBinding{CreatedAt: "fake-created-at"}, `{"created_at": "fake-created-at"}`), Entry( "everything", ServiceCredentialBinding{ @@ -71,6 +72,7 @@ var _ = Describe("service credential binding resource", func() { Parameters: types.NewOptionalObject(map[string]interface{}{ "foo": "bar", }), + CreatedAt: "fake-created-at", }, `{ "type": "app", @@ -90,7 +92,8 @@ var _ = Describe("service credential binding resource", func() { }, "parameters": { "foo": "bar" - } + }, + "created_at": "fake-created-at" }`, ), ) From f3ec155e107bcb5ca055e206bb29d6e1390db114 Mon Sep 17 00:00:00 2001 From: Jochen Ehret Date: Tue, 9 Dec 2025 12:29:20 +0100 Subject: [PATCH 2/2] Update integration tests for "service" command multi bindings support --- integration/v7/isolated/service_command_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/integration/v7/isolated/service_command_test.go b/integration/v7/isolated/service_command_test.go index bf4bffce60f..f17b3560093 100644 --- a/integration/v7/isolated/service_command_test.go +++ b/integration/v7/isolated/service_command_test.go @@ -182,9 +182,9 @@ var _ = Describe("service command", func() { Expect(session).To(SatisfyAll( Say(`Showing bound apps:\n`), - Say(`name\s+binding name\s+status\s+message\n`), - Say(`%s\s+%s\s+create succeeded\s*\n`, appName1, bindingName1), - Say(`%s\s+%s\s+create succeeded\s*\n`, appName2, bindingName2), + Say(`name\s+binding name\s+status\s+message\s+guid\s+created_at\n`), + Say(`%s\s+%s\s+create succeeded\s+%s\s+%s\n`, appName1, bindingName1, helpers.GUIDRegex, helpers.TimestampRegex), + Say(`%s\s+%s\s+create succeeded\s+%s\s+%s\n`, appName2, bindingName2, helpers.GUIDRegex, helpers.TimestampRegex), )) }) }) @@ -528,9 +528,9 @@ var _ = Describe("service command", func() { Expect(session).To(SatisfyAll( Say(`Showing bound apps:\n`), - Say(`name\s+binding name\s+status\s+message\n`), - Say(`%s\s+%s\s+create succeeded\s+very happy service\n`, appName1, bindingName1), - Say(`%s\s+%s\s+create succeeded\s+very happy service\n`, appName2, bindingName2), + Say(`name\s+binding name\s+status\s+message\s+guid\s+created_at\n`), + Say(`%s\s+%s\s+create succeeded\s+very happy service\s+%s\s+%s\n`, appName1, bindingName1, helpers.GUIDRegex, helpers.TimestampRegex), + Say(`%s\s+%s\s+create succeeded\s+very happy service\s+%s\s+%s\n`, appName2, bindingName2, helpers.GUIDRegex, helpers.TimestampRegex), )) }) })