From a9d2b2726e55745f9212928573198cf82aa416e1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 15 Apr 2026 16:53:31 +0000 Subject: [PATCH 1/6] Fix nil pointer dereference in WaitForDeploymentToComplete Add nil checks for AppDeployment.Status before accessing Status.State. The Status field is a pointer that can be nil when the API returns a deployment before its status is populated. The OnProgress callback in the same file already had this check. Task: 001.md Co-authored-by: Isaac --- bundle/appdeploy/app.go | 2 ++ bundle/appdeploy/app_test.go | 52 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 bundle/appdeploy/app_test.go diff --git a/bundle/appdeploy/app.go b/bundle/appdeploy/app.go index dd0602f670..6bea74fac3 100644 --- a/bundle/appdeploy/app.go +++ b/bundle/appdeploy/app.go @@ -49,6 +49,7 @@ func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource * // WaitForDeploymentToComplete waits for active and pending deployments on an app to finish. func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceClient, app *sdkapps.App) error { if app.ActiveDeployment != nil && + app.ActiveDeployment.Status != nil && app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the active deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.ActiveDeployment.DeploymentId, 20*time.Minute, nil) @@ -59,6 +60,7 @@ func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceCli } if app.PendingDeployment != nil && + app.PendingDeployment.Status != nil && app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the pending deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.PendingDeployment.DeploymentId, 20*time.Minute, nil) diff --git a/bundle/appdeploy/app_test.go b/bundle/appdeploy/app_test.go new file mode 100644 index 0000000000..ee15492ab4 --- /dev/null +++ b/bundle/appdeploy/app_test.go @@ -0,0 +1,52 @@ +package appdeploy + +import ( + "testing" + + sdkapps "github.com/databricks/databricks-sdk-go/service/apps" + "github.com/stretchr/testify/assert" +) + +func TestWaitForDeploymentToCompleteNilStatus(t *testing.T) { + ctx := t.Context() + + // ActiveDeployment with nil Status should not panic. + app := &sdkapps.App{ + ActiveDeployment: &sdkapps.AppDeployment{ + Status: nil, + }, + PendingDeployment: &sdkapps.AppDeployment{ + Status: nil, + }, + } + err := WaitForDeploymentToComplete(ctx, nil, app) + assert.NoError(t, err) +} + +func TestWaitForDeploymentToCompleteNilDeployments(t *testing.T) { + ctx := t.Context() + + app := &sdkapps.App{} + err := WaitForDeploymentToComplete(ctx, nil, app) + assert.NoError(t, err) +} + +func TestWaitForDeploymentToCompleteNonInProgress(t *testing.T) { + ctx := t.Context() + + // Status with a non-InProgress state should not wait. + app := &sdkapps.App{ + ActiveDeployment: &sdkapps.AppDeployment{ + Status: &sdkapps.AppDeploymentStatus{ + State: sdkapps.AppDeploymentStateSucceeded, + }, + }, + PendingDeployment: &sdkapps.AppDeployment{ + Status: &sdkapps.AppDeploymentStatus{ + State: sdkapps.AppDeploymentStateFailed, + }, + }, + } + err := WaitForDeploymentToComplete(ctx, nil, app) + assert.NoError(t, err) +} From ce4da0c51594c01caf8e2ffcc084e2ea7d29a02f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 15 Apr 2026 17:08:24 +0000 Subject: [PATCH 2/6] Wait (not skip) when deployment Status is nil in WaitForDeploymentToComplete Change the condition from `Status != nil && State == InProgress` to `Status == nil || State == InProgress`. When the API returns a deployment with no status yet, the function now waits for it to complete instead of silently skipping it. Update the nil-Status test to use testserver.New(t) and verify that the wait API endpoint is actually called. Task: 002.md Co-authored-by: Isaac --- bundle/appdeploy/app.go | 8 +++--- bundle/appdeploy/app_test.go | 50 +++++++++++++++++++++++++++--------- 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/bundle/appdeploy/app.go b/bundle/appdeploy/app.go index 6bea74fac3..8c31cc011d 100644 --- a/bundle/appdeploy/app.go +++ b/bundle/appdeploy/app.go @@ -49,8 +49,8 @@ func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource * // WaitForDeploymentToComplete waits for active and pending deployments on an app to finish. func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceClient, app *sdkapps.App) error { if app.ActiveDeployment != nil && - app.ActiveDeployment.Status != nil && - app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { + (app.ActiveDeployment.Status == nil || + app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress) { logProgress(ctx, "Waiting for the active deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.ActiveDeployment.DeploymentId, 20*time.Minute, nil) if err != nil { @@ -60,8 +60,8 @@ func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceCli } if app.PendingDeployment != nil && - app.PendingDeployment.Status != nil && - app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { + (app.PendingDeployment.Status == nil || + app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress) { logProgress(ctx, "Waiting for the pending deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.PendingDeployment.DeploymentId, 20*time.Minute, nil) if err != nil { diff --git a/bundle/appdeploy/app_test.go b/bundle/appdeploy/app_test.go index ee15492ab4..1dd2655c9b 100644 --- a/bundle/appdeploy/app_test.go +++ b/bundle/appdeploy/app_test.go @@ -1,40 +1,66 @@ -package appdeploy +package appdeploy_test import ( "testing" + "github.com/databricks/cli/bundle/appdeploy" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/testserver" + "github.com/databricks/databricks-sdk-go" sdkapps "github.com/databricks/databricks-sdk-go/service/apps" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestWaitForDeploymentToCompleteNilStatus(t *testing.T) { - ctx := t.Context() + server := testserver.New(t) + + getDeploymentCalled := 0 + server.Handle("GET", "/api/2.0/apps/{appName}/deployments/{deploymentId}", func(req testserver.Request) any { + getDeploymentCalled++ + return sdkapps.AppDeployment{ + DeploymentId: req.Vars["deploymentId"], + Status: &sdkapps.AppDeploymentStatus{ + State: sdkapps.AppDeploymentStateSucceeded, + }, + } + }) + + client, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "testtoken", + }) + require.NoError(t, err) + + ctx := cmdio.MockDiscard(t.Context()) - // ActiveDeployment with nil Status should not panic. app := &sdkapps.App{ + Name: "test-app", ActiveDeployment: &sdkapps.AppDeployment{ - Status: nil, + DeploymentId: "dep-1", + Status: nil, }, PendingDeployment: &sdkapps.AppDeployment{ - Status: nil, + DeploymentId: "dep-2", + Status: nil, }, } - err := WaitForDeploymentToComplete(ctx, nil, app) - assert.NoError(t, err) + err = appdeploy.WaitForDeploymentToComplete(ctx, client, app) + require.NoError(t, err) + assert.Equal(t, 2, getDeploymentCalled) } func TestWaitForDeploymentToCompleteNilDeployments(t *testing.T) { - ctx := t.Context() + ctx := cmdio.MockDiscard(t.Context()) app := &sdkapps.App{} - err := WaitForDeploymentToComplete(ctx, nil, app) + err := appdeploy.WaitForDeploymentToComplete(ctx, nil, app) assert.NoError(t, err) } func TestWaitForDeploymentToCompleteNonInProgress(t *testing.T) { - ctx := t.Context() + ctx := cmdio.MockDiscard(t.Context()) - // Status with a non-InProgress state should not wait. app := &sdkapps.App{ ActiveDeployment: &sdkapps.AppDeployment{ Status: &sdkapps.AppDeploymentStatus{ @@ -47,6 +73,6 @@ func TestWaitForDeploymentToCompleteNonInProgress(t *testing.T) { }, }, } - err := WaitForDeploymentToComplete(ctx, nil, app) + err := appdeploy.WaitForDeploymentToComplete(ctx, nil, app) assert.NoError(t, err) } From 646cec9b09664d74116c108d4bee733f3baeb29c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 15 Apr 2026 17:16:25 +0000 Subject: [PATCH 3/6] Update NEXT_CHANGELOG.md Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 0db4b902e1..27747f8610 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ ### CLI ### Bundles +* Fix nil pointer dereference in `WaitForDeploymentToComplete` when app deployment status is nil (denik/random-bugfixes-5) ### Dependency updates From 2ac9ed3e482cd14c357e29a8b9b3afdeada254a8 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 15 Apr 2026 19:29:14 +0000 Subject: [PATCH 4/6] Remove unit test for WaitForDeploymentToComplete Task: 003.md Co-authored-by: Isaac --- bundle/appdeploy/app_test.go | 78 ------------------------------------ 1 file changed, 78 deletions(-) delete mode 100644 bundle/appdeploy/app_test.go diff --git a/bundle/appdeploy/app_test.go b/bundle/appdeploy/app_test.go deleted file mode 100644 index 1dd2655c9b..0000000000 --- a/bundle/appdeploy/app_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package appdeploy_test - -import ( - "testing" - - "github.com/databricks/cli/bundle/appdeploy" - "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/testserver" - "github.com/databricks/databricks-sdk-go" - sdkapps "github.com/databricks/databricks-sdk-go/service/apps" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestWaitForDeploymentToCompleteNilStatus(t *testing.T) { - server := testserver.New(t) - - getDeploymentCalled := 0 - server.Handle("GET", "/api/2.0/apps/{appName}/deployments/{deploymentId}", func(req testserver.Request) any { - getDeploymentCalled++ - return sdkapps.AppDeployment{ - DeploymentId: req.Vars["deploymentId"], - Status: &sdkapps.AppDeploymentStatus{ - State: sdkapps.AppDeploymentStateSucceeded, - }, - } - }) - - client, err := databricks.NewWorkspaceClient(&databricks.Config{ - Host: server.URL, - Token: "testtoken", - }) - require.NoError(t, err) - - ctx := cmdio.MockDiscard(t.Context()) - - app := &sdkapps.App{ - Name: "test-app", - ActiveDeployment: &sdkapps.AppDeployment{ - DeploymentId: "dep-1", - Status: nil, - }, - PendingDeployment: &sdkapps.AppDeployment{ - DeploymentId: "dep-2", - Status: nil, - }, - } - err = appdeploy.WaitForDeploymentToComplete(ctx, client, app) - require.NoError(t, err) - assert.Equal(t, 2, getDeploymentCalled) -} - -func TestWaitForDeploymentToCompleteNilDeployments(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - - app := &sdkapps.App{} - err := appdeploy.WaitForDeploymentToComplete(ctx, nil, app) - assert.NoError(t, err) -} - -func TestWaitForDeploymentToCompleteNonInProgress(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - - app := &sdkapps.App{ - ActiveDeployment: &sdkapps.AppDeployment{ - Status: &sdkapps.AppDeploymentStatus{ - State: sdkapps.AppDeploymentStateSucceeded, - }, - }, - PendingDeployment: &sdkapps.AppDeployment{ - Status: &sdkapps.AppDeploymentStatus{ - State: sdkapps.AppDeploymentStateFailed, - }, - }, - } - err := appdeploy.WaitForDeploymentToComplete(ctx, nil, app) - assert.NoError(t, err) -} From 958f0d2ba9eef2a16dd29a5683023417fe6253e6 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Wed, 15 Apr 2026 19:38:06 +0000 Subject: [PATCH 5/6] [Terminated process] Only wait for deployments with known InProgress status --- bundle/appdeploy/app.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bundle/appdeploy/app.go b/bundle/appdeploy/app.go index 8c31cc011d..6bea74fac3 100644 --- a/bundle/appdeploy/app.go +++ b/bundle/appdeploy/app.go @@ -49,8 +49,8 @@ func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource * // WaitForDeploymentToComplete waits for active and pending deployments on an app to finish. func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceClient, app *sdkapps.App) error { if app.ActiveDeployment != nil && - (app.ActiveDeployment.Status == nil || - app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress) { + app.ActiveDeployment.Status != nil && + app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the active deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.ActiveDeployment.DeploymentId, 20*time.Minute, nil) if err != nil { @@ -60,8 +60,8 @@ func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceCli } if app.PendingDeployment != nil && - (app.PendingDeployment.Status == nil || - app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress) { + app.PendingDeployment.Status != nil && + app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the pending deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.PendingDeployment.DeploymentId, 20*time.Minute, nil) if err != nil { From 5348705843f35403403ac4dc629a39a5566d577f Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:52:06 +0000 Subject: [PATCH 6/6] Reverted by user via WEB UI --- NEXT_CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 27747f8610..0db4b902e1 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,7 +7,6 @@ ### CLI ### Bundles -* Fix nil pointer dereference in `WaitForDeploymentToComplete` when app deployment status is nil (denik/random-bugfixes-5) ### Dependency updates