From 3f0ecba2934587e351dfdec10c91755fb1a5ecad Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Wed, 22 Apr 2026 21:12:12 +0200 Subject: [PATCH 1/3] direct: Manage app lifecycle on create as well --- .../apps/config-drift/databricks.yml.tmpl | 21 +++++++++- .../apps/config-drift/out.plan.direct.json | 16 +++----- .../resources/apps/config-drift/output.txt | 33 ++++++++++------ .../bundle/resources/apps/config-drift/script | 20 +++++----- .../apps/create_already_exists/output.txt | 11 +++++- .../apps/lifecycle-started/output.txt | 8 ++++ bundle/direct/dresources/app.go | 38 ++++++++++++------- libs/testserver/apps.go | 15 ++++++++ 8 files changed, 112 insertions(+), 50 deletions(-) diff --git a/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl b/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl index b38d8f92f5..960a5d269b 100644 --- a/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl +++ b/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl @@ -4,9 +4,8 @@ bundle: resources: apps: myapp: - name: $UNIQUE_NAME + name: a-$UNIQUE_NAME description: my_app - source_code_path: ./app config: command: - python @@ -16,3 +15,21 @@ resources: value: original_value lifecycle: started: true + +targets: + first: + default: true + resources: + apps: + myapp: + source_code_path: ./app + second: + resources: + apps: + myapp: + name: b-$UNIQUE_NAME + git_source: + git_repository: + url: https://github.com/databricks/databricks-cli.git + provider: gitHub + branch: main diff --git a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json index fe52fca587..b05359f53c 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json +++ b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json @@ -20,7 +20,7 @@ } ], "mode": "SNAPSHOT", - "source_code_path": "./app", + "source_code_path": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files/app", "status": { "message": "Deployment succeeded", "state": "SUCCEEDED" @@ -92,12 +92,12 @@ "default_source_code_path": { "action": "skip", "reason": "spec:output_only", - "remote": "./app" + "remote": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files/app" }, "id": { "action": "skip", "reason": "spec:output_only", - "remote": "1000" + "remote": "1001" }, "service_principal_client_id": { "action": "skip", @@ -112,17 +112,11 @@ "service_principal_name": { "action": "skip", "reason": "spec:output_only", - "remote": "app-[UNIQUE_NAME]" - }, - "source_code_path": { - "action": "update", - "old": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files/app", - "new": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files/app", - "remote": "./app" + "remote": "app-a-[UNIQUE_NAME]" }, "url": { "action": "skip", "reason": "spec:output_only", - "remote": "[UNIQUE_NAME]-123.cloud.databricksapps.com" + "remote": "a-[UNIQUE_NAME]-123.cloud.databricksapps.com" } } diff --git a/acceptance/bundle/resources/apps/config-drift/output.txt b/acceptance/bundle/resources/apps/config-drift/output.txt index 6a82393fa9..9d747e3199 100644 --- a/acceptance/bundle/resources/apps/config-drift/output.txt +++ b/acceptance/bundle/resources/apps/config-drift/output.txt @@ -1,14 +1,6 @@ -=== First deploy: creates app >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... -Deploying resources... -Updating deployment state... -Deployment complete! - -=== Second deploy: pushes code with config ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files... Deploying resources... Updating deployment state... Deployment complete! @@ -17,6 +9,8 @@ Deployment complete! >>> [CLI] bundle plan Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +>>> [CLI] apps get a-[UNIQUE_NAME] --output json + === Simulate out-of-band deployment with changed command and env === Plan should detect config drift >>> [CLI] bundle plan @@ -26,7 +20,7 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged === Redeploy to fix drift >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files... Deploying resources... Updating deployment state... Deployment complete! @@ -35,9 +29,15 @@ Deployment complete! >>> [CLI] bundle plan Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +>>> [CLI] bundle deploy -t second +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/second/files... +Deploying resources... +Updating deployment state... +Deployment complete! + === Simulate out-of-band deployment with git_source added === Plan should detect git_source drift ->>> [CLI] bundle plan +>>> [CLI] bundle plan -t second update apps.myapp Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged @@ -46,7 +46,16 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged The following resources will be deleted: delete resources.apps.myapp -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first + +Deleting files... +Destroy complete! + +>>> [CLI] bundle destroy --auto-approve -t second +The following resources will be deleted: + delete resources.apps.myapp + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/second Deleting files... Destroy complete! diff --git a/acceptance/bundle/resources/apps/config-drift/script b/acceptance/bundle/resources/apps/config-drift/script index e37ac80fde..57619f19ea 100644 --- a/acceptance/bundle/resources/apps/config-drift/script +++ b/acceptance/bundle/resources/apps/config-drift/script @@ -2,22 +2,21 @@ envsubst < databricks.yml.tmpl > databricks.yml cleanup() { trace $CLI bundle destroy --auto-approve + trace $CLI bundle destroy --auto-approve -t second rm -f out.requests.txt } trap cleanup EXIT -title "First deploy: creates app" -trace $CLI bundle deploy - -title "Second deploy: pushes code with config" trace $CLI bundle deploy title "Verify no drift after deploy" trace $CLI bundle plan +SOURCE_CODE_PATH=$(trace $CLI apps get a-$UNIQUE_NAME --output json | jq -r '.active_deployment.source_code_path') + title "Simulate out-of-band deployment with changed command and env" -$CLI apps deploy $UNIQUE_NAME --no-wait --json '{ - "source_code_path": "./app", +$CLI apps deploy a-$UNIQUE_NAME --no-wait --json '{ + "source_code_path": "'$SOURCE_CODE_PATH'", "mode": "SNAPSHOT", "command": ["streamlit", "run", "dashboard.py"], "env_vars": [ @@ -36,14 +35,15 @@ trace $CLI bundle deploy title "Verify no drift after fix" trace $CLI bundle plan + +trace $CLI bundle deploy -t second title "Simulate out-of-band deployment with git_source added" -$CLI apps deploy $UNIQUE_NAME --no-wait --json '{ - "source_code_path": "./app", +$CLI apps deploy b-$UNIQUE_NAME --no-wait --json '{ "mode": "SNAPSHOT", - "git_source": {"branch": "feature-branch"}, + "git_source": {"branch": "main"}, "command": ["python", "app.py"], "env_vars": [{"name": "MY_VAR", "value": "original_value"}] }' > /dev/null title "Plan should detect git_source drift" -trace $CLI bundle plan +trace $CLI bundle plan -t second diff --git a/acceptance/bundle/resources/apps/create_already_exists/output.txt b/acceptance/bundle/resources/apps/create_already_exists/output.txt index 63c0b4a245..a5d1841c27 100644 --- a/acceptance/bundle/resources/apps/create_already_exists/output.txt +++ b/acceptance/bundle/resources/apps/create_already_exists/output.txt @@ -1,6 +1,14 @@ >>> [CLI] apps create test-app-already-exists { + "active_deployment": { + "deployment_id":"deploy-[NUMID]", + "source_code_path":"/Workspace/Users/[USERNAME]/test-app-already-exists", + "status": { + "message":"Deployment succeeded", + "state":"SUCCEEDED" + } + }, "app_status": { "message":"Application is running.", "state":"RUNNING" @@ -10,7 +18,8 @@ "message":"App compute is active.", "state":"ACTIVE" }, - "id":"1000", + "default_source_code_path":"/Workspace/Users/[USERNAME]/test-app-already-exists", + "id":"1001", "name":"test-app-already-exists", "service_principal_client_id":"[UUID]", "service_principal_id":[NUMID], diff --git a/acceptance/bundle/resources/apps/lifecycle-started/output.txt b/acceptance/bundle/resources/apps/lifecycle-started/output.txt index 68e56a6814..cfe10a2d65 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/output.txt +++ b/acceptance/bundle/resources/apps/lifecycle-started/output.txt @@ -15,6 +15,14 @@ Deployment complete! "name": "[UNIQUE_NAME]" } } +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/deployments", + "body": { + "mode": "SNAPSHOT", + "source_code_path": "/Workspace/Users/[USERNAME]/.bundle/lifecycle-started-[UNIQUE_NAME]/default/files/app" + } +} >>> errcode [CLI] apps get [UNIQUE_NAME] "ACTIVE" diff --git a/bundle/direct/dresources/app.go b/bundle/direct/dresources/app.go index 76a0881f9e..5f88217061 100644 --- a/bundle/direct/dresources/app.go +++ b/bundle/direct/dresources/app.go @@ -199,46 +199,48 @@ func (r *ResourceApp) DoUpdate(ctx context.Context, id string, config *AppState, } } + return nil, r.manageLifecycle(ctx, id, config, remoteIsStarted(entry)) +} + +func (r *ResourceApp) manageLifecycle(ctx context.Context, id string, config *AppState, alreadyStarted bool) error { if config.Lifecycle == nil || config.Lifecycle.Started == nil { - return nil, nil + return nil } desiredStarted := *config.Lifecycle.Started - remoteStarted := remoteIsStarted(entry) - if desiredStarted { // lifecycle.started=true: ensure the app compute is running and deploy the latest code. - if !remoteStarted { + if !alreadyStarted { startWaiter, err := r.client.Apps.Start(ctx, apps.StartAppRequest{Name: id}) if err != nil { - return nil, err + return err } startedApp, err := startWaiter.Get() if err != nil { - return nil, err + return err } if err := appdeploy.WaitForDeploymentToComplete(ctx, r.client, startedApp); err != nil { - return nil, err + return err } } deployment := appdeploy.BuildDeployment(config.SourceCodePath, config.Config, config.GitSource) if err := appdeploy.Deploy(ctx, r.client, id, deployment); err != nil { - return nil, err + return err } } else { // lifecycle.started=false: ensure the app compute is stopped. - if remoteStarted { + if alreadyStarted { stopWaiter, err := r.client.Apps.Stop(ctx, apps.StopAppRequest{Name: id}) if err != nil { - return nil, err + return err } if _, err = stopWaiter.Get(); err != nil { - return nil, err + return err } } } - return nil, nil + return nil } // deployOnlyFields are AppState fields managed via the Deploy API, not the App Update API. @@ -266,7 +268,7 @@ func hasAppChanges(entry *PlanEntry) bool { // OverrideChangeDesc skips source_code_path drift when the remote value is empty. // This happens when an app has no deployment yet (DefaultSourceCodePath is unset). func (*ResourceApp) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *AppRemote) error { - if path.String() == "source_code_path" && remote.SourceCodePath == "" { + if path.String() == "source_code_path" && (remote.SourceCodePath == "" || remote.SourceCodePath == "null") { change.Action = deployplan.Skip change.Reason = "no deployment" } @@ -320,7 +322,15 @@ func (r *ResourceApp) DoDelete(ctx context.Context, id string) error { } func (r *ResourceApp) WaitAfterCreate(ctx context.Context, config *AppState) (*AppRemote, error) { - return r.waitForApp(ctx, r.client, config.Name) + remote, err := r.waitForApp(ctx, r.client, config.Name) + if err != nil { + return nil, err + } + alreadyStarted := remote.Lifecycle != nil && remote.Lifecycle.Started != nil && *remote.Lifecycle.Started + if err := r.manageLifecycle(ctx, config.Name, config, alreadyStarted); err != nil { + return nil, err + } + return remote, nil } // waitForApp waits for the app to reach the target state. The target state is either ACTIVE or STOPPED. diff --git a/libs/testserver/apps.go b/libs/testserver/apps.go index e3726c650d..7b19d8ac42 100644 --- a/libs/testserver/apps.go +++ b/libs/testserver/apps.go @@ -221,6 +221,21 @@ func (s *FakeWorkspace) AppsUpsert(req Request, name string) Response { State: "ACTIVE", Message: "App compute is active.", } + + // Simulate the apps platform side effect: when an app is created, it is deployed with the default source code path. + deployment := apps.AppDeployment{ + SourceCodePath: "/Workspace/Users/tester@databricks.com/" + name, + } + + deployment.DeploymentId = fmt.Sprintf("deploy-%d", nextID()) + deployment.Status = &apps.AppDeploymentStatus{ + State: apps.AppDeploymentStateSucceeded, + Message: "Deployment succeeded", + } + + app.ActiveDeployment = &deployment + app.DefaultSourceCodePath = deployment.SourceCodePath + s.Apps[name] = app } app.Url = name + "-123.cloud.databricksapps.com" From fb08fa22c95537ef6ae7e00d67565c2cff429d13 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Wed, 22 Apr 2026 22:08:34 +0200 Subject: [PATCH 2/3] fixed test output --- .../generate/app_not_yet_deployed/output.txt | 6 +- .../generate/app_not_yet_deployed/script | 2 +- .../apps/config-drift/databricks.yml.tmpl | 21 +---- .../apps/config-drift/out.plan.direct.json | 79 ------------------- .../resources/apps/config-drift/output.txt | 30 +------ .../bundle/resources/apps/config-drift/script | 22 ++---- .../apps/create_already_exists/output.txt | 2 +- acceptance/cmd/workspace/apps/output.txt | 18 +++++ bundle/appdeploy/app.go | 4 + libs/testserver/apps.go | 1 - 10 files changed, 39 insertions(+), 146 deletions(-) diff --git a/acceptance/bundle/generate/app_not_yet_deployed/output.txt b/acceptance/bundle/generate/app_not_yet_deployed/output.txt index 2ebe86fd89..b6a68104e5 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/output.txt +++ b/acceptance/bundle/generate/app_not_yet_deployed/output.txt @@ -1,5 +1,5 @@ ->>> [CLI] apps create my-app +>>> [CLI] apps create my-app --no-compute --no-wait { "app_status": { "message":"Application is running.", @@ -7,8 +7,8 @@ }, "compute_size":"MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message":"App compute is stopped.", + "state":"STOPPED" }, "id":"1000", "name":"my-app", diff --git a/acceptance/bundle/generate/app_not_yet_deployed/script b/acceptance/bundle/generate/app_not_yet_deployed/script index f9521c5717..8883d05d15 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/script +++ b/acceptance/bundle/generate/app_not_yet_deployed/script @@ -1,2 +1,2 @@ -trace $CLI apps create my-app +trace $CLI apps create my-app --no-compute --no-wait trace $CLI bundle generate app --existing-app-name my-app --config-dir . --key out diff --git a/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl b/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl index 960a5d269b..b38d8f92f5 100644 --- a/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl +++ b/acceptance/bundle/resources/apps/config-drift/databricks.yml.tmpl @@ -4,8 +4,9 @@ bundle: resources: apps: myapp: - name: a-$UNIQUE_NAME + name: $UNIQUE_NAME description: my_app + source_code_path: ./app config: command: - python @@ -15,21 +16,3 @@ resources: value: original_value lifecycle: started: true - -targets: - first: - default: true - resources: - apps: - myapp: - source_code_path: ./app - second: - resources: - apps: - myapp: - name: b-$UNIQUE_NAME - git_source: - git_repository: - url: https://github.com/databricks/databricks-cli.git - provider: gitHub - branch: main diff --git a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json index b05359f53c..9d080b8c97 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json +++ b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json @@ -1,53 +1,4 @@ { - "active_deployment": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "command": [ - "streamlit", - "run", - "dashboard.py" - ], - "deployment_id": "deploy-[NUMID]", - "env_vars": [ - { - "name": "MY_VAR", - "value": "changed_value" - }, - { - "name": "NEW_VAR", - "value": "new_value" - } - ], - "mode": "SNAPSHOT", - "source_code_path": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files/app", - "status": { - "message": "Deployment succeeded", - "state": "SUCCEEDED" - } - } - }, - "app_status": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "message": "Application is running.", - "state": "RUNNING" - } - }, - "compute_size": { - "action": "skip", - "reason": "backend_default", - "remote": "MEDIUM" - }, - "compute_status": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "message": "App compute is active.", - "state": "ACTIVE" - } - }, "config.command": { "action": "update", "old": [ @@ -88,35 +39,5 @@ "value": "new_value" } ] - }, - "default_source_code_path": { - "action": "skip", - "reason": "spec:output_only", - "remote": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files/app" - }, - "id": { - "action": "skip", - "reason": "spec:output_only", - "remote": "1001" - }, - "service_principal_client_id": { - "action": "skip", - "reason": "spec:output_only", - "remote": "[UUID]" - }, - "service_principal_id": { - "action": "skip", - "reason": "spec:output_only", - "remote": [NUMID] - }, - "service_principal_name": { - "action": "skip", - "reason": "spec:output_only", - "remote": "app-a-[UNIQUE_NAME]" - }, - "url": { - "action": "skip", - "reason": "spec:output_only", - "remote": "a-[UNIQUE_NAME]-123.cloud.databricksapps.com" } } diff --git a/acceptance/bundle/resources/apps/config-drift/output.txt b/acceptance/bundle/resources/apps/config-drift/output.txt index 9d747e3199..4cc2933072 100644 --- a/acceptance/bundle/resources/apps/config-drift/output.txt +++ b/acceptance/bundle/resources/apps/config-drift/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! @@ -9,7 +9,7 @@ Deployment complete! >>> [CLI] bundle plan Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged ->>> [CLI] apps get a-[UNIQUE_NAME] --output json +>>> [CLI] apps get [UNIQUE_NAME] --output json === Simulate out-of-band deployment with changed command and env === Plan should detect config drift @@ -20,7 +20,7 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged === Redeploy to fix drift >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! @@ -29,33 +29,11 @@ Deployment complete! >>> [CLI] bundle plan Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged ->>> [CLI] bundle deploy -t second -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/second/files... -Deploying resources... -Updating deployment state... -Deployment complete! - -=== Simulate out-of-band deployment with git_source added -=== Plan should detect git_source drift ->>> [CLI] bundle plan -t second -update apps.myapp - -Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged - >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.apps.myapp -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/first - -Deleting files... -Destroy complete! - ->>> [CLI] bundle destroy --auto-approve -t second -The following resources will be deleted: - delete resources.apps.myapp - -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/second +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default Deleting files... Destroy complete! diff --git a/acceptance/bundle/resources/apps/config-drift/script b/acceptance/bundle/resources/apps/config-drift/script index 57619f19ea..e5466ae059 100644 --- a/acceptance/bundle/resources/apps/config-drift/script +++ b/acceptance/bundle/resources/apps/config-drift/script @@ -2,7 +2,6 @@ envsubst < databricks.yml.tmpl > databricks.yml cleanup() { trace $CLI bundle destroy --auto-approve - trace $CLI bundle destroy --auto-approve -t second rm -f out.requests.txt } trap cleanup EXIT @@ -12,10 +11,10 @@ trace $CLI bundle deploy title "Verify no drift after deploy" trace $CLI bundle plan -SOURCE_CODE_PATH=$(trace $CLI apps get a-$UNIQUE_NAME --output json | jq -r '.active_deployment.source_code_path') +SOURCE_CODE_PATH=$(trace $CLI apps get $UNIQUE_NAME --output json | jq -r '.active_deployment.source_code_path') title "Simulate out-of-band deployment with changed command and env" -$CLI apps deploy a-$UNIQUE_NAME --no-wait --json '{ +$CLI apps deploy $UNIQUE_NAME --no-wait --json '{ "source_code_path": "'$SOURCE_CODE_PATH'", "mode": "SNAPSHOT", "command": ["streamlit", "run", "dashboard.py"], @@ -27,7 +26,8 @@ $CLI apps deploy a-$UNIQUE_NAME --no-wait --json '{ title "Plan should detect config drift" trace $CLI bundle plan -$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' > out.plan.direct.json +# Skip entries with action "skip" +$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' > out.plan.direct.json title "Redeploy to fix drift" trace $CLI bundle deploy @@ -35,15 +35,5 @@ trace $CLI bundle deploy title "Verify no drift after fix" trace $CLI bundle plan - -trace $CLI bundle deploy -t second -title "Simulate out-of-band deployment with git_source added" -$CLI apps deploy b-$UNIQUE_NAME --no-wait --json '{ - "mode": "SNAPSHOT", - "git_source": {"branch": "main"}, - "command": ["python", "app.py"], - "env_vars": [{"name": "MY_VAR", "value": "original_value"}] - }' > /dev/null - -title "Plan should detect git_source drift" -trace $CLI bundle plan -t second +# TODO: add test for git_source drift when git_source is supported in the Deploy API +# Currently it fails with the error: Git source reference is required diff --git a/acceptance/bundle/resources/apps/create_already_exists/output.txt b/acceptance/bundle/resources/apps/create_already_exists/output.txt index a5d1841c27..bac47c04f9 100644 --- a/acceptance/bundle/resources/apps/create_already_exists/output.txt +++ b/acceptance/bundle/resources/apps/create_already_exists/output.txt @@ -19,7 +19,7 @@ "state":"ACTIVE" }, "default_source_code_path":"/Workspace/Users/[USERNAME]/test-app-already-exists", - "id":"1001", + "id":"1000", "name":"test-app-already-exists", "service_principal_client_id":"[UUID]", "service_principal_id":[NUMID], diff --git a/acceptance/cmd/workspace/apps/output.txt b/acceptance/cmd/workspace/apps/output.txt index ada0e6407f..e722afbe86 100644 --- a/acceptance/cmd/workspace/apps/output.txt +++ b/acceptance/cmd/workspace/apps/output.txt @@ -2,6 +2,14 @@ === Apps create with correct input >>> [CLI] apps create --json @input.json { + "active_deployment": { + "deployment_id":"deploy-[NUMID]", + "source_code_path":"/Workspace/Users/[USERNAME]/test-name", + "status": { + "message":"Deployment succeeded", + "state":"SUCCEEDED" + } + }, "app_status": { "message":"Application is running.", "state":"RUNNING" @@ -11,6 +19,7 @@ "message":"App compute is active.", "state":"ACTIVE" }, + "default_source_code_path":"/Workspace/Users/[USERNAME]/test-name", "description":"My app description.", "id":"1000", "name":"test-name", @@ -34,6 +43,14 @@ === Apps update with correct input >>> [CLI] apps update test-name --json @input.json { + "active_deployment": { + "deployment_id":"deploy-[NUMID]", + "source_code_path":"/Workspace/Users/[USERNAME]/test-name", + "status": { + "message":"Deployment succeeded", + "state":"SUCCEEDED" + } + }, "app_status": { "message":"Application is running.", "state":"RUNNING" @@ -43,6 +60,7 @@ "message":"App compute is active.", "state":"ACTIVE" }, + "default_source_code_path":"/Workspace/Users/[USERNAME]/test-name", "description":"My app description.", "id":"1001", "name":"test-name", diff --git a/bundle/appdeploy/app.go b/bundle/appdeploy/app.go index 6bea74fac3..4f590be60a 100644 --- a/bundle/appdeploy/app.go +++ b/bundle/appdeploy/app.go @@ -20,6 +20,10 @@ func logProgress(ctx context.Context, msg string) { // BuildDeployment constructs an AppDeployment from the app's source code path, inline config and git source. func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource *sdkapps.GitSource) sdkapps.AppDeployment { + // GitRepository is not supported in the Deploy API, only as part of Create, so we need to remove it. + if gitSource != nil { + gitSource.GitRepository = nil + } deployment := sdkapps.AppDeployment{ Mode: sdkapps.AppDeploymentModeSnapshot, SourceCodePath: sourcePath, diff --git a/libs/testserver/apps.go b/libs/testserver/apps.go index 7b19d8ac42..b35632e27a 100644 --- a/libs/testserver/apps.go +++ b/libs/testserver/apps.go @@ -235,7 +235,6 @@ func (s *FakeWorkspace) AppsUpsert(req Request, name string) Response { app.ActiveDeployment = &deployment app.DefaultSourceCodePath = deployment.SourceCodePath - s.Apps[name] = app } app.Url = name + "-123.cloud.databricksapps.com" From 5e8baf9f9cf49ce34184b6990192162d8f633b6a Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Thu, 23 Apr 2026 14:01:43 +0200 Subject: [PATCH 3/3] do not run on cloud --- .../bundle/resources/apps/config-drift/out.plan.direct.json | 2 ++ acceptance/bundle/resources/apps/config-drift/out.test.toml | 2 +- acceptance/bundle/resources/apps/config-drift/output.txt | 6 ++---- acceptance/bundle/resources/apps/config-drift/script | 6 +++--- acceptance/bundle/resources/apps/config-drift/test.toml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json index 9d080b8c97..0fe76b5ecf 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json +++ b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json @@ -1,3 +1,4 @@ +{} { "config.command": { "action": "update", @@ -41,3 +42,4 @@ ] } } +{} diff --git a/acceptance/bundle/resources/apps/config-drift/out.test.toml b/acceptance/bundle/resources/apps/config-drift/out.test.toml index 19b2c349a3..54146af564 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.test.toml +++ b/acceptance/bundle/resources/apps/config-drift/out.test.toml @@ -1,5 +1,5 @@ Local = true -Cloud = true +Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/config-drift/output.txt b/acceptance/bundle/resources/apps/config-drift/output.txt index 4cc2933072..6ad4310b31 100644 --- a/acceptance/bundle/resources/apps/config-drift/output.txt +++ b/acceptance/bundle/resources/apps/config-drift/output.txt @@ -6,8 +6,7 @@ Updating deployment state... Deployment complete! === Verify no drift after deploy ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +>>> [CLI] bundle plan -o json >>> [CLI] apps get [UNIQUE_NAME] --output json @@ -26,8 +25,7 @@ Updating deployment state... Deployment complete! === Verify no drift after fix ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +>>> [CLI] bundle plan -o json >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/apps/config-drift/script b/acceptance/bundle/resources/apps/config-drift/script index e5466ae059..4728255dc8 100644 --- a/acceptance/bundle/resources/apps/config-drift/script +++ b/acceptance/bundle/resources/apps/config-drift/script @@ -9,7 +9,7 @@ trap cleanup EXIT trace $CLI bundle deploy title "Verify no drift after deploy" -trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' > out.plan.direct.json SOURCE_CODE_PATH=$(trace $CLI apps get $UNIQUE_NAME --output json | jq -r '.active_deployment.source_code_path') @@ -27,13 +27,13 @@ $CLI apps deploy $UNIQUE_NAME --no-wait --json '{ title "Plan should detect config drift" trace $CLI bundle plan # Skip entries with action "skip" -$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' > out.plan.direct.json +$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' >> out.plan.direct.json title "Redeploy to fix drift" trace $CLI bundle deploy title "Verify no drift after fix" -trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' >> out.plan.direct.json # TODO: add test for git_source drift when git_source is supported in the Deploy API # Currently it fails with the error: Git source reference is required diff --git a/acceptance/bundle/resources/apps/config-drift/test.toml b/acceptance/bundle/resources/apps/config-drift/test.toml index b5c148642a..bf01b60b18 100644 --- a/acceptance/bundle/resources/apps/config-drift/test.toml +++ b/acceptance/bundle/resources/apps/config-drift/test.toml @@ -1,5 +1,5 @@ Local = true -Cloud = true +Cloud = false # This currently fails on Cloud due to incorrect API behaviour. RecordRequests = true Ignore = [".databricks", "databricks.yml"]