From 6213039128ce8043a90e24fa5b04c448f2ff7533 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 11 Aug 2026 11:10:08 +0000 Subject: [PATCH 1/3] job_runs: include resource key in deploy progress lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make concurrent job_runs deploys easier to tell apart by attaching the bundle resource key in the direct engine and formatting cmdio lines as "Output from job_runs.foo: id=…: …". --- .../bundles/job-runs-cmdio-resource-key.md | 1 + .../bundle/resources/job_runs/basic/output.txt | 4 ++-- .../resources/job_runs/failed_run/output.txt | 4 ++-- .../job_runs/interrupted_run/output.txt | 4 ++-- .../job_runs/job_parameters/output.txt | 4 ++-- .../resources/job_runs/redeploy/output.txt | 8 ++++---- .../bundle/resources/job_runs/wait/output.txt | 4 ++-- bundle/direct/apply.go | 8 ++++++++ bundle/direct/dresources/job_run.go | 8 +++++--- bundle/direct/dresources/job_run_test.go | 10 ++++++++++ bundle/direct/dresources/resource_key.go | 17 +++++++++++++++++ bundle/direct/dresources/resource_key_test.go | 13 +++++++++++++ 12 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 .nextchanges/bundles/job-runs-cmdio-resource-key.md create mode 100644 bundle/direct/dresources/resource_key.go create mode 100644 bundle/direct/dresources/resource_key_test.go diff --git a/.nextchanges/bundles/job-runs-cmdio-resource-key.md b/.nextchanges/bundles/job-runs-cmdio-resource-key.md new file mode 100644 index 00000000000..4267d570cc1 --- /dev/null +++ b/.nextchanges/bundles/job-runs-cmdio-resource-key.md @@ -0,0 +1 @@ +direct: job_runs deploy progress lines now include the resource key (e.g. `Output from job_runs.foo: id=123: ...`) so concurrent runs are easier to tell apart. diff --git a/acceptance/bundle/resources/job_runs/basic/output.txt b/acceptance/bundle/resources/job_runs/basic/output.txt index f1fd106d868..ab69e6df19c 100644 --- a/acceptance/bundle/resources/job_runs/basic/output.txt +++ b/acceptance/bundle/resources/job_runs/basic/output.txt @@ -34,8 +34,8 @@ Resources: >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-basic/default/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/job_runs/failed_run/output.txt b/acceptance/bundle/resources/job_runs/failed_run/output.txt index 1c791241bb0..ca5d9bd2ce9 100644 --- a/acceptance/bundle/resources/job_runs/failed_run/output.txt +++ b/acceptance/bundle/resources/job_runs/failed_run/output.txt @@ -3,7 +3,7 @@ >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [RUN_URL] +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [RUN_URL] Error: cannot create resources.job_runs.my_run: waiting after creating id=[MY_RUN_ID]: run did not succeed: FAILED: Task main failed with message: Workload failed, see run output for details. task "main": RuntimeError: intentional failure run page: [RUN_URL] @@ -58,7 +58,7 @@ Updating deployment state... >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... Deploying resources... -job run [MY_RUN_ID_2]: Run URL: [RUN_URL] +Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [RUN_URL] Error: cannot recreate resources.job_runs.my_run: waiting after creating id=[MY_RUN_ID_2]: run did not succeed: FAILED: Task main failed with message: Workload failed, see run output for details. task "main": RuntimeError: intentional failure run page: [RUN_URL] diff --git a/acceptance/bundle/resources/job_runs/interrupted_run/output.txt b/acceptance/bundle/resources/job_runs/interrupted_run/output.txt index e8f85ed5ae5..0cef3d98ecf 100644 --- a/acceptance/bundle/resources/job_runs/interrupted_run/output.txt +++ b/acceptance/bundle/resources/job_runs/interrupted_run/output.txt @@ -69,8 +69,8 @@ Exit code: 1 >>> [CLI] bundle deploy --plan tmp.plan.json Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-interrupted-run/default/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/job_runs/job_parameters/output.txt b/acceptance/bundle/resources/job_runs/job_parameters/output.txt index cceb94e5b85..ba81f565993 100644 --- a/acceptance/bundle/resources/job_runs/job_parameters/output.txt +++ b/acceptance/bundle/resources/job_runs/job_parameters/output.txt @@ -3,8 +3,8 @@ >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-job-parameters/default/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/job_runs/redeploy/output.txt b/acceptance/bundle/resources/job_runs/redeploy/output.txt index 82642b86ca0..c63b6e463f3 100644 --- a/acceptance/bundle/resources/job_runs/redeploy/output.txt +++ b/acceptance/bundle/resources/job_runs/redeploy/output.txt @@ -3,8 +3,8 @@ >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-redeploy/default/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete! @@ -95,8 +95,8 @@ Resources: >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-redeploy/default/files... Deploying resources... -job run [MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[NUMID] -job run [MY_RUN_ID_2]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID_2]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID_2]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID_2]: SUCCESS Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/job_runs/wait/output.txt b/acceptance/bundle/resources/job_runs/wait/output.txt index b96624932a1..163818bc34c 100644 --- a/acceptance/bundle/resources/job_runs/wait/output.txt +++ b/acceptance/bundle/resources/job_runs/wait/output.txt @@ -3,8 +3,8 @@ >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [RUN_URL] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [RUN_URL] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete! diff --git a/bundle/direct/apply.go b/bundle/direct/apply.go index cbb0a2d45ff..81e2279630c 100644 --- a/bundle/direct/apply.go +++ b/bundle/direct/apply.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "reflect" + "strings" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct/dresources" @@ -14,8 +15,14 @@ import ( "github.com/databricks/databricks-sdk-go/apierr" ) +func (d *DeploymentUnit) withResourceKey(ctx context.Context) context.Context { + // Match plan output (e.g. "job_runs.foo"), not the internal "resources." form. + return dresources.WithResourceKey(ctx, strings.TrimPrefix(d.ResourceKey, "resources.")) +} + func (d *DeploymentUnit) Destroy(ctx context.Context, db *dstate.DeploymentState) error { ctx = log.WithPrefix(ctx, "destroying "+d.ResourceKey) + ctx = d.withResourceKey(ctx) id := db.GetResourceID(d.ResourceKey) if id == "" { log.Infof(ctx, "Cannot delete %s: missing from state", d.ResourceKey) @@ -27,6 +34,7 @@ func (d *DeploymentUnit) Destroy(ctx context.Context, db *dstate.DeploymentState func (d *DeploymentUnit) Deploy(ctx context.Context, db *dstate.DeploymentState, newState any, actionType deployplan.ActionType, planEntry *deployplan.PlanEntry) error { ctx = log.WithPrefix(ctx, "deploying "+d.ResourceKey) + ctx = d.withResourceKey(ctx) if actionType == deployplan.Create { return d.Create(ctx, db, newState) } diff --git a/bundle/direct/dresources/job_run.go b/bundle/direct/dresources/job_run.go index a71757ca6d2..9327efd4134 100644 --- a/bundle/direct/dresources/job_run.go +++ b/bundle/direct/dresources/job_run.go @@ -324,11 +324,13 @@ func runIsTerminal(state jobs.RunLifeCycleState) bool { state == jobs.RunLifeCycleStateInternalError } -// reportRunLine names the run, since resources deploy concurrently onto one stream. +// reportRunLine names the resource and run id so concurrent deploys stay readable. +// Deploy attaches the key via [WithResourceKey]. func reportRunLine(ctx context.Context, runID int64, msg string) { - if cmdio.HasIO(ctx) { - cmdio.LogString(ctx, fmt.Sprintf("job run %d: %s", runID, msg)) + if !cmdio.HasIO(ctx) { + return } + cmdio.LogString(ctx, fmt.Sprintf("Output from %s: id=%d: %s", ResourceKey(ctx), runID, msg)) } // DoUpdate finishes the wait an interrupted deploy abandoned. diff --git a/bundle/direct/dresources/job_run_test.go b/bundle/direct/dresources/job_run_test.go index 4138ab18a66..84bd2d602c0 100644 --- a/bundle/direct/dresources/job_run_test.go +++ b/bundle/direct/dresources/job_run_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/testserver" "github.com/databricks/databricks-sdk-go" @@ -78,6 +79,15 @@ func TestJobRunWaitSucceeds(t *testing.T) { assert.Equal(t, jobs.RunResultStateSuccess, remote.State.ResultState) } +func TestReportRunLineIncludesResourceKey(t *testing.T) { + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + ctx = WithResourceKey(ctx, "job_runs.my_run") + + reportRunLine(ctx, 123, "SUCCESS") + + assert.Equal(t, "Output from job_runs.my_run: id=123: SUCCESS\n", stderr.String()) +} + func TestJobRunWaitFailsOnFailedResult(t *testing.T) { client := jobRunClient(t, &jobs.RunState{ LifeCycleState: jobs.RunLifeCycleStateTerminated, diff --git a/bundle/direct/dresources/resource_key.go b/bundle/direct/dresources/resource_key.go new file mode 100644 index 00000000000..640c02243d4 --- /dev/null +++ b/bundle/direct/dresources/resource_key.go @@ -0,0 +1,17 @@ +package dresources + +import "context" + +type resourceKeyType struct{} + +// WithResourceKey attaches the bundle resource key used in cmdio progress lines. +// key is the plan form without the "resources." prefix (e.g. "job_runs.foo"). +func WithResourceKey(ctx context.Context, key string) context.Context { + return context.WithValue(ctx, resourceKeyType{}, key) +} + +// ResourceKey returns the key attached by [WithResourceKey], or "" if none. +func ResourceKey(ctx context.Context) string { + key, _ := ctx.Value(resourceKeyType{}).(string) + return key +} diff --git a/bundle/direct/dresources/resource_key_test.go b/bundle/direct/dresources/resource_key_test.go new file mode 100644 index 00000000000..8d5bdfe54bb --- /dev/null +++ b/bundle/direct/dresources/resource_key_test.go @@ -0,0 +1,13 @@ +package dresources + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestResourceKeyRoundTrip(t *testing.T) { + ctx := WithResourceKey(t.Context(), "job_runs.foo") + assert.Equal(t, "job_runs.foo", ResourceKey(ctx)) + assert.Equal(t, "", ResourceKey(t.Context())) +} From 0957da1193c26783c0ec84a6ff64a2bef17f8bc1 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 11 Aug 2026 11:28:37 +0000 Subject: [PATCH 2/3] fix: use assert.Empty for missing resource key Satisfy testifylint so CI lint passes on the resource key round-trip test. --- bundle/direct/dresources/resource_key_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/direct/dresources/resource_key_test.go b/bundle/direct/dresources/resource_key_test.go index 8d5bdfe54bb..b6e9c72b6eb 100644 --- a/bundle/direct/dresources/resource_key_test.go +++ b/bundle/direct/dresources/resource_key_test.go @@ -9,5 +9,5 @@ import ( func TestResourceKeyRoundTrip(t *testing.T) { ctx := WithResourceKey(t.Context(), "job_runs.foo") assert.Equal(t, "job_runs.foo", ResourceKey(ctx)) - assert.Equal(t, "", ResourceKey(t.Context())) + assert.Empty(t, ResourceKey(t.Context())) } From 2f4aaca191adea5bce11b6709e172bb54a043bac Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 11 Aug 2026 11:43:10 +0000 Subject: [PATCH 3/3] test: update retried_run_now golden for resource-key cmdio --- .../bundle/resources/job_runs/retried_run_now/output.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/acceptance/bundle/resources/job_runs/retried_run_now/output.txt b/acceptance/bundle/resources/job_runs/retried_run_now/output.txt index 2811de8bb90..ffb26d1485b 100644 --- a/acceptance/bundle/resources/job_runs/retried_run_now/output.txt +++ b/acceptance/bundle/resources/job_runs/retried_run_now/output.txt @@ -3,8 +3,8 @@ >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/job-runs-retried-run-now/default/files... Deploying resources... -job run [MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] -job run [MY_RUN_ID]: SUCCESS +Output from job_runs.my_run: id=[MY_RUN_ID]: Run URL: [DATABRICKS_URL]/jobs/[MY_JOB_ID]/runs/[MY_RUN_ID]?o=[NUMID] +Output from job_runs.my_run: id=[MY_RUN_ID]: SUCCESS Updating deployment state... Deployment complete!