diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index deafebf5f2..fad1499ad6 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -5,12 +5,12 @@ ### CLI * Add `completion install`, `uninstall`, and `status` subcommands ([#4581](https://github.com/databricks/cli/pull/4581)) -### Internal +### Bundles +* Added support for git_source and git_repository for Apps ([#4538](https://github.com/databricks/cli/pull/4538)) ### Dependency updates * Upgrade TF provider to 1.109.0 ([#4561](https://github.com/databricks/cli/pull/4561)) * Upgrade Go SDK to v0.110.0 ([#4552](https://github.com/databricks/cli/pull/4552)) ### API Changes - -- Bump databricks-sdk-go from v0.111.0 to v0.112.0. +* Bump databricks-sdk-go from v0.111.0 to v0.112.0. diff --git a/acceptance/bundle/apps/git_source/app/app.py b/acceptance/bundle/apps/git_source/app/app.py new file mode 100644 index 0000000000..271596cec0 --- /dev/null +++ b/acceptance/bundle/apps/git_source/app/app.py @@ -0,0 +1 @@ +print("Simple test app") diff --git a/acceptance/bundle/apps/git_source/app/app.yml b/acceptance/bundle/apps/git_source/app/app.yml new file mode 100644 index 0000000000..45b242d406 --- /dev/null +++ b/acceptance/bundle/apps/git_source/app/app.yml @@ -0,0 +1 @@ +command: ["python", "app.py"] diff --git a/acceptance/bundle/apps/git_source/databricks.yml.tmpl b/acceptance/bundle/apps/git_source/databricks.yml.tmpl new file mode 100644 index 0000000000..d5901c0dab --- /dev/null +++ b/acceptance/bundle/apps/git_source/databricks.yml.tmpl @@ -0,0 +1,18 @@ +bundle: + name: app-git-source-$UNIQUE_NAME + +workspace: + root_path: "~/.bundle/app-git-source-$UNIQUE_NAME" + +resources: + apps: + my_app: + name: app-$UNIQUE_NAME + description: "App with git source" + git_repository: + url: https://github.com/databricks/cli + provider: gitHub + git_source: + branch: main + source_code_path: internal/testdata/simple-app + source_code_path: ./app diff --git a/acceptance/bundle/apps/git_source/out.test.toml b/acceptance/bundle/apps/git_source/out.test.toml new file mode 100644 index 0000000000..6feb8784c8 --- /dev/null +++ b/acceptance/bundle/apps/git_source/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = false +RequiresWarehouse = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/git_source/output.txt b/acceptance/bundle/apps/git_source/output.txt new file mode 100644 index 0000000000..0c9982661c --- /dev/null +++ b/acceptance/bundle/apps/git_source/output.txt @@ -0,0 +1,72 @@ + +=== Validate bundle configuration +>>> [CLI] bundle validate +Name: app-git-source-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/app-git-source-[UNIQUE_NAME] + +Validation OK! + +=== Plan bundle deployment +>>> [CLI] bundle plan +create apps.my_app + +Plan: 1 to add, 0 to change, 0 to delete, 0 unchanged + +=== Deploy bundle +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/app-git-source-[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Get app details and verify git_source configuration +>>> [CLI] bundle summary --output json + +>>> [CLI] apps get [APP_NAME] --output json +{ + "name": "[APP_NAME]", + "description": "App with git source", + "git_repository": { + "provider": "gitHub", + "url": "https://github.com/databricks/cli" + }, + "git_source": null +} + +=== Verify no drift after deployment +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged + +=== Run the app to verify it works +>>> cat out.app-run +✓ Getting the status of the app [APP_NAME] +✓ App is in RUNNING state +✓ App compute is in ACTIVE state +✓ Deployment succeeded +You can access the app at [APP_NAME]-123.cloud.databricksapps.com + +=== Update git_source branch and redeploy +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/app-git-source-[UNIQUE_NAME]/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Verify config update was applied +>>> [CLI] apps get [APP_NAME] --output json +{ + "name": "[APP_NAME]", + "git_source": null +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.apps.my_app + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/app-git-source-[UNIQUE_NAME] + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/apps/git_source/script b/acceptance/bundle/apps/git_source/script new file mode 100644 index 0000000000..5e87210ee4 --- /dev/null +++ b/acceptance/bundle/apps/git_source/script @@ -0,0 +1,40 @@ +#!/bin/bash + +# Generate databricks.yml from template +envsubst < databricks.yml.tmpl > databricks.yml + +# Set up cleanup trap to ensure resources are destroyed even on failure +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +title "Validate bundle configuration" +trace $CLI bundle validate + +title "Plan bundle deployment" +trace $CLI bundle plan + +title "Deploy bundle" +trace $CLI bundle deploy + +title "Get app details and verify git_source configuration" +app_name=$(trace $CLI bundle summary --output json | jq -r '.resources.apps.my_app.name') +echo "$app_name:APP_NAME" >> ACC_REPLS + +trace $CLI apps get $app_name --output json | jq '{name, description, git_repository, git_source}' + +title "Verify no drift after deployment" +trace $CLI bundle plan + +title "Run the app to verify it works" +$CLI bundle run my_app &> out.app-run || true +trace cat out.app-run | head -20 + +title "Update git_source branch and redeploy" +# Change branch from main to a different value (still main, but via sed to test config change) +sed -i.bak 's/branch: main/branch: main # updated/' databricks.yml +trace $CLI bundle deploy + +title "Verify config update was applied" +trace $CLI apps get $app_name --output json | jq '{name, git_source}' diff --git a/acceptance/bundle/apps/git_source/test.toml b/acceptance/bundle/apps/git_source/test.toml new file mode 100644 index 0000000000..ee68c7a08d --- /dev/null +++ b/acceptance/bundle/apps/git_source/test.toml @@ -0,0 +1,50 @@ +# Test that git_repository and git_source fields are properly configured for apps +Local = true +# Temporary disable cloud tests because it fails with "Git repository cannot be defined in this workspace. Please try again later." +Cloud = false +RecordRequests = false +RequiresWarehouse = true + +Ignore = [".databricks", "databricks.yml", "databricks.yml.bak", "out.app-run"] + +# Apps can take longer to deploy +TimeoutCloud = "5m" + +# Mock responses for app deployment +[[Server]] +Pattern = "POST /api/2.0/apps/{app_name}/deployments" +Response.Body = ''' +{ + "deployment_id": "test-deployment-123", + "status": { + "state": "SUCCEEDED", + "message": "Deployment succeeded" + }, + "git_source": { + "branch": "main", + "source_code_path": "internal/testdata/simple-app", + "resolved_commit": "abc123def456" + }, + "source_code_path": "/Workspace/Users/tester@databricks.com/.bundle/files/app", + "mode": "SNAPSHOT" +} +''' + +[[Server]] +Pattern = "GET /api/2.0/apps/{app_name}/deployments/{deployment_id}" +Response.Body = ''' +{ + "deployment_id": "test-deployment-123", + "status": { + "state": "SUCCEEDED", + "message": "Deployment succeeded" + }, + "git_source": { + "branch": "main", + "source_code_path": "internal/testdata/simple-app", + "resolved_commit": "abc123def456" + }, + "source_code_path": "/Workspace/Users/tester@databricks.com/.bundle/files/app", + "mode": "SNAPSHOT" +} +''' diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index 77dc2d859e..3ebaeaa628 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -119,6 +119,15 @@ resources.apps.*.effective_user_api_scopes[*] string ALL resources.apps.*.git_repository *apps.GitRepository ALL resources.apps.*.git_repository.provider string ALL resources.apps.*.git_repository.url string ALL +resources.apps.*.git_source *apps.GitSource INPUT +resources.apps.*.git_source.branch string INPUT +resources.apps.*.git_source.commit string INPUT +resources.apps.*.git_source.git_repository *apps.GitRepository INPUT +resources.apps.*.git_source.git_repository.provider string INPUT +resources.apps.*.git_source.git_repository.url string INPUT +resources.apps.*.git_source.resolved_commit string INPUT +resources.apps.*.git_source.source_code_path string INPUT +resources.apps.*.git_source.tag string INPUT resources.apps.*.id string ALL resources.apps.*.lifecycle resources.Lifecycle INPUT resources.apps.*.lifecycle.prevent_destroy bool INPUT diff --git a/bundle/config/resources/apps.go b/bundle/config/resources/apps.go index 7dca924342..18c48f10d3 100644 --- a/bundle/config/resources/apps.go +++ b/bundle/config/resources/apps.go @@ -35,6 +35,7 @@ type AppEnvVar struct { type App struct { BaseResource apps.App // nolint App struct also defines Id and URL field with the same json tag "id" and "url" + // Note: apps.App already includes GitRepository field from the SDK // SourceCodePath is a required field used by DABs to point to Databricks app source code // on local disk and to the corresponding workspace path during app deployment. @@ -45,6 +46,10 @@ type App struct { // This allows users to define app configuration directly in the bundle YAML instead of maintaining a separate app.yaml file. Config *AppConfig `json:"config,omitempty"` + // GitSource specifies the git reference (branch, tag, or commit) to use during deployment. + // This is used in conjunction with GitRepository (from apps.App) and is passed to the Deploy API. + GitSource *apps.GitSource `json:"git_source,omitempty"` + Permissions []AppPermission `json:"permissions,omitempty"` } diff --git a/bundle/direct/dresources/type_test.go b/bundle/direct/dresources/type_test.go index b3e1af6530..ade20e0ccd 100644 --- a/bundle/direct/dresources/type_test.go +++ b/bundle/direct/dresources/type_test.go @@ -93,6 +93,7 @@ var knownMissingInStateType = map[string][]string{ "apps": { "config", "source_code_path", + "git_source", }, "dashboards": { "file_path", diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index b0cb190dd8..137bb9bc57 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -522,6 +522,12 @@ github.com/databricks/cli/bundle/config/resources.AlertPermission: "user_name": "description": |- PLACEHOLDER +github.com/databricks/cli/bundle/config/resources.App: + "git_source": + "description": |- + Git source configuration for app deployments. Specifies which git reference (branch, tag, or commit) + to use when deploying the app. Used in conjunction with git_repository to deploy code directly from git. + The source_code_path within git_source specifies the relative path to the app code within the repository. github.com/databricks/cli/bundle/config/resources.AppConfig: "command": "description": |- diff --git a/bundle/internal/validation/generated/required_fields.go b/bundle/internal/validation/generated/required_fields.go index 80a03bc06c..ae59c9f046 100644 --- a/bundle/internal/validation/generated/required_fields.go +++ b/bundle/internal/validation/generated/required_fields.go @@ -22,6 +22,7 @@ var RequiredFields = map[string][]string{ "resources.apps.*.active_deployment.git_source.git_repository": {"provider", "url"}, "resources.apps.*.config.env[*]": {"name"}, "resources.apps.*.git_repository": {"provider", "url"}, + "resources.apps.*.git_source.git_repository": {"provider", "url"}, "resources.apps.*.pending_deployment.git_source.git_repository": {"provider", "url"}, "resources.apps.*.permissions[*]": {"level"}, "resources.apps.*.resources[*]": {"name"}, diff --git a/bundle/run/app.go b/bundle/run/app.go index 3c3b0d3bf6..1dd4484093 100644 --- a/bundle/run/app.go +++ b/bundle/run/app.go @@ -166,6 +166,11 @@ func (a *appRunner) buildAppDeployment() apps.AppDeployment { SourceCodePath: a.app.SourceCodePath, } + // Add git source if provided + if a.app.GitSource != nil { + deployment.GitSource = a.app.GitSource + } + // Add inline config if provided if a.app.Config != nil { if len(a.app.Config.Command) > 0 { diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 13e31833ff..6cff9a7f60 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -191,6 +191,10 @@ "x-databricks-preview": "PRIVATE", "doNotSuggest": true }, + "git_source": { + "description": "Git source configuration for app deployments. Specifies which git reference (branch, tag, or commit)\nto use when deploying the app. Used in conjunction with git_repository to deploy code directly from git.\nThe source_code_path within git_source specifies the relative path to the app code within the repository.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource" + }, "lifecycle": { "description": "Lifecycle is a struct that contains the lifecycle settings for a resource. It controls the behavior of the resource when it is deployed or destroyed.", "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" diff --git a/libs/structs/structwalk/walktype_test.go b/libs/structs/structwalk/walktype_test.go index 5d64222bcc..dd452e8d3e 100644 --- a/libs/structs/structwalk/walktype_test.go +++ b/libs/structs/structwalk/walktype_test.go @@ -136,7 +136,7 @@ func TestTypeJobSettings(t *testing.T) { func TestTypeRoot(t *testing.T) { testStruct(t, reflect.TypeOf(config.Root{}), - 4300, 4800, // 4754 after adding external locations support + 4600, 5000, // 4814 after adding external locations support map[string]any{ "bundle.target": "", `variables.*.lookup.dashboard`: "",