Skip to content

Commit 876287c

Browse files
authored
Fix resource references not correctly resolved in apps config section (#4964)
## Changes Fix resource references not correctly resolved in apps config section ## Why Fixes #4962 ## Tests Added an acceptance test <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent a5c9f95 commit 876287c

8 files changed

Lines changed: 170 additions & 1 deletion

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
### Bundles
1313
* Added support for lifecycle.started option for apps ([#4672](https://github.com/databricks/cli/pull/4672))
1414
* engine/direct: Fix permissions for resources.models ([#4941](https://github.com/databricks/cli/pull/4941))
15+
* Fix resource references not correctly resolved in apps config section ([#4964](https://github.com/databricks/cli/pull/4964))
1516
* Allow run_as for dashboards with embed_credentials set to false ([#4961](https://github.com/databricks/cli/pull/4961))
1617
* direct: Pass changed fields into update mask for apps instead of wildcard ([#4963](https://github.com/databricks/cli/pull/4963))
1718

acceptance/bundle/resources/apps/resource-refs/app/app.py

Whitespace-only changes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
bundle:
2+
name: resource-refs
3+
4+
variables:
5+
example_var:
6+
default: "example_value"
7+
8+
resources:
9+
apps:
10+
data_app:
11+
name: "data-app"
12+
source_code_path: ./app
13+
description: "A Streamlit app that uses a SQL warehouse"
14+
config:
15+
command: ["streamlit", "run", "app.py"]
16+
env:
17+
- name: MY_EXAMPLE_SCHEMA
18+
value: ${resources.schemas.example.catalog_name}
19+
- name: MY_EXAMPLE_JOB
20+
value: ${resources.jobs.example_job.name}
21+
- name: MY_EXAMPLE_JOB_ID
22+
value: ${resources.jobs.example_job.id}
23+
- name: MY_EXAMPLE_VAR
24+
value: ${var.example_var}
25+
schemas:
26+
example:
27+
name: "example_schema"
28+
catalog_name: "main"
29+
30+
jobs:
31+
example_job:
32+
name: "example_job"
33+
tasks:
34+
- task_key: "example_task"
35+
spark_python_task:
36+
python_file: "./app/app.py"
37+
environment_key: "default"
38+
environments:
39+
- environment_key: "default"
40+
spec:
41+
client: "1"

acceptance/bundle/resources/apps/resource-refs/out.test.toml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
2+
>>> [CLI] bundle deploy
3+
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/resource-refs/default/files...
4+
Deploying resources...
5+
Updating deployment state...
6+
Deployment complete!
7+
8+
>>> [CLI] bundle run data_app
9+
✓ Getting the status of the app data-app
10+
✓ App is in RUNNING state
11+
✓ App compute is in STOPPED state
12+
✓ Starting the app data-app
13+
✓ App is starting...
14+
✓ App is started!
15+
✓ Deployment succeeded
16+
You can access the app at data-app-123.cloud.databricksapps.com
17+
18+
>>> print_requests.py //apps
19+
{
20+
"method": "POST",
21+
"path": "/api/2.0/apps",
22+
"q": {
23+
"no_compute": "true"
24+
},
25+
"body": {
26+
"description": "A Streamlit app that uses a SQL warehouse",
27+
"name": "data-app"
28+
}
29+
}
30+
{
31+
"method": "POST",
32+
"path": "/api/2.0/apps/data-app/start",
33+
"body": {}
34+
}
35+
{
36+
"method": "POST",
37+
"path": "/api/2.0/apps/data-app/deployments",
38+
"body": {
39+
"command": [
40+
"streamlit",
41+
"run",
42+
"app.py"
43+
],
44+
"env_vars": [
45+
{
46+
"name": "MY_EXAMPLE_SCHEMA",
47+
"value": "main"
48+
},
49+
{
50+
"name": "MY_EXAMPLE_JOB",
51+
"value": "example_job"
52+
},
53+
{
54+
"name": "MY_EXAMPLE_JOB_ID",
55+
"value": "[NUMID]"
56+
},
57+
{
58+
"name": "MY_EXAMPLE_VAR",
59+
"value": "example_value"
60+
}
61+
],
62+
"mode": "SNAPSHOT",
63+
"source_code_path": "/Workspace/Users/[USERNAME]/.bundle/resource-refs/default/files/app"
64+
}
65+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
trace $CLI bundle deploy
2+
trace $CLI bundle run data_app
3+
trace print_requests.py //apps
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Local = true
2+
Cloud = false

bundle/run/app.go

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/databricks/cli/bundle/config/resources"
1212
"github.com/databricks/cli/bundle/run/output"
1313
"github.com/databricks/cli/libs/cmdio"
14+
"github.com/databricks/cli/libs/dyn"
15+
"github.com/databricks/cli/libs/dyn/convert"
16+
"github.com/databricks/cli/libs/dyn/dynvar"
1417
"github.com/databricks/databricks-sdk-go/service/apps"
1518
"github.com/spf13/cobra"
1619
)
@@ -135,10 +138,59 @@ func (a *appRunner) start(ctx context.Context) error {
135138

136139
func (a *appRunner) deploy(ctx context.Context) error {
137140
w := a.bundle.WorkspaceClient()
138-
deployment := appdeploy.BuildDeployment(a.app.SourceCodePath, a.app.Config, a.app.GitSource)
141+
config, err := a.resolvedConfig()
142+
if err != nil {
143+
return err
144+
}
145+
deployment := appdeploy.BuildDeployment(a.app.SourceCodePath, config, a.app.GitSource)
139146
return appdeploy.Deploy(ctx, w, a.app.Name, deployment)
140147
}
141148

149+
// resolvedConfig returns the app config with any ${resources.*} variable references
150+
// resolved against the current bundle state. This is needed because the app runtime
151+
// configuration (env vars, command) can reference other bundle resources whose
152+
// properties are known only after the initialization phase.
153+
func (a *appRunner) resolvedConfig() (*resources.AppConfig, error) {
154+
if a.app.Config == nil {
155+
return nil, nil
156+
}
157+
158+
root := a.bundle.Config.Value()
159+
160+
// Normalize the full config so that all typed fields are present, even those
161+
// not explicitly set. This allows looking up resource properties by path.
162+
normalized, _ := convert.Normalize(a.bundle.Config, root, convert.IncludeMissingFields)
163+
164+
// Get the app's config section as a dyn.Value to resolve references in it.
165+
// The key is of the form "apps.<name>", so the full path is "resources.apps.<name>.config".
166+
configPath := dyn.MustPathFromString("resources." + a.Key() + ".config")
167+
configV, err := dyn.GetByPath(root, configPath)
168+
if err != nil || !configV.IsValid() {
169+
return a.app.Config, nil
170+
}
171+
172+
resourcesPrefix := dyn.MustPathFromString("resources")
173+
174+
// Resolve ${resources.*} references in the app config against the full bundle config.
175+
// Other variable types (bundle.*, workspace.*, variables.*) are already resolved
176+
// during the initialization phase and are left in place if encountered here.
177+
resolved, err := dynvar.Resolve(configV, func(path dyn.Path) (dyn.Value, error) {
178+
if !path.HasPrefix(resourcesPrefix) {
179+
return dyn.InvalidValue, dynvar.ErrSkipResolution
180+
}
181+
return dyn.GetByPath(normalized, path)
182+
})
183+
if err != nil {
184+
return nil, err
185+
}
186+
187+
var config resources.AppConfig
188+
if err := convert.ToTyped(&config, resolved); err != nil {
189+
return nil, err
190+
}
191+
return &config, nil
192+
}
193+
142194
func (a *appRunner) Cancel(ctx context.Context) error {
143195
// We should cancel the app by stopping it.
144196
app := a.app

0 commit comments

Comments
 (0)