Skip to content

Add JSON output to bundle init reporting where the template materialized - #6161

Merged
pavloKozlov merged 9 commits into
mainfrom
bundle-init-report-bundle-roots
Aug 5, 2026
Merged

Add JSON output to bundle init reporting where the template materialized#6161
pavloKozlov merged 9 commits into
mainfrom
bundle-init-report-bundle-roots

Conversation

@pavloKozlov

@pavloKozlov pavloKozlov commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

bundle init gives callers no machine-readable indication of what it wrote, so a caller that passes --output-dir has to assume the output is a single directory named after the project. That holds for the built-in templates, which output {{.project_name}}/, but not for a custom template whose output path is nested: with output path projects/{{.group}}/{{.project_name}}/ the bundle lands at <output-dir>/projects/alpha/yz/, and a caller looking for <output-dir>/yz/ misses it.

Related: #6142.

Adds JSON output to bundle init:

{
  "outputs": [
    "projects/alpha/yz/databricks.yml",
    "projects/alpha/yz/src/yz.py"
  ]
}

outputs are the files the template wrote, relative to the output directory, slash-separated and sorted. They come from the files persistToDisk actually persisted, so a file removed by a {{skip}} directive is not listed. Paths are relative rather than absolute and slash-separated rather than OS-specific, so the contract is identical for the local and workspace filers. A caller that needs the bundle root can select the entry whose base name is a bundle configuration file.

Field names are part of the JSON contract — new fields should be additive so consumers tolerate CLI versions that predate this change.

Compatibility. Default text output is unchanged: rendering is gated on flags.OutputJSON, and the success message is still printed by Materialize. Materialize's signature is untouched, so cmd/pipelines/init.go is unaffected.

Tests. The standard shape is covered by passing -o json in the existing default-minimal/python case. A new nested-output case covers a custom template whose output path is nested, asserting the outputs are under projects/alpha/yz and that no stray top-level yz/ directory is created. The goldens cover the sorting and the {{skip}} filtering — verified by removing each and confirming they fail.

…lized

`bundle init` gave callers no machine-readable indication of where it wrote
the template. The success message is the template's own rendered text, so a
caller that passes `--output-dir` could not learn which subdirectory under it
became the bundle root, and had to assume it is named after the project. That
assumption holds for the built-in templates, which output `{{.project_name}}/`,
but not for a custom template whose output path is nested.

Add an `InitResult` reported by `bundle init -o json`:

    {
      "template_name": "custom",
      "output_dir": "/path/to/output",
      "bundle_roots": ["projects/alpha/yz"]
    }

`bundle_roots` are the directories that received a bundle configuration file,
relative to the resolved output directory, slash-separated and sorted. They are
derived from the files `persistToDisk` actually persisted, so a configuration
file removed by a `{{skip}}` directive is not reported. Paths stay relative and
slash-based so the contract is identical for the local and workspace filers.

A template may emit more than one bundle configuration file, in which case each
root is an independent bundle, so this is a list; callers that expect exactly
one should handle len != 1 explicitly.

The default text output is unchanged: rendering is gated on JSON output, and
the success message is still printed by `Materialize`. `Configure` now resolves
the output directory with `filepath.Abs` and retains it, so a relative
`--output-dir` such as `..` can be joined with a bundle root by the caller.
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 8ef7a04

Run: 30994534509

Env 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 4 4 291 1096 8:43
💚​ aws windows 4 4 293 1094 6:46
💚​ azure linux 4 4 290 1096 8:49
💚​ azure windows 4 4 292 1094 6:32
💚​ gcp linux 1 5 291 1096 8:28
💚​ gcp windows 1 5 293 1094 7:58
8 interesting tests: 4 RECOVERED, 4 SKIP
Test Name aws linux aws windows azure linux azure windows gcp linux gcp windows
💚​ TestAccept 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo/root 💚​R 💚​R 💚​R 💚​R
💚​ TestFetchRepositoryInfoAPI_FromRepo/subdir 💚​R 💚​R 💚​R 💚​R
Top 9 slowest tests (at least 2 minutes):
duration env testname
7:48 gcp windows TestAccept
6:15 aws windows TestAccept
6:13 azure windows TestAccept
2:56 azure linux TestAccept
2:55 aws linux TestAccept
2:51 gcp linux TestFilerWorkspaceFilesExtensionsStat
2:46 azure windows TestFilerWorkspaceFilesExtensionsReadDir
2:45 gcp linux TestAccept
2:11 aws linux TestFilerWorkspaceFilesExtensionsReadDir

The two new acceptance tests compared `output_dir` verbatim, but it is an OS
path, so Windows rendered it backslash-separated and the golden files only
matched on Unix. Pipe the JSON through sed to normalize the separators, keeping
a single golden per case. `bundle_roots` are always slash-separated, so they are
unaffected.

Also drop the unit cases that the acceptance tests already cover end to end
(nested root, a root at the output directory, and the empty input). The
remaining rows cover what the acceptance tests cannot reach without a template
fixture each: the four configuration file names, ordering, de-duplication, and
files merely named like a configuration file.
`template_name` and `output_dir` echoed back values the caller already passed as
arguments, so they carried no information. Replace all three fields with a
single `outputs` array listing the files the template wrote, relative to the
output directory and sorted:

    { "outputs": ["projects/alpha/yz/databricks.yml", "projects/alpha/yz/src/yz.py"] }

A caller that needs the bundle root can select the entry whose base name is a
bundle configuration file, which also removes the need for this package to
duplicate the list of configuration file names.

Since the output no longer contains an OS path, `Configure` no longer has to
retain the resolved output directory, so `constructOutputFiler` keeps resolving
it as before, and the acceptance tests no longer need to normalize path
separators for Windows.
`InitResult` is new in this change and its only caller invokes it after a
successful `Materialize`, so the renderer is always set. The nil branch was
never reachable, and removing it also removes the test that existed only to
cover it; the acceptance tests pin the sorting and the {{skip}} filtering that
remain.
# after the project. This pins the contract for the standard shape.
#
# stderr is dropped so that the golden output only pins the JSON payload.
trace $CLI bundle init default-minimal --config-file ./input.json --output-dir output -o json 2>/dev/null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a bunch of template tests, should we just use this option on some of those tests and drop this one?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — dropped this case and passed -o json in the existing default-minimal/python test instead. The JSON is appended after the success message (the message goes to stderr, the payload to stdout), so that case still asserts everything it did before.

I kept nested-output, since no existing case uses a template whose output path is nested — that shape is the reason for this change, and the case also pins that the CLI creates no stray top-level directory. Happy to fold it into an existing case too if you would rather not have a new one.

Rather than add a test case that only differs by `-o json`, pass the flag in the
existing default-minimal case, which already initializes a built-in template and
validates the result. The JSON is appended to that test's output, so the case
keeps asserting everything it did before.
@pavloKozlov
pavloKozlov marked this pull request as ready for review August 5, 2026 10:51
@pavloKozlov
pavloKozlov merged commit 516413a into main Aug 5, 2026
23 of 24 checks passed
@pavloKozlov
pavloKozlov deleted the bundle-init-report-bundle-roots branch August 5, 2026 13:06
@janniklasrose

Copy link
Copy Markdown
Contributor

Windows runners are not picking up CI jobs. Linux ones passed 👍

deco-sdk-tagging Bot added a commit that referenced this pull request Aug 6, 2026
## Release v1.11.0

### CLI

 * Fixed `databricks repos get/update/delete` failing with `object at path "..." is not a repo` for Git-CLI-enabled folders (currently in preview), which the workspace API reports as directories rather than repos ([#6181](#6181)).
 * Support `dbfs:/Skills/...` paths in `databricks fs` commands, routed to the Files API. ([#6147](#6147))

### Bundles

 * For jobs where `ai_runtime_task.code_source_path` is a relative path to a local directory, the directory is now packaged into a tarball (honoring `.gitignore` and `sync.include`/`sync.exclude`), uploaded during deployment, and `code_source_path` is rewritten to the uploaded workspace path. ([#6110](#6110))
 * Added JSON output to `bundle init`. Running `databricks bundle init <template> -o json` now reports the files the template wrote, relative to the output directory. This lets callers that pass `--output-dir` learn where the template materialized instead of assuming the output is a single directory named after the project. The default text output is unchanged. ([#6161](#6161))
 * The terraform deployment engine is deprecated and will stop working in a future version of the CLI. Setting `bundle.engine: terraform` now emits a deprecation warning. See https://docs.databricks.com/aws/en/dev-tools/bundles/direct for how to migrate to the direct deployment engine. ([#6099](#6099))
 * Fixed the direct deployment engine planning a spurious `create` for an empty `grants: []` list. Terraform records no grants resource for such a list, so `bundle plan` after `bundle deployment migrate` no longer reports an action for it. Emptying a previously deployed list still revokes the grants, after which the node is dropped from the deployment state instead of being reported as unchanged forever. ([#6039](#6039))
 * Fixed `bundle generate` downloading notebooks found inside a folder without their file extension. They are now exported like top-level notebooks, so a Python notebook lands as `notebook.py` instead of an extensionless file ([#6144](#6144)).
 * direct: `webhook_notifications.on_*` destinations on jobs, tasks, and `for_each_task` are now compared as unordered sets. Previously the Jobs API returning these lists in a different order than submitted produced a phantom diff that `bundle plan` and `bundle deploy` could never converge past, reporting `1 to change` on every run ([#6060](#6060)).
 * Fixed a pipeline with `allow_duplicate_names: true` never converging on the direct engine: the field is only accepted on create/update and is never returned by the pipelines GET API, so every subsequent `bundle plan` reported the pipeline as a perpetual update. ([#6076](#6076))
 * direct: A local change to an input-only field (one the API accepts on write but never returns on read, e.g. pipelines' `run_as` or external locations' `skip_validation`) is no longer silently skipped when the new value coincidentally matches the field's fabricated remote value. Previously such a change could hit the `remote_already_set` shortcut and be dropped from the plan. ([#6112](#6112))
 * Revert usage of RedactiveSenstiveFields (added in [#5896](#5896), released in 1.10.0) which lead to incorrect behaviour (permanent drift) for duration field in Postgres resources ([#6179](#6179)).
 * Document postgres resource fields in the json schema ([#6164](#6164), [#6163](#6163)).
 * direct: Recreating a `vector_search_indexes` resource no longer fails with "Index ... is currently pending deletion" when the backend has not yet released the index name. The create is now retried until the name becomes available. ([#6143](#6143))

### Dependency Updates

 * Bump `github.com/databricks/databricks-sdk-go` from v0.165.0 to v0.166.0. ([#6175](#6175))
 * Upgrade Terraform provider to 1.124.0. ([#6174](#6174))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants