From bb9d8524734943cc94a1bebe09baecbdd692afe7 Mon Sep 17 00:00:00 2001 From: johha Date: Wed, 7 Oct 2026 11:59:29 +0200 Subject: [PATCH] V8 Backport: Show job GUID in service command progress hints Async service operations run as CC jobs with no CLI-visible handle. When a command returns before the job finishes (no --wait provided), print the job GUID so users and operators can trace it via 'cf curl v3/jobs/' which is the only key to that job's status and warnings. --- actor/v7action/job.go | 2 + actor/v7action/job_test.go | 6 ++ api/cloudcontroller/ccv3/job.go | 2 + api/cloudcontroller/ccv3/job_test.go | 7 +++ command/v7/bind_route_service_command.go | 3 +- command/v7/bind_route_service_command_test.go | 2 + command/v7/bind_service_command.go | 3 +- command/v7/bind_service_command_test.go | 2 + ...eanup_outdated_service_bindings_command.go | 3 +- ..._outdated_service_bindings_command_test.go | 2 + command/v7/create_service_command.go | 3 +- command/v7/create_service_command_test.go | 2 + command/v7/create_service_key_command.go | 3 +- command/v7/create_service_key_command_test.go | 2 + command/v7/delete_command.go | 2 +- command/v7/delete_org_command.go | 2 +- command/v7/delete_service_command.go | 3 +- command/v7/delete_service_command_test.go | 4 +- command/v7/delete_service_key_command.go | 3 +- command/v7/delete_service_key_command_test.go | 2 + command/v7/delete_space_command.go | 2 +- command/v7/shared/result_waiter.go | 21 +++++-- command/v7/shared/result_waiter_test.go | 55 ++++++++++++++----- command/v7/unbind_route_service_command.go | 3 +- .../v7/unbind_route_service_command_test.go | 2 + command/v7/unbind_service_command.go | 3 +- command/v7/unbind_service_command_test.go | 2 + command/v7/update_service_command.go | 3 +- command/v7/update_service_command_test.go | 2 + command/v7/upgrade_service_command.go | 3 +- command/v7/upgrade_service_command_test.go | 2 + 31 files changed, 122 insertions(+), 34 deletions(-) diff --git a/actor/v7action/job.go b/actor/v7action/job.go index d9558f9e184..bc986225101 100644 --- a/actor/v7action/job.go +++ b/actor/v7action/job.go @@ -18,6 +18,7 @@ type PollJobEvent struct { State JobState Err error Warnings Warnings + JobGUID string } func (actor Actor) PollUploadBuildpackJob(jobURL ccv3.JobURL) (Warnings, error) { @@ -39,6 +40,7 @@ func (actor Actor) PollJobToEventStream(jobURL ccv3.JobURL) chan PollJobEvent { State: JobState(event.State), Err: event.Err, Warnings: Warnings(event.Warnings), + JobGUID: event.JobGUID, } } close(output) diff --git a/actor/v7action/job_test.go b/actor/v7action/job_test.go index bfe10768e88..b9239749e0b 100644 --- a/actor/v7action/job_test.go +++ b/actor/v7action/job_test.go @@ -81,16 +81,19 @@ var _ = Describe("Job Actions", func() { State: constant.JobProcessing, Err: nil, Warnings: ccv3.Warnings{"foo"}, + JobGUID: "job-guid", } fakeStream <- ccv3.PollJobEvent{ State: constant.JobPolling, Err: nil, Warnings: ccv3.Warnings{"bar"}, + JobGUID: "job-guid", } fakeStream <- ccv3.PollJobEvent{ State: constant.JobFailed, Err: errors.New("bad thing"), Warnings: ccv3.Warnings{"baz"}, + JobGUID: "job-guid", } close(fakeStream) }() @@ -105,16 +108,19 @@ var _ = Describe("Job Actions", func() { State: JobProcessing, Err: nil, Warnings: Warnings{"foo"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: JobPolling, Err: nil, Warnings: Warnings{"bar"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: JobFailed, Err: errors.New("bad thing"), Warnings: Warnings{"baz"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(BeClosed()) }) diff --git a/api/cloudcontroller/ccv3/job.go b/api/cloudcontroller/ccv3/job.go index 63189a47f68..69927f7dbd6 100644 --- a/api/cloudcontroller/ccv3/job.go +++ b/api/cloudcontroller/ccv3/job.go @@ -150,6 +150,7 @@ type PollJobEvent struct { State constant.JobState Err error Warnings Warnings + JobGUID string } func (client *Client) PollJobToEventStream(jobURL JobURL) chan PollJobEvent { @@ -170,6 +171,7 @@ func (client *Client) PollJobToEventStream(jobURL JobURL) chan PollJobEvent { State: job.State, Err: err, Warnings: warnings, + JobGUID: job.GUID, } switch { diff --git a/api/cloudcontroller/ccv3/job_test.go b/api/cloudcontroller/ccv3/job_test.go index e538a088397..1aca44dc4b4 100644 --- a/api/cloudcontroller/ccv3/job_test.go +++ b/api/cloudcontroller/ccv3/job_test.go @@ -431,16 +431,19 @@ var _ = Describe("Job", func() { State: constant.JobProcessing, Err: nil, Warnings: Warnings{"warning-1"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: constant.JobProcessing, Err: nil, Warnings: Warnings{"warning-2"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: constant.JobComplete, Err: nil, Warnings: Warnings{"warning-3", "warning-4"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(BeClosed()) }) @@ -458,16 +461,19 @@ var _ = Describe("Job", func() { State: constant.JobProcessing, Err: nil, Warnings: Warnings{"warning-1"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: constant.JobProcessing, Err: nil, Warnings: Warnings{"warning-2", "warning-3"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(Receive(Equal(PollJobEvent{ State: constant.JobFailed, Err: ccerror.BuildpackAlreadyExistsForStackError{Message: "some-message"}, Warnings: Warnings{"warning-4"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(BeClosed()) }) @@ -500,6 +506,7 @@ var _ = Describe("Job", func() { JobGUID: "job-guid", }, Warnings: Warnings{"warning-4"}, + JobGUID: "job-guid", }))) Eventually(stream).Should(BeClosed()) }) diff --git a/command/v7/bind_route_service_command.go b/command/v7/bind_route_service_command.go index c793182e25f..ae09bc5cea6 100644 --- a/command/v7/bind_route_service_command.go +++ b/command/v7/bind_route_service_command.go @@ -46,7 +46,7 @@ func (cmd BindRouteServiceCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -56,6 +56,7 @@ func (cmd BindRouteServiceCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Binding in progress.") + shared.DisplayJobHint(cmd.UI, jobGUID) return nil } } diff --git a/command/v7/bind_route_service_command_test.go b/command/v7/bind_route_service_command_test.go index 9863428b382..6b13a145a58 100644 --- a/command/v7/bind_route_service_command_test.go +++ b/command/v7/bind_route_service_command_test.go @@ -221,6 +221,7 @@ var _ = Describe("bind-route-service Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -236,6 +237,7 @@ var _ = Describe("bind-route-service Command", func() { Say(`OK\n`), Say(`\n`), Say(`Binding in progress\.\n`), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/bind_service_command.go b/command/v7/bind_service_command.go index 84db8e2c3d5..c4775182fb2 100644 --- a/command/v7/bind_service_command.go +++ b/command/v7/bind_service_command.go @@ -57,7 +57,7 @@ func (cmd BindServiceCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -68,6 +68,7 @@ func (cmd BindServiceCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Binding in progress. Use 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.names()) + shared.DisplayJobHint(cmd.UI, jobGUID) cmd.UI.DisplayNewline() cmd.UI.DisplayText("TIP: Once this operation succeeds, use 'cf restage {{.AppName}}' to ensure your env variable changes take effect", cmd.names()) return nil diff --git a/command/v7/bind_service_command_test.go b/command/v7/bind_service_command_test.go index 22dd0c47057..7f61765ed8d 100644 --- a/command/v7/bind_service_command_test.go +++ b/command/v7/bind_service_command_test.go @@ -221,6 +221,7 @@ var _ = Describe("bind-service Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -236,6 +237,7 @@ var _ = Describe("bind-service Command", func() { Say(`OK\n`), Say(`\n`), Say(`Binding in progress. Use 'cf service %s' to check operation status.\n`, fakeServiceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), Say(`\n`), Say(`TIP: Once this operation succeeds, use 'cf restage %s' to ensure your env variable changes take effect\n`, fakeAppName), )) diff --git a/command/v7/cleanup_outdated_service_bindings_command.go b/command/v7/cleanup_outdated_service_bindings_command.go index d35e07dd2e0..ef1afdfe474 100644 --- a/command/v7/cleanup_outdated_service_bindings_command.go +++ b/command/v7/cleanup_outdated_service_bindings_command.go @@ -106,7 +106,7 @@ func (cmd CleanupOutdatedServiceBindingsCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -120,6 +120,7 @@ func (cmd CleanupOutdatedServiceBindingsCommand) Execute(args []string) error { } cmd.UI.DisplayOK() cmd.UI.DisplayText("Unbinding in progress. Use 'cf service {{.ServiceInstanceName}}' to check operation status.", map[string]interface{}{"ServiceInstanceName": si.Name}) + shared.DisplayJobHint(cmd.UI, jobGUID) } } diff --git a/command/v7/cleanup_outdated_service_bindings_command_test.go b/command/v7/cleanup_outdated_service_bindings_command_test.go index 7bc17182305..04fe5dc5a36 100644 --- a/command/v7/cleanup_outdated_service_bindings_command_test.go +++ b/command/v7/cleanup_outdated_service_bindings_command_test.go @@ -384,6 +384,7 @@ var _ = Describe("cleanup-outdated-service-bindings Command", func() { eventStream2 <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job 2 polling warning"}, + JobGUID: "job-guid", } }() @@ -423,6 +424,7 @@ var _ = Describe("cleanup-outdated-service-bindings Command", func() { Say(`OK\n`), Say(`\n`), Say(`Unbinding in progress. Use 'cf service %s' to check operation status\.\n`, fakeServiceInstanceName2), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/create_service_command.go b/command/v7/create_service_command.go index 34fa0829cdb..decc1547ade 100644 --- a/command/v7/create_service_command.go +++ b/command/v7/create_service_command.go @@ -95,7 +95,7 @@ func (cmd CreateServiceCommand) Execute(args []string) error { } cmd.UI.DisplayNewline() - complete, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + complete, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -103,6 +103,7 @@ func (cmd CreateServiceCommand) Execute(args []string) error { cmd.UI.DisplayTextWithFlavor("Service instance {{.ServiceInstanceName}} created.", cmd.serviceInstanceName()) default: cmd.UI.DisplayTextWithFlavor("Create in progress. Use 'cf services' or 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.serviceInstanceName()) + shared.DisplayJobHint(cmd.UI, jobGUID) } cmd.UI.DisplayOK() diff --git a/command/v7/create_service_command_test.go b/command/v7/create_service_command_test.go index 0da4aa1e00a..3db505ab6b0 100644 --- a/command/v7/create_service_command_test.go +++ b/command/v7/create_service_command_test.go @@ -202,6 +202,7 @@ var _ = Describe("create-service Command", func() { fakeStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"stream warning"}, + JobGUID: "job-guid", } }() }) @@ -211,6 +212,7 @@ var _ = Describe("create-service Command", func() { Say(`Creating service instance %s in org %s / space %s as %s\.\.\.\n`, requestedServiceInstanceName, fakeOrgName, fakeSpaceName, fakeUserName), Say(`\n`), Say(`Create in progress. Use 'cf services' or 'cf service %s' to check operation status\.\n`, requestedServiceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), Say(`OK`), )) diff --git a/command/v7/create_service_key_command.go b/command/v7/create_service_key_command.go index 36f1381fd56..5d022b63058 100644 --- a/command/v7/create_service_key_command.go +++ b/command/v7/create_service_key_command.go @@ -42,7 +42,7 @@ func (cmd CreateServiceKeyCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -52,6 +52,7 @@ func (cmd CreateServiceKeyCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Create in progress.") + shared.DisplayJobHint(cmd.UI, jobGUID) return nil } } diff --git a/command/v7/create_service_key_command_test.go b/command/v7/create_service_key_command_test.go index 24d07569509..7741252336a 100644 --- a/command/v7/create_service_key_command_test.go +++ b/command/v7/create_service_key_command_test.go @@ -167,6 +167,7 @@ var _ = Describe("create-service-key Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -182,6 +183,7 @@ var _ = Describe("create-service-key Command", func() { Say(`OK\n`), Say(`\n`), Say(`Create in progress\.\n`), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/delete_command.go b/command/v7/delete_command.go index 8fe9508595b..972e75de731 100644 --- a/command/v7/delete_command.go +++ b/command/v7/delete_command.go @@ -84,7 +84,7 @@ func (cmd DeleteCommand) Execute(args []string) error { } } - if _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { + if _, _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { return err } diff --git a/command/v7/delete_org_command.go b/command/v7/delete_org_command.go index 30b16e45362..1aaf530cec0 100644 --- a/command/v7/delete_org_command.go +++ b/command/v7/delete_org_command.go @@ -58,7 +58,7 @@ func (cmd *DeleteOrgCommand) Execute(args []string) error { default: return err } - } else if _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { + } else if _, _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { return err } diff --git a/command/v7/delete_service_command.go b/command/v7/delete_service_command.go index 3a6749a69f2..41e7b31dc48 100644 --- a/command/v7/delete_service_command.go +++ b/command/v7/delete_service_command.go @@ -52,7 +52,7 @@ func (cmd DeleteServiceCommand) Execute(args []string) error { return err } - deleted, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + deleted, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) if err != nil { return err } @@ -62,6 +62,7 @@ func (cmd DeleteServiceCommand) Execute(args []string) error { cmd.UI.DisplayTextWithFlavor("Service instance {{.ServiceInstanceName}} deleted.", cmd.serviceInstanceName()) default: cmd.UI.DisplayText("Delete in progress. Use 'cf services' or 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.serviceInstanceName()) + shared.DisplayJobHint(cmd.UI, jobGUID) } cmd.UI.DisplayOK() diff --git a/command/v7/delete_service_command_test.go b/command/v7/delete_service_command_test.go index efdacea0084..9acb4f7c18c 100644 --- a/command/v7/delete_service_command_test.go +++ b/command/v7/delete_service_command_test.go @@ -89,7 +89,8 @@ var _ = Describe("delete-service command", func() { go func() { stream <- v7action.PollJobEvent{ - State: v7action.JobPolling, + State: v7action.JobPolling, + JobGUID: "job-guid", } // channel not closed }() @@ -104,6 +105,7 @@ var _ = Describe("delete-service command", func() { Expect(testUI.Out).To(SatisfyAll( Say("\n"), Say(`Delete in progress. Use 'cf services' or 'cf service %s' to check operation status\.\n`, serviceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), Say("OK\n"), )) }) diff --git a/command/v7/delete_service_key_command.go b/command/v7/delete_service_key_command.go index ef22c839b39..c6ebd7bd835 100644 --- a/command/v7/delete_service_key_command.go +++ b/command/v7/delete_service_key_command.go @@ -53,7 +53,7 @@ func (cmd DeleteServiceKeyCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -63,6 +63,7 @@ func (cmd DeleteServiceKeyCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Delete in progress.") + shared.DisplayJobHint(cmd.UI, jobGUID) return nil } } diff --git a/command/v7/delete_service_key_command_test.go b/command/v7/delete_service_key_command_test.go index aaddeb96c2e..fd7845a043b 100644 --- a/command/v7/delete_service_key_command_test.go +++ b/command/v7/delete_service_key_command_test.go @@ -241,6 +241,7 @@ var _ = Describe("delete-service-key Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -256,6 +257,7 @@ var _ = Describe("delete-service-key Command", func() { Say(`OK\n`), Say(`\n`), Say(`Delete in progress\.\n`), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/delete_space_command.go b/command/v7/delete_space_command.go index 61cd59e90c4..0c40ab042ec 100644 --- a/command/v7/delete_space_command.go +++ b/command/v7/delete_space_command.go @@ -74,7 +74,7 @@ func (cmd DeleteSpaceCommand) Execute(args []string) error { default: return err } - } else if _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { + } else if _, _, err := shared.WaitForResult(stream, cmd.UI, true); err != nil { return err } diff --git a/command/v7/shared/result_waiter.go b/command/v7/shared/result_waiter.go index 5a454658880..2c3819465a3 100644 --- a/command/v7/shared/result_waiter.go +++ b/command/v7/shared/result_waiter.go @@ -7,9 +7,9 @@ import ( "code.cloudfoundry.org/cli/v8/command" ) -func WaitForResult(stream chan v7action.PollJobEvent, ui command.UI, waitForCompletion bool) (bool, error) { +func WaitForResult(stream chan v7action.PollJobEvent, ui command.UI, waitForCompletion bool) (bool, string, error) { if stream == nil { - return true, nil + return true, "", nil } if waitForCompletion { @@ -21,21 +21,32 @@ func WaitForResult(stream chan v7action.PollJobEvent, ui command.UI, waitForComp }() } + var jobGUID string seen := map[string]bool{} for event := range stream { ui.DisplayWarnings(dedupeSeenWarnings(event.Warnings, seen)) + if event.JobGUID != "" { + jobGUID = event.JobGUID + } if waitForCompletion { fmt.Fprint(ui.Writer(), ".") } if event.Err != nil { - return false, event.Err + return false, jobGUID, event.Err } if event.State == v7action.JobPolling && !waitForCompletion { - return false, nil + return false, jobGUID, nil } } - return true, nil + return true, jobGUID, nil +} + +func DisplayJobHint(ui command.UI, jobGUID string) { + if jobGUID == "" { + return + } + ui.DisplayText("Job ({{.JobGUID}}) is being processed.", map[string]interface{}{"JobGUID": jobGUID}) } // dedupeSeenWarnings prints each distinct warning at most once per operation: diff --git a/command/v7/shared/result_waiter_test.go b/command/v7/shared/result_waiter_test.go index 2a1f84703c7..40f7ce14418 100644 --- a/command/v7/shared/result_waiter_test.go +++ b/command/v7/shared/result_waiter_test.go @@ -16,6 +16,7 @@ var _ = Describe("WaitForResult", func() { testUI *ui.UI stream chan v7action.PollJobEvent completed bool + jobGUID string err error ) @@ -24,10 +25,11 @@ var _ = Describe("WaitForResult", func() { }) When("the stream is nil (synchronous operation)", func() { - It("reports completion with no error", func() { - completed, err = WaitForResult(nil, testUI, false) + It("reports completion with no error and no job GUID", func() { + completed, jobGUID, err = WaitForResult(nil, testUI, false) Expect(err).NotTo(HaveOccurred()) Expect(completed).To(BeTrue()) + Expect(jobGUID).To(BeEmpty()) }) }) @@ -35,14 +37,15 @@ var _ = Describe("WaitForResult", func() { BeforeEach(func() { s := make(chan v7action.PollJobEvent, 1) stream = s - s <- v7action.PollJobEvent{State: v7action.JobPolling, Warnings: v7action.Warnings{"a warning"}} + s <- v7action.PollJobEvent{State: v7action.JobPolling, JobGUID: "the-job-guid", Warnings: v7action.Warnings{"a warning"}} // channel intentionally left open }) - It("returns not-completed and displays the warning", func() { - completed, err = WaitForResult(stream, testUI, false) + It("returns not-completed and hands back the observed job GUID", func() { + completed, jobGUID, err = WaitForResult(stream, testUI, false) Expect(err).NotTo(HaveOccurred()) Expect(completed).To(BeFalse()) + Expect(jobGUID).To(Equal("the-job-guid")) Expect(testUI.Err).To(Say("a warning")) }) }) @@ -51,14 +54,15 @@ var _ = Describe("WaitForResult", func() { BeforeEach(func() { s := make(chan v7action.PollJobEvent, 1) stream = s - s <- v7action.PollJobEvent{State: v7action.JobFailed, Err: errors.New("boom")} + s <- v7action.PollJobEvent{State: v7action.JobFailed, Err: errors.New("boom"), JobGUID: "err-job-guid"} close(s) }) - It("returns the error and not-completed", func() { - completed, err = WaitForResult(stream, testUI, false) + It("returns the error, not-completed, and the observed GUID", func() { + completed, jobGUID, err = WaitForResult(stream, testUI, false) Expect(err).To(MatchError("boom")) Expect(completed).To(BeFalse()) + Expect(jobGUID).To(Equal("err-job-guid")) }) }) @@ -66,14 +70,15 @@ var _ = Describe("WaitForResult", func() { BeforeEach(func() { s := make(chan v7action.PollJobEvent, 1) stream = s - s <- v7action.PollJobEvent{State: v7action.JobComplete} + s <- v7action.PollJobEvent{State: v7action.JobComplete, JobGUID: "complete-guid"} close(s) }) - It("returns completed with no error", func() { - completed, err = WaitForResult(stream, testUI, true) + It("returns completed with no error and the GUID", func() { + completed, jobGUID, err = WaitForResult(stream, testUI, true) Expect(err).NotTo(HaveOccurred()) Expect(completed).To(BeTrue()) + Expect(jobGUID).To(Equal("complete-guid")) }) }) @@ -89,7 +94,7 @@ var _ = Describe("WaitForResult", func() { }) It("prints the warning only once", func() { - completed, err = WaitForResult(stream, testUI, true) + completed, jobGUID, err = WaitForResult(stream, testUI, true) Expect(err).NotTo(HaveOccurred()) Expect(completed).To(BeTrue()) Expect(testUI.Err).To(Say("still in progress")) @@ -107,7 +112,7 @@ var _ = Describe("WaitForResult", func() { }) It("prints each distinct warning, preserving order", func() { - completed, err = WaitForResult(stream, testUI, true) + completed, jobGUID, err = WaitForResult(stream, testUI, true) Expect(err).NotTo(HaveOccurred()) Expect(testUI.Err).To(Say("first")) Expect(testUI.Err).To(Say("second")) @@ -125,7 +130,7 @@ var _ = Describe("WaitForResult", func() { }) It("prints each distinct warning only once for the whole operation", func() { - completed, err = WaitForResult(stream, testUI, true) + completed, jobGUID, err = WaitForResult(stream, testUI, true) Expect(err).NotTo(HaveOccurred()) Expect(testUI.Err).To(Say("persisted")) Expect(testUI.Err).To(Say("changed")) @@ -134,3 +139,25 @@ var _ = Describe("WaitForResult", func() { }) }) }) + +var _ = Describe("DisplayJobHint", func() { + var testUI *ui.UI + + BeforeEach(func() { + testUI = ui.NewTestUI(nil, NewBuffer(), NewBuffer()) + }) + + When("a job GUID is given", func() { + It("prints a hint naming the job", func() { + DisplayJobHint(testUI, "the-job-guid") + Expect(testUI.Out).To(Say(`Job \(the-job-guid\) is being processed\.`)) + }) + }) + + When("the job GUID is empty", func() { + It("prints nothing", func() { + DisplayJobHint(testUI, "") + Expect(testUI.Out).NotTo(Say("is being processed")) + }) + }) +}) diff --git a/command/v7/unbind_route_service_command.go b/command/v7/unbind_route_service_command.go index d77a474b216..416a708b540 100644 --- a/command/v7/unbind_route_service_command.go +++ b/command/v7/unbind_route_service_command.go @@ -56,7 +56,7 @@ func (cmd UnbindRouteServiceCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -66,6 +66,7 @@ func (cmd UnbindRouteServiceCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Unbinding in progress.") + shared.DisplayJobHint(cmd.UI, jobGUID) return nil } } diff --git a/command/v7/unbind_route_service_command_test.go b/command/v7/unbind_route_service_command_test.go index 8603e65dcd2..7fa142e7aee 100644 --- a/command/v7/unbind_route_service_command_test.go +++ b/command/v7/unbind_route_service_command_test.go @@ -326,6 +326,7 @@ var _ = Describe("unbind-route-service Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -341,6 +342,7 @@ var _ = Describe("unbind-route-service Command", func() { Say(`OK\n`), Say(`\n`), Say(`Unbinding in progress\.\n`), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/unbind_service_command.go b/command/v7/unbind_service_command.go index e5a3ab1b870..cffc847597a 100644 --- a/command/v7/unbind_service_command.go +++ b/command/v7/unbind_service_command.go @@ -71,7 +71,7 @@ func (cmd UnbindServiceCommand) Execute(args []string) error { return err } - completed, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + completed, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -80,6 +80,7 @@ func (cmd UnbindServiceCommand) Execute(args []string) error { default: cmd.UI.DisplayOK() cmd.UI.DisplayText("Unbinding in progress. Use 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.names()) + shared.DisplayJobHint(cmd.UI, jobGUID) } } return nil diff --git a/command/v7/unbind_service_command_test.go b/command/v7/unbind_service_command_test.go index 20000beb47e..5478d90f4d8 100644 --- a/command/v7/unbind_service_command_test.go +++ b/command/v7/unbind_service_command_test.go @@ -209,6 +209,7 @@ var _ = Describe("unbind-service Command", func() { eventStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"job polling warning"}, + JobGUID: "job-guid", } }() @@ -225,6 +226,7 @@ var _ = Describe("unbind-service Command", func() { Say(`OK\n`), Say(`\n`), Say(`Unbinding in progress. Use 'cf service %s' to check operation status\.\n`, fakeServiceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), )) Expect(testUI.Err).To(SatisfyAll( diff --git a/command/v7/update_service_command.go b/command/v7/update_service_command.go index 2c10086cff3..e395d734c91 100644 --- a/command/v7/update_service_command.go +++ b/command/v7/update_service_command.go @@ -66,7 +66,7 @@ func (cmd UpdateServiceCommand) Execute(args []string) error { return err } - complete, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + complete, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -74,6 +74,7 @@ func (cmd UpdateServiceCommand) Execute(args []string) error { cmd.UI.DisplayTextWithFlavor("Update of service instance {{.ServiceInstance}} complete.", cmd.serviceInstanceName()) default: cmd.UI.DisplayTextWithFlavor("Update in progress. Use 'cf services' or 'cf service {{.ServiceInstance}}' to check operation status.", cmd.serviceInstanceName()) + shared.DisplayJobHint(cmd.UI, jobGUID) } cmd.UI.DisplayOK() diff --git a/command/v7/update_service_command_test.go b/command/v7/update_service_command_test.go index a1b9045d568..69832583341 100644 --- a/command/v7/update_service_command_test.go +++ b/command/v7/update_service_command_test.go @@ -151,6 +151,7 @@ var _ = Describe("update-service command", func() { fakeStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"poll warning"}, + JobGUID: "job-guid", } }() }) @@ -160,6 +161,7 @@ var _ = Describe("update-service command", func() { Say(`Updating service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), Say(`\n`), Say(`Update in progress. Use 'cf services' or 'cf service %s' to check operation status\.`, serviceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), Say(`OK\n`), )) diff --git a/command/v7/upgrade_service_command.go b/command/v7/upgrade_service_command.go index fccd0133612..eea2d1253ee 100644 --- a/command/v7/upgrade_service_command.go +++ b/command/v7/upgrade_service_command.go @@ -57,7 +57,7 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { return actorError } - complete, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) + complete, jobGUID, err := shared.WaitForResult(stream, cmd.UI, cmd.Wait) switch { case err != nil: return err @@ -65,6 +65,7 @@ func (cmd UpgradeServiceCommand) Execute(args []string) error { cmd.UI.DisplayTextWithFlavor("Upgrade of service instance {{.ServiceInstanceName}} complete.", cmd.serviceInstanceName()) default: cmd.UI.DisplayTextWithFlavor("Upgrade in progress. Use 'cf services' or 'cf service {{.ServiceInstanceName}}' to check operation status.", cmd.serviceInstanceName()) + shared.DisplayJobHint(cmd.UI, jobGUID) } cmd.UI.DisplayOK() diff --git a/command/v7/upgrade_service_command_test.go b/command/v7/upgrade_service_command_test.go index cc3fff8c6c6..485b3c86a72 100644 --- a/command/v7/upgrade_service_command_test.go +++ b/command/v7/upgrade_service_command_test.go @@ -119,6 +119,7 @@ var _ = Describe("upgrade-service command", func() { fakeStream <- v7action.PollJobEvent{ State: v7action.JobPolling, Warnings: v7action.Warnings{"poll warning"}, + JobGUID: "job-guid", } }() }) @@ -128,6 +129,7 @@ var _ = Describe("upgrade-service command", func() { Say(`Upgrading service instance %s in org %s / space %s as %s...\n`, serviceInstanceName, orgName, spaceName, username), Say(`\n`), Say(`Upgrade in progress. Use 'cf services' or 'cf service %s' to check operation status\.`, serviceInstanceName), + Say(`Job \(job-guid\) is being processed\.\n`), Say(`OK\n`), ))