Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import http.server, os

http.server.HTTPServer(
("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler
).serve_forever()
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
bundle:
name: config-drift-stopped-$UNIQUE_NAME

resources:
apps:
myapp:
name: $UNIQUE_NAME
description: my_app
source_code_path: ./app
config:
command:
- python
- app.py
env:
- name: MY_VAR
value: original_value
lifecycle:
started: true

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions acceptance/bundle/resources/apps/config-drift-stopped/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

=== Deploy with started=true so the app has an active deployment
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-stopped-[UNIQUE_NAME]/default/files...
Deploying resources...
✓ Deployment succeeded
Updating deployment state...
Deployment complete!

=== Change config while running: drift is detected (active deployment present)
>>> update_file.py databricks.yml original_value changed_value

>>> [CLI] bundle plan -o json
{
"config.env[0].value": {
"action": "update",
"old": "original_value",
"new": "changed_value",
"remote": "original_value"
}
}

=== Stop the app: the backend clears the active deployment
>>> [CLI] apps stop [UNIQUE_NAME]
"STOPPED"

=== Same config change is now skipped: no active deployment to compare against
>>> [CLI] bundle plan -o json
{
"config": {
"action": "skip",
"reason": "no active deployment",
"old": {
"command": [
"python",
"app.py"
],
"env": [
{
"name": "MY_VAR",
"value": "original_value"
}
]
},
"new": {
"command": [
"python",
"app.py"
],
"env": [
{
"name": "MY_VAR",
"value": "changed_value"
}
]
}
},
"config.env[0].value": {
"action": "skip",
"reason": "no active deployment",
"old": "original_value",
"new": "changed_value"
}
}

>>> [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-stopped-[UNIQUE_NAME]/default

Deleting files...
Destroy complete!
19 changes: 19 additions & 0 deletions acceptance/bundle/resources/apps/config-drift-stopped/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
envsubst < databricks.yml.tmpl > databricks.yml

cleanup() {
trace $CLI bundle destroy --auto-approve
}
trap cleanup EXIT

title "Deploy with started=true so the app has an active deployment"
trace $CLI bundle deploy

title "Change config while running: drift is detected (active deployment present)"
trace update_file.py databricks.yml original_value changed_value
trace $CLI bundle plan -o json | jq '.plan[].changes | with_entries(select(.key | startswith("config")))'

title "Stop the app: the backend clears the active deployment"
trace $CLI apps stop $UNIQUE_NAME | jq '.compute_status.state'

title "Same config change is now skipped: no active deployment to compare against"
trace $CLI bundle plan -o json | jq '.plan[].changes | with_entries(select(.key | startswith("config")))'
11 changes: 11 additions & 0 deletions acceptance/bundle/resources/apps/config-drift-stopped/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Local = true
Cloud = false

# Asserts plan classification, not requests; drop the inherited RecordRequests.
RecordRequests = false

Ignore = [".databricks", "databricks.yml"]

# Direct engine only: the skip logic lives in OverrideChangeDesc. Mirrors
# config-no-deployment.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
10 changes: 10 additions & 0 deletions acceptance/bundle/resources/apps/config-no-deployment/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import os

from flask import Flask

app = Flask(__name__)


@app.route("/")
def home():
return "Hello from config-no-deployment app!"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
command:
- python
- app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
bundle:
name: app-config-no-deployment

resources:
apps:
myapp:
name: test-app-config-no-deployment
description: my_app_description
source_code_path: ./app
config:
command: ["python", "app.py"]
env:
- name: MY_ENV_VAR
value: test_value

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions acceptance/bundle/resources/apps/config-no-deployment/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

=== Deploy: app created with no_compute; config is not deployed until the app starts
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/app-config-no-deployment/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

=== Plan is a no-op: config/source_code_path drift is skipped while the app has no active deployment
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged

=== Both deploy-only fields are skipped with reason "no active deployment"
>>> [CLI] bundle plan -o json
{
"config": {
"action": "skip",
"reason": "no active deployment",
"old": {
"command": [
"python",
"app.py"
],
"env": [
{
"name": "MY_ENV_VAR",
"value": "test_value"
}
]
},
"new": {
"command": [
"python",
"app.py"
],
"env": [
{
"name": "MY_ENV_VAR",
"value": "test_value"
}
]
}
},
"source_code_path": {
"action": "skip",
"reason": "no active deployment",
"old": "/Workspace/Users/[USERNAME]/.bundle/app-config-no-deployment/default/files/app",
"new": "/Workspace/Users/[USERNAME]/.bundle/app-config-no-deployment/default/files/app"
}
}
10 changes: 10 additions & 0 deletions acceptance/bundle/resources/apps/config-no-deployment/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
echo "*" > .gitignore

title "Deploy: app created with no_compute; config is not deployed until the app starts"
trace $CLI bundle deploy

title "Plan is a no-op: config/source_code_path drift is skipped while the app has no active deployment"
trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged"

title "Both deploy-only fields are skipped with reason \"no active deployment\""
trace $CLI bundle plan -o json | jq '.plan[].changes | {config, source_code_path}'
11 changes: 11 additions & 0 deletions acceptance/bundle/resources/apps/config-no-deployment/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Local = true
Cloud = false

# Asserts plan classification, not requests; drop the inherited RecordRequests.
RecordRequests = false

Ignore = [".databricks"]

# Direct engine only: the skip logic lives in OverrideChangeDesc. Mirrors
# lifecycle-started-omitted.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
bundle:
name: app-git-source-no-deployment

resources:
apps:
myapp:
name: test-app-git-source-no-deployment
description: my_app_description
git_source:
branch: main

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@

=== Deploy: app created with no_compute; git_source is not deployed until the app starts
>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/app-git-source-no-deployment/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

=== Plan is a no-op: git_source drift is skipped while the app has no active deployment
>>> [CLI] bundle plan
Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged

=== git_source is skipped with reason "no active deployment"
>>> [CLI] bundle plan -o json
{
"git_source": {
"action": "skip",
"reason": "no active deployment",
"old": {
"branch": "main"
},
"new": {
"branch": "main"
}
}
}
10 changes: 10 additions & 0 deletions acceptance/bundle/resources/apps/git-source-no-deployment/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
echo "*" > .gitignore

title "Deploy: app created with no_compute; git_source is not deployed until the app starts"
trace $CLI bundle deploy

title "Plan is a no-op: git_source drift is skipped while the app has no active deployment"
trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged"

title "git_source is skipped with reason \"no active deployment\""
trace $CLI bundle plan -o json | jq '.plan[].changes | {git_source}'
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Local = true
Cloud = false

# Asserts plan classification, not requests; drop the inherited RecordRequests.
RecordRequests = false

Ignore = [".databricks"]

# Direct engine only: the skip logic lives in OverrideChangeDesc. Mirrors
# config-no-deployment.
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
18 changes: 13 additions & 5 deletions bundle/direct/dresources/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,20 @@ func hasAppChanges(entry *PlanEntry) bool {
return entry.Changes.HasChangeExcept("source_code_path", "config", "git_source", "lifecycle", "lifecycle.started")
}

// OverrideChangeDesc skips source_code_path drift when the remote value is empty.
// This happens when an app has no deployment yet (DefaultSourceCodePath is unset).
// OverrideChangeDesc suppresses drift on the deploy-only fields (source_code_path,
// config, git_source) when the app has no active deployment. DoRead reads these only
// from the active deployment, which is absent before the first deploy and, for
// non-scalable apps, cleared on stop. Without it the remote side is empty, so any
// diff is spurious; the change applies on the next start (see manageLifecycle). Real
// drift is still reported once a deployment exists.
func (*ResourceApp) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *AppRemote) error {
if path.String() == "source_code_path" && (remote.SourceCodePath == "" || remote.SourceCodePath == "null") {
change.Action = deployplan.Skip
change.Reason = "no deployment"
// Prefix(1) so a nested diff (e.g. config.command) matches its top-level field.
switch path.Prefix(1).String() {
case "source_code_path", "config", "git_source":
if remote.ActiveDeployment == nil {
change.Action = deployplan.Skip
change.Reason = "no active deployment"
}
}
return nil
}
Expand Down
6 changes: 6 additions & 0 deletions libs/testserver/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ func (s *FakeWorkspace) AppsStop(_ Request, name string) Response {
State: "UNAVAILABLE",
Message: appStatusUnavailableMessage,
}
// Non-scalable apps clear the active deployment on stop (pending is always
// cleared); only default_source_code_path is retained. The fixtures are all
// non-scalable, so clear unconditionally. Scalable apps retain it on stop, which
// this fake does not model.
app.ActiveDeployment = nil
app.PendingDeployment = nil
s.Apps[name] = app

return Response{Body: app}
Expand Down
Loading