diff --git a/.nextchanges/bundles/generate-app-git.md b/.nextchanges/bundles/generate-app-git.md new file mode 100644 index 00000000000..160c8ae5bde --- /dev/null +++ b/.nextchanges/bundles/generate-app-git.md @@ -0,0 +1 @@ +* `databricks bundle generate app` now reproduces a git-backed app's `git_repository` and `git_source` configuration instead of emitting a workspace `source_code_path`, so generating from a Git-deployed app no longer silently converts it to workspace source. diff --git a/acceptance/bundle/generate/app_git_backed/databricks.yml b/acceptance/bundle/generate/app_git_backed/databricks.yml new file mode 100644 index 00000000000..33f02e64d95 --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: app_git_backed diff --git a/acceptance/bundle/generate/app_git_backed/out.app.yml b/acceptance/bundle/generate/app_git_backed/out.app.yml new file mode 100644 index 00000000000..a8ddc85733b --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/out.app.yml @@ -0,0 +1,12 @@ +resources: + apps: + out: + name: my_git_app + description: This is a git-backed app + git_repository: + url: https://github.com/my-org/my-repo + provider: gitHub + auto_deploy: true + git_source: + branch: main + source_code_path: apps/my-app diff --git a/acceptance/bundle/generate/app_git_backed/out.test.toml b/acceptance/bundle/generate/app_git_backed/out.test.toml new file mode 100644 index 00000000000..e1af1a235ad --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/out.test.toml @@ -0,0 +1,3 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/generate/app_git_backed/output.txt b/acceptance/bundle/generate/app_git_backed/output.txt new file mode 100644 index 00000000000..8b097941315 --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/output.txt @@ -0,0 +1,10 @@ +Loading app 'my_git_app' configuration +App configuration successfully saved to out.app.yml +Warning: Generated configuration is not included in the bundle + +The file out.app.yml is not matched by any pattern in the 'include' section of databricks.yml, +so it will not be deployed. Add a matching entry to the 'include' section, for example: + +include: + - *.yml + diff --git a/acceptance/bundle/generate/app_git_backed/script b/acceptance/bundle/generate/app_git_backed/script new file mode 100644 index 00000000000..8f39e55343f --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/script @@ -0,0 +1 @@ +$CLI bundle generate app --existing-app-name my_git_app --config-dir . --key out diff --git a/acceptance/bundle/generate/app_git_backed/test.toml b/acceptance/bundle/generate/app_git_backed/test.toml new file mode 100644 index 00000000000..096f61774f7 --- /dev/null +++ b/acceptance/bundle/generate/app_git_backed/test.toml @@ -0,0 +1,18 @@ +[[Server]] +Pattern = "GET /api/2.0/apps/my_git_app" +Response.Body = ''' +{ + "app_id": "1234567890", + "name": "my_git_app", + "description": "This is a git-backed app", + "git_repository": { + "url": "https://github.com/my-org/my-repo", + "provider": "gitHub", + "auto_deploy": true + }, + "git_source": { + "branch": "main", + "source_code_path": "apps/my-app" + } +} +''' diff --git a/bundle/generate/app.go b/bundle/generate/app.go index b4a37d8d6de..234967ce072 100644 --- a/bundle/generate/app.go +++ b/bundle/generate/app.go @@ -15,9 +15,21 @@ func ConvertAppToValue(app *apps.App, sourceCodePath string) (dyn.Value, error) // The majority of fields of the app struct are read-only. // We copy the relevant fields manually. dv := map[string]dyn.Value{ - "name": dyn.NewValue(app.Name, []dyn.Location{{Line: 1}}), - "description": dyn.NewValue(app.Description, []dyn.Location{{Line: 2}}), - "source_code_path": dyn.NewValue(sourceCodePath, []dyn.Location{{Line: 3}}), + "name": dyn.NewValue(app.Name, []dyn.Location{{Line: 1}}), + "description": dyn.NewValue(app.Description, []dyn.Location{{Line: 2}}), + } + + // For a git-backed app, emit git_repository + git_source instead of a + // workspace source_code_path. Otherwise the generated bundle would silently + // down-convert the app to workspace source and point source_code_path at a + // local directory that has nothing downloaded into it. + if app.GitRepository != nil { + dv["git_repository"] = gitRepositoryValue(app.GitRepository) + if gs := gitSourceValue(app); gs.Kind() != dyn.KindNil { + dv["git_source"] = gs + } + } else { + dv["source_code_path"] = dyn.NewValue(sourceCodePath, []dyn.Location{{Line: 4}}) } if ar.Kind() != dyn.KindNil { @@ -26,3 +38,46 @@ func ConvertAppToValue(app *apps.App, sourceCodePath string) (dyn.Value, error) return dyn.V(dv), nil } + +func gitRepositoryValue(r *apps.GitRepository) dyn.Value { + m := map[string]dyn.Value{ + "url": dyn.NewValue(r.Url, []dyn.Location{{Line: 1}}), + "provider": dyn.NewValue(r.Provider, []dyn.Location{{Line: 2}}), + } + if r.AutoDeploy { + m["auto_deploy"] = dyn.NewValue(r.AutoDeploy, []dyn.Location{{Line: 3}}) + } + return dyn.NewValue(m, []dyn.Location{{Line: 3}}) +} + +// gitSourceValue returns the reference the app deploys from (branch, tag, or +// commit, plus an optional repo-relative source_code_path). It prefers the +// configured git_source and falls back to the default source of the app's most +// recent deployment. System-populated fields (resolved_commit and the nested +// git_repository) are intentionally omitted. +func gitSourceValue(app *apps.App) dyn.Value { + src := app.GitSource + if src == nil { + src = app.DefaultGitSource + } + if src == nil { + return dyn.NilValue + } + + m := map[string]dyn.Value{} + switch { + case src.Branch != "": + m["branch"] = dyn.NewValue(src.Branch, []dyn.Location{{Line: 1}}) + case src.Tag != "": + m["tag"] = dyn.NewValue(src.Tag, []dyn.Location{{Line: 1}}) + case src.Commit != "": + m["commit"] = dyn.NewValue(src.Commit, []dyn.Location{{Line: 1}}) + } + if src.SourceCodePath != "" { + m["source_code_path"] = dyn.NewValue(src.SourceCodePath, []dyn.Location{{Line: 2}}) + } + if len(m) == 0 { + return dyn.NilValue + } + return dyn.NewValue(m, []dyn.Location{{Line: 4}}) +} diff --git a/cmd/bundle/generate/app.go b/cmd/bundle/generate/app.go index 845853adbdf..bec005408cd 100644 --- a/cmd/bundle/generate/app.go +++ b/cmd/bundle/generate/app.go @@ -81,8 +81,10 @@ per target environment.`, sourceCodePath := app.DefaultSourceCodePath // If the source code path is not set, we don't need to download anything. - // This is the case for apps that are not yet deployed. - if sourceCodePath != "" { + // This is the case for apps that are not yet deployed. A git-backed app + // keeps its source in Git rather than the workspace, so there is nothing + // to download and ConvertAppToValue emits git_repository/git_source instead. + if app.GitRepository == nil && sourceCodePath != "" { err = downloader.MarkDirectoryForDownload(ctx, &sourceCodePath) if err != nil { return err