diff --git a/.agent/rules/auto-generated-files.md b/.agent/rules/auto-generated-files.md index 6393a640751..458e3041c6c 100644 --- a/.agent/rules/auto-generated-files.md +++ b/.agent/rules/auto-generated-files.md @@ -50,49 +50,54 @@ paths: ## Identification -The files matching this rule glob pattern are most likely generated artifacts. Auto-generated files generally have a comment (if the file type allows for comments) at or near the top of the file indicating that they are generated, or their file name/path may indicate they are generated. You may also consult Makefile as starting point to determine if a file is auto-generated. +Files matching this rule's glob pattern are most likely generated artifacts. Auto-generated files generally have a comment (when the file type allows it) at or near the top indicating they are generated, or their name or path signals it. You may also consult the Makefile as a starting point to determine if a file is auto-generated. ## Rules -DO NOT "MANUALLY" EDIT THESE FILES! +**RULE: Do not manually edit auto-generated files.** -If a change is needed in any matched file: -1. Find the source logic/template/annotation that drives the file. -2. Run the appropriate generator/update command. -3. Commit both the source change (if any) and regenerated outputs. +**RULE: To change a generated file, edit the source and regenerate.** + +1. Find the source logic, template, or annotation that drives the file. +2. Run the appropriate generator or update command. +3. Commit both the source change (if any) and the regenerated outputs. ### Core generation commands +- Everything, in one shot: + - `./task generate` — aggregator that runs all generators below - OpenAPI SDK/CLI command stubs and related generated artifacts: - - `make generate` - - Includes generated `cmd/account/**`, `cmd/workspace/**`, `.gitattributes`, `internal/genkit/tagging.py`, and direct engine refresh. + - `./task generate-genkit` + - Includes generated `cmd/account/**`, `cmd/workspace/**`, `.gitattributes`, `internal/genkit/tagging.py`. - Direct engine generated YAML: - - `make generate-direct` (or `make generate-direct-apitypes`, `make generate-direct-resources`) + - `./task generate-direct` (or `./task generate-direct-apitypes`, `./task generate-direct-resources`) - Bundle schemas: - - `make schema` - - `make schema-for-docs` + - `./task generate-schema` + - `./task generate-schema-docs` - This can also refresh `bundle/internal/schema/annotations_openapi.yml` when OpenAPI annotation extraction is enabled. - Bundle docs: - - `make docs` + - `./task generate-docs` - Validation generated code: - - `make generate-validation` + - `./task generate-validation` - Mock files: - `go run github.com/vektra/mockery/v2@b9df18e0f7b94f0bc11af3f379c8a9aea1e1e8da` - Python bundle codegen: - - `make -C python codegen` + - `./task pydabs-codegen` ### Acceptance and test generated outputs -Acceptance outputs are generated and should not be hand-edited (except rare, intentional mass replacement when explicitly justified by repo guidance). +**RULE: Do not hand-edit acceptance outputs.** Exception: rare, intentional mass replacement when explicitly justified by repo guidance. + +Regeneration commands: + +- `./task test-update` +- `./task test-update-templates` (templates only) + +Typical generated files: -- Preferred regeneration: - - `make test-update` - - `make test-update-templates` (templates only) - - `make generate-out-test-toml` (only `out.test.toml`) -- Typical generated files include: - - `acceptance/**/out*` - - `acceptance/**/output.txt` - - `acceptance/**/output.*.txt` - - `acceptance/**/output/**` (materialized template output trees) +- `acceptance/**/out*` +- `acceptance/**/output.txt` +- `acceptance/**/output.*.txt` +- `acceptance/**/output/**` (materialized template output trees) -When touching acceptance sources (`databricks.yml`, scripts, templates, or test config), regenerate outputs instead of editing generated files directly. +**RULE: When touching acceptance sources, regenerate outputs instead of editing generated files.** Sources include `databricks.yml`, scripts, templates, and test config. diff --git a/.agent/rules/dresources.md b/.agent/rules/dresources.md new file mode 100644 index 00000000000..351b19d172a --- /dev/null +++ b/.agent/rules/dresources.md @@ -0,0 +1,8 @@ +--- +description: Rules for implementing resources in bundle/direct/dresources +globs: bundle/direct/dresources/**/*.go +paths: + - "bundle/direct/dresources/**/*.go" +--- + +**RULE: Before implementing or modifying a resource, read `bundle/direct/dresources/README.md`.** It covers constraints, field classification, update mask rules, async patterns, testing requirements, and state compatibility. diff --git a/.agent/rules/style-guide-go.md b/.agent/rules/style-guide-go.md index 3a121490018..f7e360b174e 100644 --- a/.agent/rules/style-guide-go.md +++ b/.agent/rules/style-guide-go.md @@ -9,11 +9,9 @@ paths: ## General guidance -Please make sure code that you author is consistent with the codebase and concise. +Code you author should be consistent with the codebase and concise. The code should be self-documenting based on its function and variable names. -The code should be self-documenting based on the code and function names. - -Functions should be documented with a doc comment as follows: +**RULE: Document functions with a doc comment that starts with the function name and ends with a period.** ```go // SomeFunc does something. @@ -22,21 +20,181 @@ func SomeFunc() { } ``` -Note how the comment starts with the name of the function and is followed by a period. +**RULE: Avoid redundant and verbose comments.** Only add a comment if it complements the code rather than repeating it. + +**RULE: Focus on making implementations as small and elegant as possible.** Avoid unnecessary loops and allocations. If dropping or relaxing a requirement would simplify things, ask the user about the trade-off. + +### Modern Go (1.24+) idioms + +**RULE: Use `for i := range X` for integer iteration, not `for i := 0; i < X; i++`.** + +**RULE: Use builtin `min()` and `max()` where possible.** They work on any type and any number of values. + +**RULE: Do not capture the for-range variable.** Since Go 1.22 a new copy is created each iteration, so the capture workaround is no longer needed. + +**RULE: Use empty struct types for context keys.** -Avoid redundant and verbose comments. Use terse comments and only add comments if it complements, not repeats the code. +GOOD: -Focus on making implementation as small and elegant as possible. Avoid unnecessary loops and allocations. If you see an opportunity of making things simpler by dropping or relaxing some requirements, ask user about the trade-off. +```go +type myKeyType struct{} +``` + +BAD: + +```go +type myKeyType int +``` -Use modern idiomatic Golang features (version 1.24+). Specifically: - - Use for-range for integer iteration where possible. Instead of for i:=0; i < X; i++ {} you must write for i := range X{}. - - Use builtin min() and max() where possible (works on any type and any number of values). - - Do not capture the for-range variable, since go 1.22 a new copy of the variable is created for each loop iteration. - - Use empty struct types for context keys: `type myKeyType struct{}` (not `int`). - - Define magic strings as named constants at the top of the file. - - When integrating external tools or detecting environment variables, include source reference URLs as comments so they can be traced later. +**RULE: Define magic strings as named constants at the top of the file.** When a value is used in more than one place, define a package-level constant even if the literal is short. If a related value already has a constant in the same package, follow the existing pattern instead of introducing a parallel literal. + +**RULE: When integrating external tools or detecting environment variables, include source reference URLs as comments.** This lets future readers trace where the behavior came from. + +### Control flow + +**RULE: When several branches are alternatives for the same decision, prefer `switch` over `if / else if`.** Same-decision means: you're dispatching on one value or one boolean question and each branch is a different answer to it. Ordered precedence chains (try this, else this, else this) are a different shape; leave those as early-return `if`s. + +GOOD (alternatives for one decision): + +```go +switch mode { +case modeText: + renderText(out, result) +case modeJSON: + renderJSON(out, result) +default: + return fmt.Errorf("unknown mode %q", mode) +} +``` + +Also fine (ordered precedence, early-return style): + +```go +// see libs/auth/storage/mode.go#ResolveStorageMode +if override != "" { + return override, nil +} +if envValue != "" { + return parseMode(envValue) +} +return loadFromFile() +``` + +**RULE: Collapse `if err != nil { return err }; return nil` to just `return err`.** The pattern is never doing anything useful in the intermediate step. + +GOOD: + +```go +return someCall() +``` + +BAD: + +```go +if err := someCall(); err != nil { + return err +} +return nil +``` + +### Determinism + +**RULE: When you build a slice by iterating over a map and its order affects tests, logs, update masks, or the wire format, sort it before returning.** Go maps have randomized iteration order, so the same input produces different outputs across runs. Reviewers catch these when they produce flaky test output or noisy diffs in update masks. If the slice is purely internal and nothing downstream observes its order, sorting is unnecessary — some accepted code in `bundle/direct/dresources/` and `bundle/config/mutator/resourcemutator/` returns unsorted slices precisely because order doesn't matter there. + +GOOD: + +```go +fieldPaths := make([]string, 0, len(changes)) +for p := range changes { + fieldPaths = append(fieldPaths, p) +} +slices.Sort(fieldPaths) +return fieldPaths +``` + +BAD: + +```go +fieldPaths := make([]string, 0, len(changes)) +for p := range changes { + fieldPaths = append(fieldPaths, p) +} +return fieldPaths +``` -### Configuration Patterns +### Environment variables + +**RULE: In library and product code, use `github.com/databricks/cli/libs/env` for reading environment variables, not `os.Getenv`.** `env.Get(ctx, name)` and `env.Lookup(ctx, name)` can be overridden per-context in tests, so you don't have to mutate process-wide state to exercise a code path. `os.Getenv` is still fine in `main`, tests, and acceptance/integration harnesses where no `ctx` is available and overrides aren't needed. + +GOOD: + +```go +import "github.com/databricks/cli/libs/env" + +token := env.Get(ctx, "DATABRICKS_TOKEN") +if path, ok := env.Lookup(ctx, "DATABRICKS_CLI_PATH"); ok { + // use path +} +``` + +BAD: + +```go +token := os.Getenv("DATABRICKS_TOKEN") +``` + +### Lazy initialization + +**RULE: Use `sync.OnceValue`, `sync.OnceValues`, or `sync.OnceFunc` for one-time initialization, not `sync.Once` with package variables.** The `sync.OnceValue[T]` family (Go 1.21+) removes the boilerplate and makes the cached result a first-class return value. Use `sync.OnceFunc` when the initialization has side effects and no return value (the CLI already uses it in places like `libs/cmdio/spinner.go`). + +GOOD: + +```go +var loadConfig = sync.OnceValues(func() (*Config, error) { + return parseConfigFile("config.yml") +}) + +func GetConfig() (*Config, error) { + return loadConfig() +} +``` + +BAD: + +```go +var ( + config *Config + configErr error + configOnce sync.Once +) + +func GetConfig() (*Config, error) { + configOnce.Do(func() { + config, configErr = parseConfigFile("config.yml") + }) + return config, configErr +} +``` + +Caveats that apply to both: results are cached forever (including errors), and a panic is rethrown on every subsequent call. Create a new instance if you need retry semantics. + +### Constructors + +When a constructor's parameter list is long enough that callers forget the order or misread positional arguments, consider grouping dependencies into a struct. This is a judgment call. The CLI has many ordinary constructors with 4+ parameters, and that's fine when the arguments are obvious at the call site. The signal for switching is readability, not parameter count. + +```go +type ServiceDeps struct { + Client HTTPClient + Logger Logger + Config Config + Validator Validator + Metrics MetricsCollector +} + +func NewService(deps ServiceDeps) *Service { ... } +``` + +### Configuration patterns - Bundle config uses `dyn.Value` for dynamic typing - Config loading supports includes, variable interpolation, and target overrides @@ -44,11 +202,15 @@ Use modern idiomatic Golang features (version 1.24+). Specifically: ## Context -Always pass `context.Context` as a function argument; never store it in a struct. Storing context in a struct obscures the lifecycle and prevents callers from setting per-call deadlines, cancellation, and metadata (see https://go.dev/blog/context-and-structs). Do not use `context.Background()` outside of `main.go` files. In tests, use `t.Context()` (or `b.Context()` for benchmarks). +**RULE: Always pass `context.Context` as a function argument; never store it in a struct.** Storing context in a struct obscures the lifecycle and prevents callers from setting per-call deadlines, cancellation, and metadata. See https://go.dev/blog/context-and-structs. + +**RULE: Do not use `context.Background()` outside of `main.go` files.** + +**RULE: In tests, use `t.Context()` (or `b.Context()` for benchmarks).** ## Logging -Use the following for logging: +**RULE: Use `github.com/databricks/cli/libs/log` for debug/info/warn/error logging.** The `ctx` variable must be passed in by the caller. ```go import "github.com/databricks/cli/libs/log" @@ -59,10 +221,7 @@ log.Warnf(ctx, "...") log.Errorf(ctx, "...") ``` -Note that the 'ctx' variable here is something that should be passed in as -an argument by the caller. - -Use cmdio.LogString to print to stdout: +**RULE: Use `cmdio.LogString` to print to stdout.** ```go import "github.com/databricks/cli/libs/cmdio" @@ -70,4 +229,26 @@ import "github.com/databricks/cli/libs/cmdio" cmdio.LogString(ctx, "...") ``` -Always output file path with forward slashes, even on Windows, so that acceptance test output is stable between OSes. Use filepath.ToSlash for this. +**RULE: Always output file paths with forward slashes, even on Windows.** Use `filepath.ToSlash` so acceptance test output is stable between OSes. + +**RULE: Pick log levels deliberately.** Warn for things the user should know about and might act on. Debug for diagnostic signal a developer wants but a user shouldn't see by default. Error for actual failures that also surface as a returned error. A message that's warn-by-default but isn't user-actionable belongs at debug. + +GOOD: + +```go +if err := w.Config.Authenticate(); err != nil { + // user can check their profile; worth warning + log.Warnf(ctx, "could not authenticate: %v", err) +} + +if err := cleanupExpiredCacheEntries(ctx); err != nil { + // internal-only, user can't act on this + log.Debugf(ctx, "cache cleanup failed: %v", err) +} +``` + +BAD: + +```go +log.Warnf(ctx, "cache cleanup failed: %v", err) // noisy, not actionable +``` diff --git a/.agent/rules/style-guide-py.md b/.agent/rules/style-guide-py.md index 1edf94c60bc..280af2181b8 100644 --- a/.agent/rules/style-guide-py.md +++ b/.agent/rules/style-guide-py.md @@ -9,11 +9,18 @@ paths: ## General guidance -When writing Python scripts, we bias for conciseness. We think of Python in this code base as scripts. -- use Python 3.11 -- Do not catch exceptions to make nicer messages, only catch if you can add critical information -- use pathlib.Path in almost all cases over os.path unless it makes code longer -- Do not add redundant comments. -- Try to keep your code small and the number of abstractions low. -- After done, format your code with `ruff format -n ` -- Use `#!/usr/bin/env python3` shebang. +Python in this codebase is written as scripts. Bias for conciseness. + +**RULE: Use Python 3.11.** + +**RULE: Use `#!/usr/bin/env python3` as the shebang.** + +**RULE: Prefer `pathlib.Path` over `os.path`.** Exception: when it would make the code longer. + +**RULE: Do not catch exceptions just to add a nicer message.** Only catch if you can add critical information the caller can't produce. + +**RULE: Avoid redundant comments.** + +**RULE: Keep code small and minimize abstractions.** + +**RULE: Format with `ruff format -n ` before committing.** diff --git a/.agent/rules/testing.md b/.agent/rules/testing.md index 3b7ff37ed68..67f41cd2ea2 100644 --- a/.agent/rules/testing.md +++ b/.agent/rules/testing.md @@ -10,11 +10,22 @@ description: Rules for the testing strategy of this repo - **Integration tests**: `integration/` directory, requires live Databricks workspace - **Acceptance tests**: `acceptance/` directory, uses mock HTTP server -Each file like process_target_mode_test.go should have a corresponding test file -like process_target_mode_test.go. If you add new functionality to a file, -the test file should be extended to cover the new functionality. +## Choosing a test level + +**RULE: For user-visible CLI output and changes to the bundle mutator pipeline, reach for acceptance tests first.** They exercise the full pipeline and capture the exact output the user sees. `cmd/...` commands and anything under the mutator pipeline (`bundle/config/mutator/...` and `bundle/mutator/...`) are the strongest candidates. When the coverage overlaps with an existing test, extend the existing acceptance directory instead of creating a new one. + +**Unit tests are still the right tool** for pure functions, utility code, parsing/formatting helpers, and anything you can meaningfully test without mocking the whole world. Don't force a unit into an acceptance test just because the code lives under `cmd/`, and don't add a mutator unit test that only duplicates what an acceptance test already covers. + +When in doubt: would the test fail in a useful way if a mutator earlier in the pipeline changed? If yes, the test wants to be an acceptance test. + +## Unit tests + +**RULE: Each source file should have a corresponding test file.** If you add new functionality to a file, extend the test file to cover it. + +**RULE: Place tests in the same package but with a `_test` suffix.** Test names start with `Test` and describe the function or module under test. + +**RULE: Use `require` for preconditions that would make the rest of the test meaningless on failure.** Use `assert` for expected values where the test can keep running after a failure. -Tests should look like the following: ```go package mutator_test @@ -31,49 +42,102 @@ func TestApplySomeChangeFixesThings(t *testing.T) { } ``` -Notice that: -- Tests are often in the same package but suffixed with _test. -- The test names are prefixed with Test and are named after the function or module they are testing. -- 'require' and 'require.NoError' are used to check for things that would cause the rest of the test case to fail. -- 'assert' is used to check for expected values where the rest of the test is not expected to fail. +**RULE: Use table-driven tests for multiple similar cases.** Reviewers prefer this pattern over repeating near-identical test functions when the inputs differ but the logic is the same. -When writing tests, please don't include an explanation in each -test case in your responses. I am just interested in the tests. +**RULE: If a value is shared across tests and they must change together, extract it to a package-level `const` or `var`.** Think shared fixtures, identifiers that must stay in sync across tests, and expected error messages that the tests verify as a set. Repeated literals that happen to be the same (e.g. a header name like `"Authorization"` appearing inline in many tests) are fine to leave inline; forcing extraction there hurts readability without buying anything. -Use table-driven tests when testing multiple similar cases (e.g., different inputs producing different outputs). Reviewers prefer this pattern over repeating near-identical test functions. +When writing tests, don't include an explanation in each test case in your responses. Only the tests are needed. ## Acceptance Tests -- Located in `acceptance/` with nested directory structure. +**RULE: Never edit generated acceptance output files directly.** Files named `output.txt`, `out.test.toml`, `out.requests.txt`, or anything starting with `out` are regenerated. Use the `-update` flag to regenerate them. + +Exception: mass string replacement when the change is predictable and much cheaper than re-running the test suite. + +**RULE: All `EnvMatrix` variants MUST produce identical output files.** Filenames containing `$DATABRICKS_BUNDLE_ENGINE` (e.g. `output.direct.txt`) are the only per-engine exception. + +**RULE: Do not run `-update` while a divergent variant exists.** It is destructive: it overwrites with the last variant and breaks the others. To debug: run a single variant you consider correct with `-update`, then debug the other variant to find why it diverges. + +**RULE: Put common `test.toml` options in a parent directory.** Config is inherited from parents. + +**RULE: Add test artifacts (e.g. `.databricks`) to `Ignore` in `test.toml`.** + +**RULE: Commit static test inputs into the acceptance test directory; do not create them in `script` at test time.** If a file's content is dynamic, generating it in `script` is fine. For everything else, check it in and let the test read it directly; you won't need an `Ignore` entry because there's nothing to clean up. + +GOOD: + +``` +acceptance/cmd/fs/cp/file-to-dir/ + script # $CLI fs cp local.txt dbfs:/path/ + test.toml + local.txt # committed input + output.txt +``` + +BAD: + +``` +acceptance/cmd/fs/cp/file-to-dir/ + script # echo "contents" > local.txt; $CLI fs cp local.txt dbfs:/path/; rm local.txt + test.toml # Ignore = ["local.txt"] + output.txt +``` + +**RULE: When output genuinely diverges between engines (terraform vs direct), split only the diverging file into per-engine variants.** Keep the rest of the output unified. Files named `output.$DATABRICKS_BUNDLE_ENGINE.txt` or `out.requests.$DATABRICKS_BUNDLE_ENGINE.json` are the allowed per-engine form. + +If the only reason for divergence is a server-side default that one engine sets and the other doesn't, set the field explicitly in `databricks.yml` so both engines produce identical output. Don't paper over it with per-engine files. + +### Reference + +- Tests live in `acceptance/` with a nested directory structure. - Each test directory contains `databricks.yml`, `script`, and `output.txt`. - Source files: `test.toml`, `script`, `script.prepare`, `databricks.yml`, etc. -- Tests are configured via `test.toml`. Config schema and explanation is in `acceptance/internal/config.go`. Config is inherited from parent directories. Certain options are also dumped to `out.test.toml` so that inherited values are visible on PRs. -- Generated output files start with `out`: `output.txt`, `out.test.toml`, `out.requests.txt`. Never edit these directly — use `-update` to regenerate. Exception: mass string replacement when the change is predictable and much cheaper than re-running the test suite. +- Tests are configured via `test.toml`. Config schema and explanation is in `acceptance/internal/config.go`. Certain options are also dumped to `out.test.toml` so that inherited values are visible on PRs. - Run a single test: `go test ./acceptance -run TestAccept/bundle///` -- Run a specific variant by appending EnvMatrix values to the test name: `go test ./acceptance -run 'TestAccept/.../DATABRICKS_BUNDLE_ENGINE=direct'`. When there are multiple EnvMatrix variables, they appear in alphabetical order. +- Run a specific variant by appending `EnvMatrix` values to the test name: `go test ./acceptance -run 'TestAccept/.../DATABRICKS_BUNDLE_ENGINE=direct'`. When there are multiple `EnvMatrix` variables, they appear in alphabetical order. - Useful flags: `-v` for verbose output, `-tail` to follow test output (requires `-v`), `-logrequests` to log all HTTP requests/responses (requires `-v`). - Run tests on cloud: `deco env run -i -n aws-prod-ucws -- ` (requires `deco` tool and access to test env). -- Use `-update` flag to regenerate expected output files. When a test fails because of stale output, re-run with `-update` instead of editing output files. -- All EnvMatrix variants share the same output files — they MUST produce identical output. Exception: filenames containing `$DATABRICKS_BUNDLE_ENGINE` (e.g. `output.direct.txt`) are recorded per-engine. -- `-update` with divergent variant outputs is destructive: overwrites with last variant, breaking others. To debug: run a single variant you consider correct with `-update`, then debug the other variant to find why it diverges. -- `test.toml` is inherited — put common options into a parent directory. -- Add test artifacts (e.g. `.databricks`) to `Ignore` in `test.toml`. -- `script.prepare` files from parent directories are concatenated into the test script — use them for shared bash helpers. - -**Helper scripts** in `acceptance/bin/` are available on `PATH` during test execution: -- `contains.py SUBSTR [!SUBSTR_NOT]` — passthrough filter (stdin→stdout) that checks substrings are present (or absent with `!` prefix). Errors are reported on stderr. -- `print_requests.py //path [^//exclude] [--get] [--sort] [--keep]` — print recorded HTTP requests matching path filters. Requires `RecordRequests=true` in `test.toml`. Clears `out.requests.txt` afterwards unless `--keep`. Use `--get` to include GET requests (excluded by default). Use `^` prefix to exclude paths. -- `replace_ids.py [-t TARGET]` — read deployment state and add `[NAME_ID]` replacements for all resource IDs. -- `read_id.py [-t TARGET] NAME` — read ID of a single resource from state, print it, and add a `[NAME_ID]` replacement. -- `add_repl.py VALUE REPLACEMENT` — add a custom replacement (VALUE will be replaced with `[REPLACEMENT]` in output). -- `update_file.py FILENAME OLD NEW` — replace all occurrences of OLD with NEW in FILENAME. Errors if OLD is not found. Cannot be used on `output.txt`. -- `find.py REGEX [--expect N]` — find files matching regex in current directory. `--expect N` to assert exact count. -- `diff.py DIR1 DIR2` or `diff.py FILE1 FILE2` — recursive diff with test replacements applied. -- `print_state.py [-t TARGET] [--backup]` — print deployment state (terraform or direct). -- `edit_resource.py TYPE ID < script.py` — fetch resource by ID, execute Python on it (resource in `r`), then update it. TYPE is `jobs` or `pipelines`. -- `gron.py` — flatten JSON into greppable discrete assignments (simpler than `jq` for searching JSON). +- `script.prepare` files from parent directories are concatenated into the test script. Use them for shared bash helpers. + +### Helper scripts + +**RULE: Use the `acceptance/bin/` helpers before reaching for inline `jq` or `grep` pipelines.** When a test needs to filter recorded requests, assert a substring is or isn't present, or register a dynamic replacement, the helpers handle sorting, URL query normalization, redaction hooks, and cross-platform path issues. Inline `jq` in an acceptance script is brittle and hard to read. + +GOOD: + +```bash +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to delete, 1 to update" +trace print_requests.py //api/2.0/apps +echo "$deployment_id:DEPLOYMENT_ID" >> ACC_REPLS +``` + +BAD: + +```bash +{ trace jq 'select(.method == "POST" and .path == "/api/2.0/apps")' out.requests.txt; } || true +``` + +Available on `PATH` during test execution (from `acceptance/bin/`): + +- `contains.py SUBSTR [!SUBSTR_NOT]`: passthrough filter (stdin→stdout) that checks substrings are present (or absent with `!` prefix). Errors are reported on stderr. +- `print_requests.py //path [^//exclude] [--get] [--sort] [--keep]`: print recorded HTTP requests matching path filters. Requires `RecordRequests=true` in `test.toml`. Clears `out.requests.txt` afterwards unless `--keep`. Use `--get` to include GET requests (excluded by default). Use `^` prefix to exclude paths. +- `replace_ids.py [-t TARGET]`: read deployment state and add `[NAME_ID]` replacements for all resource IDs. +- `read_id.py [-t TARGET] NAME`: read ID of a single resource from state, print it, and add a `[NAME_ID]` replacement. +- `add_repl.py VALUE REPLACEMENT`: add a custom replacement (VALUE will be replaced with `[REPLACEMENT]` in output). +- `update_file.py FILENAME OLD NEW`: replace all occurrences of OLD with NEW in FILENAME. Errors if OLD is not found. Cannot be used on `output.txt`. +- `find.py REGEX [--expect N]`: find files matching regex in current directory. `--expect N` asserts an exact count. +- `diff.py DIR1 DIR2` or `diff.py FILE1 FILE2`: recursive diff with test replacements applied. +- `print_state.py [-t TARGET] [--backup]`: print deployment state (terraform or direct). +- `edit_resource.py TYPE ID < script.py`: fetch resource by ID, execute Python on it (resource in `r`), then update it. TYPE is `jobs` or `pipelines`. +- `gron.py`: flatten JSON into greppable discrete assignments (simpler than `jq` for searching JSON). - `jq` is also available for JSON processing. -**Update workflow**: Run `make test-update` to regenerate outputs. Then run `make fmt` and `make lint` — if these modify files in `acceptance/`, there's an issue in source files. Fix the source, regenerate, and verify lint/fmt pass cleanly. +### Update workflow + +**RULE: Run `./task test-update` to regenerate outputs, then `./task fmt` and `./task lint`.** If fmt or lint modify files in `acceptance/`, there's an issue in the source files. Fix the source, regenerate, and verify fmt/lint pass cleanly. + +### Template tests + +Tests in `acceptance/bundle/templates` include materialized templates in output directories. These directories follow the same `out` convention: everything starting with `out` is generated output. Sources are in `libs/template/templates/`. -**Template tests**: Tests in `acceptance/bundle/templates` include materialized templates in output directories. These directories follow the same `out` convention — everything starting with `out` is generated output. Sources are in `libs/template/templates/`. Use `make test-update-templates` to regenerate. If linters or formatters find issues in materialized templates, do not fix the output files — fix the source in `libs/template/templates/`, then regenerate. +**RULE: Use `./task test-update-templates` to regenerate materialized templates.** If linters or formatters find issues in materialized templates, do not fix the output files; fix the source in `libs/template/templates/` and regenerate. diff --git a/.agent/skills/pr-checklist/SKILL.md b/.agent/skills/pr-checklist/SKILL.md index ae21c13ca04..3eae258d6e7 100644 --- a/.agent/skills/pr-checklist/SKILL.md +++ b/.agent/skills/pr-checklist/SKILL.md @@ -3,26 +3,70 @@ name: pr-checklist description: Checklist to run before submitting a PR --- -Before submitting a PR, run these commands to match what CI checks. CI uses the **full** variants (not the diff-only wrappers), so `make lint` alone is insufficient. +Before submitting a PR, run these commands to match what CI checks. CI uses the full variants (not the `-q` diff-only wrappers), so `./task lint-q` alone is insufficient. ```bash -# 1. Formatting and checks (CI runs fmtfull, not fmt) -make fmtfull -make checks +# 1. Formatting and checks (CI runs fmt, not fmt-q) +./task fmt +./task checks -# 2. Linting (CI runs full golangci-lint, not the diff-only wrapper) -make lintfull +# 2. Linting (CI runs full golangci-lint across all modules, not the diff-only wrapper) +./task lint # 3. Tests (CI runs with both deployment engines) -make test +./task test # 4. If you changed bundle config structs or schema-related code: -make schema +./task generate-schema # 5. If you changed files in python/: -cd python && make codegen && make test && make lint && make docs +./task pydabs-codegen pydabs-test pydabs-lint pydabs-docs # 6. If you changed experimental/aitools or experimental/ssh: -make test-exp-aitools # only if aitools code changed -make test-exp-ssh # only if ssh code changed +./task test-exp-aitools # only if aitools code changed +./task test-exp-ssh # only if ssh code changed ``` + +## Final cleanup scan + +After the commands above pass, scrub the diff before pushing. The quick version: run `git diff @{u}` and read through what you added. Specifically: + +- **Debug prints**: look for newly added `fmt.Print`, `fmt.Printf`, `fmt.Println`, `log.Print`, `log.Printf`, `log.Println`, or bare `println(...)` calls. A regex that scans only added lines against your upstream branch: + + ```bash + git diff @{u} -- '*.go' | rg '^\+.*\b(fmt|log)\.(Print|Printf|Println)\b|^\+.*\bprintln\(' + ``` + + If you have no upstream yet, substitute the intended base (e.g. `origin/main`) for `@{u}`. +- **Commented-out code**: delete it. If it's needed for reference, it lives in git history. +- **TODOs without a ticket**: either add a ticket reference (e.g. `// TODO(DECO-1234): ...`) or remove the TODO. Un-tracked TODOs rot. +- **Unintended files**: review `git status` and `git diff --stat` to confirm only the files you meant to change are staged. + +## PR description + +Follow `.github/PULL_REQUEST_TEMPLATE.md` exactly. Use its section headings (`## Changes`, `## Why`, `## Tests`) in the same order, and fill each one in. Do not invent new sections (`## Summary`, `## Test plan`, etc.), do not drop sections, and do not leave the HTML comment placeholders in the final body — replace them with real content. If a section genuinely does not apply (e.g. a docs-only change has no test steps), say so explicitly under that heading rather than removing it. + +When using `gh pr create`, read `.github/PULL_REQUEST_TEMPLATE.md` first and base `--body` on it. + +If an agent (you) authored or substantially helped author the PR, disclose it on the last line of the body, e.g. `_This PR was written by Claude Code._` or `_PR description drafted with Claude Code._`. Be honest about the level of involvement — "written by" vs. "drafted with" vs. "reviewed by" — and keep it to a single italicized line so it doesn't crowd the template sections. + +## Changelog entry + +Add a `NEXT_CHANGELOG.md` entry when your change is user-visible. CI generates the real `CHANGELOG.md` from `NEXT_CHANGELOG.md` at release time, so never hand-edit `CHANGELOG.md` directly. + +**When to add an entry:** +- New or changed CLI command, flag, or subcommand behavior +- New or changed bundle config field, schema, or engine behavior +- New direct dependency (annotate under `Dependency updates`) +- Bug fix that users will notice + +**When to skip:** +- Experimental commands (under `experimental/`): no entry until the feature graduates out of experimental +- Pure refactors, internal renames, test-only changes, and doc-only changes +- Auto-generated output changes without a corresponding user-facing change + +**How to add:** +- Pick the right section (`CLI`, `Bundles`, `Dependency updates`) under the current `## Release vX.Y.Z` header. +- One or two sentences, user-facing language, no Jira links. +- Reference the PR number once it's open: after `gh pr create`, edit the entry to append ` (#NNNN)` or similar matching nearby entries. +- Match the voice and tense of the existing entries in the file. diff --git a/.claude/settings.json b/.claude/settings.json index 330a8d606b9..140e91e97f3 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,18 +1,7 @@ { "permissions": { "allow": [ - "Bash(make lint:*)", - "Bash(make lintfull:*)", - "Bash(make fmt:*)", - "Bash(make test:*)", - "Bash(make checks:*)", - "Bash(make build:*)", - "Bash(make cover:*)", - "Bash(make schema:*)", - "Bash(make docs:*)", - "Bash(make test-update:*)", - "Bash(make test-update-templates:*)", - "Bash(make ws:*)", + "Bash(./task *)", "Bash(go test:*)", "Bash(go build:*)", "Bash(go vet:*)", diff --git a/.codegen.json b/.codegen.json index 175688cecc1..e2a84cb8c14 100644 --- a/.codegen.json +++ b/.codegen.json @@ -7,15 +7,11 @@ "python/databricks/bundles/version.py": "__version__ = \"$VERSION\"", "python/pyproject.toml": "version = \"$VERSION\"", "python/uv.lock": "name = \"databricks-bundles\"\nversion = \"$VERSION\"", - "libs/template/templates/experimental-jobs-as-code/library/versions.tmpl": "{{define \"latest_databricks_bundles_version\" -}}$VERSION{{- end}}", "libs/template/templates/default/library/versions.tmpl": "{{define \"latest_databricks_bundles_version\" -}}$VERSION{{- end}}" }, "toolchain": { "required": [ "go" - ], - "post_generate": [ - "./tools/post-generate.sh" ] } } diff --git a/.codegen/_openapi_sha b/.codegen/_openapi_sha index e5a90379123..15378345074 100644 --- a/.codegen/_openapi_sha +++ b/.codegen/_openapi_sha @@ -1 +1 @@ -d09dbd77f5a9560cbb816746773da43a8bdbde08 \ No newline at end of file +11ae6f9d98f0d0838a5e53c27032f178fecc4ee0 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 47d09387cc6..ffe19eb43c4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,3 @@ -NEXT_CHANGELOG.md merge=union - # Generated by genkit update-sdk: cmd/account/access-control/access-control.go linguist-generated=true cmd/account/billable-usage/billable-usage.go linguist-generated=true diff --git a/.gitattributes.manual b/.gitattributes.manual index 4d8da53399d..fce33166e80 100644 --- a/.gitattributes.manual +++ b/.gitattributes.manual @@ -1,3 +1 @@ -NEXT_CHANGELOG.md merge=union - # Generated by genkit update-sdk: diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index d1618a2deeb..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,11 +0,0 @@ -* @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum -/cmd/bundle/bundle.go @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum @lennartkats-db -/libs/template/ @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum @lennartkats-db -/acceptance/pipelines/ @jefferycheng1 @kanterov @lennartkats-db -/cmd/pipelines/ @jefferycheng1 @kanterov @lennartkats-db -/cmd/labs/ @alexott @nfx -/cmd/apps/ @databricks/eng-apps-devex -/cmd/workspace/apps/ @databricks/eng-apps-devex -/libs/apps/ @databricks/eng-apps-devex -/acceptance/apps/ @databricks/eng-apps-devex -/experimental/aitools/ @databricks/eng-apps-devex @lennartkats-db diff --git a/.github/OWNERS b/.github/OWNERS index 6ac08fddc4b..7cae525465a 100644 --- a/.github/OWNERS +++ b/.github/OWNERS @@ -1,25 +1,63 @@ # Maintainers (can approve any PR) -* @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum +* @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum @renaudhartert-db # Bundles -/bundle/ @andrewnester @anton-107 @denik @pietern @shreyas-goenka @lennartkats-db -/cmd/bundle/ @andrewnester @anton-107 @denik @pietern @shreyas-goenka @lennartkats-db -/acceptance/bundle/ @andrewnester @anton-107 @denik @pietern @shreyas-goenka @lennartkats-db -/libs/template/ @andrewnester @anton-107 @denik @pietern @shreyas-goenka @simonfaltum @lennartkats-db +/bundle/ team:bundle @lennartkats-db +/cmd/bundle/ team:bundle @lennartkats-db +/acceptance/bundle/ team:bundle @lennartkats-db +/libs/template/ team:bundle @lennartkats-db # Pipelines /cmd/pipelines/ @jefferycheng1 @kanterov @lennartkats-db /acceptance/pipelines/ @jefferycheng1 @kanterov @lennartkats-db # Labs -/cmd/labs/ @alexott @nfx -/acceptance/labs/ @alexott @nfx +/cmd/labs/ @alexott @asnare +/acceptance/labs/ @alexott @asnare # Apps -/cmd/apps/ @databricks/eng-apps-devex -/cmd/workspace/apps/ @databricks/eng-apps-devex -/libs/apps/ @databricks/eng-apps-devex -/acceptance/apps/ @databricks/eng-apps-devex +/cmd/apps/ team:eng-apps-devex +/cmd/workspace/apps/ team:eng-apps-devex +/libs/apps/ team:eng-apps-devex +/acceptance/apps/ team:eng-apps-devex + +# Auth +/cmd/auth/ team:platform +/libs/auth/ team:platform +/acceptance/auth/ team:platform + +# Filesystem & sync +/cmd/fs/ team:platform +/cmd/sync/ team:platform +/libs/filer/ team:platform +/libs/sync/ team:platform + +# Core CLI infrastructure +/cmd/root/ team:platform +/cmd/version/ team:platform +/cmd/completion/ team:platform +/cmd/configure/ team:platform +/cmd/cache/ team:platform +/cmd/api/ team:platform +/cmd/selftest/ team:platform +/cmd/psql/ team:platform +/libs/psql/ team:platform + +# Libs (general) +/libs/databrickscfg/ team:platform +/libs/env/ team:platform +/libs/flags/ team:platform +/libs/cmdio/ team:platform +/libs/log/ team:platform +/libs/telemetry/ team:platform +/libs/process/ team:platform +/libs/git/ team:platform + +# Integration tests +/integration/ team:platform + +# Internal +/internal/ team:platform # Experimental -/experimental/aitools/ @databricks/eng-apps-devex @lennartkats-db +/experimental/aitools/ team:eng-apps-devex @lennartkats-db diff --git a/.github/OWNERTEAMS b/.github/OWNERTEAMS new file mode 100644 index 00000000000..9bcab6116e9 --- /dev/null +++ b/.github/OWNERTEAMS @@ -0,0 +1,16 @@ +# Team aliases for OWNERS file. +# Use "team:" in OWNERS to reference a team defined here. +# Format: team: @member1 @member2 ... +# +# Keep these in sync with actual GitHub team rosters. GITHUB_TOKEN can't +# resolve org team membership via the API, so this file is the source of +# truth for the maintainer-approval workflow. +# +# GitHub team pages: +# bundle: https://github.com/orgs/databricks/teams/cli-maintainers +# platform: https://github.com/orgs/databricks/teams/cli-platform +# eng-apps-devex: https://github.com/orgs/databricks/teams/eng-apps-devex + +team:bundle @andrewnester @anton-107 @denik @janniklasrose @pietern @shreyas-goenka +team:platform @simonfaltum @renaudhartert-db @hectorcast-db @parthban-db @tanmay-db @Divyansh-db @tejaskochar-db @mihaimitrea-db @chrisst @rauchy +team:eng-apps-devex @fjakobs @jamesbroadhead @Shridhad @atilafassina @keugenek @arsenyinfo @igrekun @pkosiec @MarioCadenas @pffigueiredo @ditadi @calvarjorge diff --git a/.github/actions/setup-build-environment/action.yml b/.github/actions/setup-build-environment/action.yml index 60f42b1d8e8..3fd492c70f6 100644 --- a/.github/actions/setup-build-environment/action.yml +++ b/.github/actions/setup-build-environment/action.yml @@ -20,7 +20,7 @@ runs: shell: bash - name: Setup Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache-dependency-path: | @@ -33,16 +33,16 @@ runs: python-version: '3.13' - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.8.9" - name: Install Python versions for tests - run: make install-pythons + run: go tool -modfile=tools/task/go.mod task install-pythons shell: bash - name: Install ruff (Python linter and formatter) - uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3.6.1 + uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0 with: version: "0.9.1" args: "--version" diff --git a/.github/scripts/owners.js b/.github/scripts/owners.js index 03ac253a5ef..a158fa4af5a 100644 --- a/.github/scripts/owners.js +++ b/.github/scripts/owners.js @@ -1,9 +1,56 @@ const fs = require("fs"); +const path = require("path"); + +/** + * Read a file and return non-empty, non-comment lines split by whitespace. + * Returns [] if the file does not exist. + * + * @param {string} filePath + * @returns {string[][]} array of whitespace-split tokens per line + */ +function readDataLines(filePath) { + let content; + try { + content = fs.readFileSync(filePath, "utf-8"); + } catch (e) { + if (e.code === "ENOENT") return []; + throw e; + } + const result = []; + for (const raw of content.split("\n")) { + const line = raw.trim(); + if (!line || line.startsWith("#")) continue; + const parts = line.split(/\s+/); + if (parts.length >= 2) result.push(parts); + } + return result; +} + +/** + * Parse an OWNERTEAMS file into a map of team aliases. + * Format: "team: @member1 @member2 ..." + * Returns Map where key is "team:" and value is member logins. + * + * @param {string} filePath - absolute path to the OWNERTEAMS file + * @returns {Map} + */ +function parseOwnerTeams(filePath) { + const teams = new Map(); + for (const parts of readDataLines(filePath)) { + if (!parts[0].startsWith("team:")) continue; + const members = parts.slice(1).filter((p) => p.startsWith("@")).map((p) => p.slice(1)); + teams.set(parts[0], members); + } + return teams; +} /** * Parse an OWNERS file (same format as CODEOWNERS). * Returns array of { pattern, owners } rules. * + * If an OWNERTEAMS file exists alongside the OWNERS file, "team:" + * tokens are expanded to their member lists. + * * By default, team refs (org/team) are filtered out and @ is stripped. * Pass { includeTeams: true } to keep team refs (with @ stripped). * @@ -13,18 +60,19 @@ const fs = require("fs"); */ function parseOwnersFile(filePath, opts) { const includeTeams = opts && opts.includeTeams; - const lines = fs.readFileSync(filePath, "utf-8").split("\n"); + const teamsPath = path.join(path.dirname(filePath), "OWNERTEAMS"); + const teams = parseOwnerTeams(teamsPath); const rules = []; - for (const raw of lines) { - const line = raw.trim(); - if (!line || line.startsWith("#")) continue; - const parts = line.split(/\s+/); - if (parts.length < 2) continue; + for (const parts of readDataLines(filePath)) { const pattern = parts[0]; - const owners = parts - .slice(1) - .filter((p) => p.startsWith("@") && (includeTeams || !p.includes("/"))) - .map((p) => p.slice(1)); + const owners = []; + for (const p of parts.slice(1)) { + if (p.startsWith("team:") && teams.has(p)) { + owners.push(...teams.get(p)); + } else if (p.startsWith("@") && (includeTeams || !p.includes("/"))) { + owners.push(p.slice(1)); + } + } rules.push({ pattern, owners }); } return rules; @@ -89,4 +137,4 @@ function getOwnershipGroups(filenames, rules) { return groups; } -module.exports = { parseOwnersFile, ownersMatch, findOwners, getMaintainers, getOwnershipGroups }; +module.exports = { parseOwnerTeams, parseOwnersFile, ownersMatch, findOwners, getMaintainers, getOwnershipGroups }; diff --git a/.github/scripts/owners.test.js b/.github/scripts/owners.test.js index 65ca79bba4e..5594d297505 100644 --- a/.github/scripts/owners.test.js +++ b/.github/scripts/owners.test.js @@ -5,6 +5,7 @@ const os = require("os"); const path = require("path"); const { + parseOwnerTeams, ownersMatch, parseOwnersFile, findOwners, @@ -125,6 +126,99 @@ describe("parseOwnersFile", () => { }); }); +// --- parseOwnerTeams --- + +describe("parseOwnerTeams", () => { + let tmpDir; + + before(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ownerteams-test-")); + }); + + after(() => { + fs.rmSync(tmpDir, { recursive: true }); + }); + + it("parses team definitions", () => { + const teamsPath = path.join(tmpDir, "OWNERTEAMS"); + fs.writeFileSync(teamsPath, "team:platform @alice @bob @carol\n"); + const teams = parseOwnerTeams(teamsPath); + assert.equal(teams.size, 1); + assert.deepEqual(teams.get("team:platform"), ["alice", "bob", "carol"]); + }); + + it("parses multiple teams", () => { + const teamsPath = path.join(tmpDir, "OWNERTEAMS"); + fs.writeFileSync(teamsPath, "team:platform @alice @bob\nteam:bundle @carol @dave\n"); + const teams = parseOwnerTeams(teamsPath); + assert.equal(teams.size, 2); + assert.deepEqual(teams.get("team:platform"), ["alice", "bob"]); + assert.deepEqual(teams.get("team:bundle"), ["carol", "dave"]); + }); + + it("skips comments and blank lines", () => { + const teamsPath = path.join(tmpDir, "OWNERTEAMS"); + fs.writeFileSync(teamsPath, "# comment\n\nteam:platform @alice\n"); + const teams = parseOwnerTeams(teamsPath); + assert.equal(teams.size, 1); + }); + + it("returns empty map if file does not exist", () => { + const teams = parseOwnerTeams(path.join(tmpDir, "NONEXISTENT")); + assert.equal(teams.size, 0); + }); +}); + +// --- parseOwnersFile with team aliases --- + +describe("parseOwnersFile with OWNERTEAMS", () => { + let tmpDir; + let ownersPath; + let teamsPath; + + before(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "owners-teams-test-")); + ownersPath = path.join(tmpDir, "OWNERS"); + teamsPath = path.join(tmpDir, "OWNERTEAMS"); + }); + + after(() => { + fs.rmSync(tmpDir, { recursive: true }); + }); + + it("expands team aliases to members", () => { + fs.writeFileSync(teamsPath, "team:platform @alice @bob\n"); + fs.writeFileSync(ownersPath, "/cmd/auth/ team:platform\n"); + const rules = parseOwnersFile(ownersPath); + assert.equal(rules.length, 1); + assert.deepEqual(rules[0].owners, ["alice", "bob"]); + }); + + it("mixes team aliases with individual owners", () => { + fs.writeFileSync(teamsPath, "team:platform @alice @bob\n"); + fs.writeFileSync(ownersPath, "/cmd/auth/ team:platform @carol\n"); + const rules = parseOwnersFile(ownersPath); + assert.equal(rules.length, 1); + assert.deepEqual(rules[0].owners, ["alice", "bob", "carol"]); + }); + + it("unknown team alias is ignored", () => { + fs.writeFileSync(teamsPath, "team:platform @alice\n"); + fs.writeFileSync(ownersPath, "/cmd/auth/ team:unknown @bob\n"); + const rules = parseOwnersFile(ownersPath); + assert.deepEqual(rules[0].owners, ["bob"]); + }); + + it("works without OWNERTEAMS file", () => { + const tmpDir2 = fs.mkdtempSync(path.join(os.tmpdir(), "owners-noteams-")); + const ownersPath2 = path.join(tmpDir2, "OWNERS"); + fs.writeFileSync(ownersPath2, "* @alice\n"); + const rules = parseOwnersFile(ownersPath2); + assert.deepEqual(rules[0].owners, ["alice"]); + fs.rmSync(tmpDir2, { recursive: true }); + }); +}); + // --- findOwners --- describe("findOwners", () => { diff --git a/.github/workflows/bump-go-toolchain.yml b/.github/workflows/bump-go-toolchain.yml new file mode 100644 index 00000000000..080a15e5f9f --- /dev/null +++ b/.github/workflows/bump-go-toolchain.yml @@ -0,0 +1,106 @@ +name: Bump Go toolchain + +on: + schedule: + # Run daily at 05:00 UTC. + - cron: "0 5 * * *" + workflow_dispatch: + inputs: + version: + description: > + Go toolchain version to use (e.g. "go1.25.9"). + If empty, the latest patch release is detected automatically. + required: false + +permissions: + contents: write + pull-requests: write + +jobs: + bump-go-toolchain: + runs-on: + group: databricks-protected-runner-group-large + labels: linux-ubuntu-latest-large + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Determine current toolchain version + id: current + run: | + toolchain=$(grep '^toolchain' go.mod | awk '{print $2}') + minor=$(echo "$toolchain" | sed 's/^go//' | cut -d. -f1,2) + echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" + echo "minor=$minor" >> "$GITHUB_OUTPUT" + + - name: Determine latest patch release + id: latest + env: + INPUT_VERSION: ${{ inputs.version }} + run: | + if [ -n "$INPUT_VERSION" ]; then + if ! echo "$INPUT_VERSION" | grep -qE '^go[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Invalid version format: $INPUT_VERSION" + exit 1 + fi + toolchain="$INPUT_VERSION" + else + minor=${{ steps.current.outputs.minor }} + toolchain=$( + curl -fsSL 'https://go.dev/dl/?mode=json' | + jq -r --arg minor "go${minor}." '[.[] | select(.version | startswith($minor))][0].version // empty' + ) + if [ -z "$toolchain" ]; then + echo "No release found for go${minor}.x" + exit 1 + fi + fi + echo "toolchain=$toolchain" >> "$GITHUB_OUTPUT" + + - name: Check if update is needed + id: check + run: | + if [ "${{ steps.current.outputs.toolchain }}" = "${{ steps.latest.outputs.toolchain }}" ]; then + echo "Up to date: ${{ steps.current.outputs.toolchain }}" + echo "needed=false" >> "$GITHUB_OUTPUT" + else + echo "Update available: ${{ steps.current.outputs.toolchain }} -> ${{ steps.latest.outputs.toolchain }}" + echo "needed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Setup Go + if: steps.check.outputs.needed == 'true' + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + + - name: Update go.mod files + if: steps.check.outputs.needed == 'true' + env: + TOOLCHAIN: ${{ steps.latest.outputs.toolchain }} + run: | + while IFS= read -r modfile; do + dir=$(dirname "$modfile") + if grep -q '^toolchain' "$modfile"; then + (cd "$dir" && go mod edit -toolchain="$TOOLCHAIN") + fi + done < <(git ls-files '**/go.mod' 'go.mod') + + - name: Show diff + if: steps.check.outputs.needed == 'true' + run: git diff + + - name: Create pull request + if: steps.check.outputs.needed == 'true' && inputs.version == '' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + branch: auto/bump-go-toolchain + commit-message: "Bump Go toolchain to ${{ steps.latest.outputs.toolchain }}" + title: "Bump Go toolchain to ${{ steps.latest.outputs.toolchain }}" + body: | + Bump Go toolchain from `${{ steps.current.outputs.toolchain }}` to `${{ steps.latest.outputs.toolchain }}`. + + Release notes: https://go.dev/doc/devel/release#${{ steps.latest.outputs.toolchain }} + reviewers: simonfaltum,andrewnester,anton-107,denik,janniklasrose,pietern,shreyas-goenka + labels: dependencies diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bc82a2529f6..5bff4cf9935 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod # Use different schema from regular job, to avoid overwriting the same key @@ -36,22 +36,26 @@ jobs: run: git diff --exit-code - name: Run Go lint checks (does not include formatting checks) - run: go tool -modfile=tools/go.mod golangci-lint run --timeout=15m + run: go tool -modfile=tools/task/go.mod task lint - name: Run ruff (Python linter and formatter) - uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3.6.1 + uses: astral-sh/ruff-action@0ce1b0bf8b818ef400413f810f8a11cdbda0034b # v4.0.0 with: version: "0.9.1" args: "format --check" - - name: "make fmtfull: Python and Go formatting" + - name: Install uv + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 + with: + version: "0.8.9" + + - name: "task fmt: Python and Go formatting" # Python formatting is already checked above, but this also checks Go and YAML formatting - # and verifies that the make command works correctly run: | - make fmtfull + go tool -modfile=tools/task/go.mod task fmt git diff --exit-code - - name: "make checks: custom checks outside of fmt and lint" + - name: "task checks: custom checks outside of fmt and lint" run: |- - make checks + go tool -modfile=tools/task/go.mod task checks git diff --exit-code diff --git a/.github/workflows/maintainer-approval.js b/.github/workflows/maintainer-approval.js index 7cf7cb4c468..38371f8cdcb 100644 --- a/.github/workflows/maintainer-approval.js +++ b/.github/workflows/maintainer-approval.js @@ -96,7 +96,7 @@ async function checkPerPathApproval(files, rulesWithTeams, approverLogins, githu // --- Git history & scoring helpers --- -const MENTION_REVIEWERS = true; +const MENTION_REVIEWERS = false; const OWNERS_LINK = "[OWNERS](.github/OWNERS)"; const MARKER = ""; const STATUS_CONTEXT = "maintainer-approval"; @@ -203,9 +203,8 @@ function topDirs(ds, n = 3) { } function fmtReviewer(login, dirs) { - const mention = MENTION_REVIEWERS ? `@${login}` : login; const dirList = dirs.map((d) => `\`${d}/\``).join(", "); - return `- ${mention} -- recent work in ${dirList}`; + return `- ${fmtLogin(login)} -- recent work in ${dirList}`; } function selectReviewers(ss) { @@ -221,8 +220,12 @@ function selectReviewers(ss) { } function fmtEligible(owners) { - if (MENTION_REVIEWERS) return owners.map((o) => `@${o}`).join(", "); - return owners.join(", "); + return owners.map((o) => fmtLogin(o)).join(", "); +} + +function fmtLogin(login) { + if (MENTION_REVIEWERS) return `@${login}`; + return `\`@${login}\``; } async function countRecentReviews(github, owner, repo, logins, days = 30) { @@ -258,6 +261,13 @@ async function selectRoundRobin(github, owner, repo, eligibleOwners, prAuthor) { // --- Comment builders --- +function fmtFileList(files) { + if (files.length < 4) { + return `Files: ${files.map(f => `\`${f}\``).join(", ")}`; + } + return `${files.length} files changed`; +} + function buildPendingPerGroupComment(groups, scores, dirScores, approvedBy, maintainers, prAuthor) { const authorLower = (prAuthor || "").toLowerCase(); const lines = [MARKER, "## Approval status: pending", ""]; @@ -267,23 +277,23 @@ function buildPendingPerGroupComment(groups, scores, dirScores, approvedBy, main const approver = approvedBy.get(pattern); if (approver) { - lines.push(`### \`${pattern}\` - approved by @${approver}`); + lines.push(`### \`${pattern}\` - approved by ${fmtLogin(approver)}`); } else { lines.push(`### \`${pattern}\` - needs approval`); } - lines.push(`Files: ${files.map(f => `\`${f}\``).join(", ")}`); + lines.push(fmtFileList(files)); const teams = owners.filter(o => o.includes("/")); const individuals = owners.filter(o => !o.includes("/") && o.toLowerCase() !== authorLower); if (teams.length > 0) { - lines.push(`Teams: ${teams.map(t => `@${t}`).join(", ")}`); + lines.push(`Teams: ${teams.map(t => fmtLogin(t)).join(", ")}`); } if (!approver && individuals.length > 0) { const scored = individuals.map(o => [o, scores[o] || 0]).sort((a, b) => b[1] - a[1]); if (scored[0][1] > 0) { - lines.push(`Suggested: @${scored[0][0]}`); + lines.push(`Suggested: ${fmtLogin(scored[0][0])}`); const rest = scored.slice(1).map(([o]) => o); if (rest.length > 0) { lines.push(`Also eligible: ${fmtEligible(rest)}`); @@ -298,7 +308,7 @@ function buildPendingPerGroupComment(groups, scores, dirScores, approvedBy, main const starGroup = groups.get("*"); if (starGroup) { lines.push("### General files (require maintainer)"); - lines.push(`Files: ${starGroup.files.map(f => `\`${f}\``).join(", ")}`); + lines.push(fmtFileList(starGroup.files)); const maintainerSet = new Set(maintainers.map(m => m.toLowerCase())); const maintainerScores = Object.entries(scores) @@ -320,7 +330,7 @@ function buildPendingPerGroupComment(groups, scores, dirScores, approvedBy, main const maintainerList = maintainers .filter(m => m.toLowerCase() !== authorLower) - .map(m => `@${m}`) + .map(m => fmtLogin(m)) .join(", "); lines.push( @@ -349,7 +359,7 @@ function buildSingleDomainPendingComment(sortedScores, dirScores, scoredCount, e } else if (roundRobinReviewer) { lines.push( "Could not determine reviewers from git history.", - `Round-robin suggestion: @${roundRobinReviewer}`, + `Round-robin suggestion: ${fmtLogin(roundRobinReviewer)}`, "" ); } @@ -443,11 +453,11 @@ module.exports = async ({ github, context, core }) => { const prNumber = context.issue.number; const authorLogin = pr?.user?.login; const sha = pr.head.sha; - const statusParams = { + const checkParams = { owner: context.repo.owner, repo: context.repo.repo, - sha, - context: STATUS_CONTEXT, + head_sha: sha, + name: STATUS_CONTEXT, }; const reviews = await github.paginate(github.rest.pulls.listReviews, { @@ -464,10 +474,11 @@ module.exports = async ({ github, context, core }) => { if (maintainerApproval) { const approver = maintainerApproval.user.login; core.info(`Maintainer approval from @${approver}`); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "success", - description: `Approved by @${approver}`, + await github.rest.checks.create({ + ...checkParams, + status: "completed", + conclusion: "success", + output: { title: STATUS_CONTEXT, summary: `Approved by @${approver}` }, }); await deleteMarkerComments(github, owner, repo, prNumber); return; @@ -481,10 +492,11 @@ module.exports = async ({ github, context, core }) => { ); if (hasAnyApproval) { core.info(`Maintainer-authored PR approved by a reviewer.`); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "success", - description: "Approved (maintainer-authored PR)", + await github.rest.checks.create({ + ...checkParams, + status: "completed", + conclusion: "success", + output: { title: STATUS_CONTEXT, summary: "Approved (maintainer-authored PR)" }, }); await deleteMarkerComments(github, owner, repo, prNumber); return; @@ -514,13 +526,17 @@ module.exports = async ({ github, context, core }) => { core ); - // Set commit status. Approved PRs return early (commit status is sufficient). + // Approved PRs get a success check run and return early. + // Pending PRs intentionally create NO check run or status. The required + // status check "maintainer-approval" stays as "Expected" (yellow dot) in + // the GitHub UI, which blocks the merge until approval is granted. if (result.allCovered && approverLogins.length > 0) { core.info("All ownership groups have per-path approval."); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "success", - description: "All ownership groups approved", + await github.rest.checks.create({ + ...checkParams, + status: "completed", + conclusion: "success", + output: { title: STATUS_CONTEXT, summary: "All ownership groups approved" }, }); await deleteMarkerComments(github, owner, repo, prNumber); return; @@ -528,36 +544,20 @@ module.exports = async ({ github, context, core }) => { if (result.hasWildcardFiles) { const fileList = result.wildcardFiles.join(", "); - const msg = + core.info( `Files need maintainer review: ${fileList}. ` + - `Maintainers: ${maintainers.join(", ")}`; - core.info(msg); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "pending", - description: msg.length > 140 ? msg.slice(0, 137) + "..." : msg, - }); + `Maintainers: ${maintainers.join(", ")}` + ); } else if (result.uncovered && result.uncovered.length > 0) { const groupList = result.uncovered .map(({ pattern, owners }) => `${pattern} (needs: ${owners.join(", ")})`) .join("; "); - const msg = `Needs approval: ${groupList}`; core.info( - `${msg}. Alternatively, any maintainer can approve: ${maintainers.join(", ")}.` + `Needs approval: ${groupList}. ` + + `Alternatively, any maintainer can approve: ${maintainers.join(", ")}.` ); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "pending", - description: msg.length > 140 ? msg.slice(0, 137) + "..." : msg, - }); } else { - const msg = `Waiting for maintainer approval: ${maintainers.join(", ")}`; - core.info(msg); - await github.rest.repos.createCommitStatus({ - ...statusParams, - state: "pending", - description: msg.length > 140 ? msg.slice(0, 137) + "..." : msg, - }); + core.info(`Waiting for maintainer approval: ${maintainers.join(", ")}`); } // Score contributors via git history diff --git a/.github/workflows/maintainer-approval.test.js b/.github/workflows/maintainer-approval.test.js index 24a90c46af3..2866dc9d3d7 100644 --- a/.github/workflows/maintainer-approval.test.js +++ b/.github/workflows/maintainer-approval.test.js @@ -8,18 +8,23 @@ const runModule = require("./maintainer-approval"); // --- Test helpers --- -function makeTmpOwners(content) { +function makeTmpOwners(content, ownerTeamsContent) { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "approval-test-")); const ghDir = path.join(tmpDir, ".github"); fs.mkdirSync(ghDir); fs.writeFileSync(path.join(ghDir, "OWNERS"), content); + if (ownerTeamsContent) { + fs.writeFileSync(path.join(ghDir, "OWNERTEAMS"), ownerTeamsContent); + } return tmpDir; } +const OWNERTEAMS_CONTENT = "team:eng-apps-devex @teamdev1 @teamdev2\n"; + const OWNERS_CONTENT = [ "* @maintainer1 @maintainer2", "/cmd/pipelines/ @jefferycheng1 @kanterov", - "/cmd/apps/ @databricks/eng-apps-devex", + "/cmd/apps/ team:eng-apps-devex", "/bundle/ @bundleowner", ].join("\n"); @@ -60,7 +65,7 @@ function makeGithub({ reviews = [], files = [], teamMembers = {}, existingCommen const listReviews = Symbol("listReviews"); const listFiles = Symbol("listFiles"); const listComments = Symbol("listComments"); - const statuses = []; + const checkRuns = []; const createdComments = []; const updatedComments = []; const deletedCommentIds = []; @@ -77,9 +82,9 @@ function makeGithub({ reviews = [], files = [], teamMembers = {}, existingCommen listReviews, listFiles, }, - repos: { - createCommitStatus: async (params) => { - statuses.push(params); + checks: { + create: async (params) => { + checkRuns.push(params); }, }, issues: { @@ -105,7 +110,7 @@ function makeGithub({ reviews = [], files = [], teamMembers = {}, existingCommen }, }, }, - _statuses: statuses, + _checkRuns: checkRuns, _comments: createdComments, _updatedComments: updatedComments, _deletedCommentIds: deletedCommentIds, @@ -121,7 +126,7 @@ describe("maintainer-approval", () => { before(() => { originalWorkspace = process.env.GITHUB_WORKSPACE; - tmpDir = makeTmpOwners(OWNERS_CONTENT); + tmpDir = makeTmpOwners(OWNERS_CONTENT, OWNERTEAMS_CONTENT); process.env.GITHUB_WORKSPACE = tmpDir; }); @@ -146,9 +151,9 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "success"); - assert.ok(github._statuses[0].description.includes("maintainer1")); + assert.equal(github._checkRuns.length, 1); + assert.equal(github._checkRuns[0].conclusion, "success"); + assert.ok(github._checkRuns[0].output.summary.includes("maintainer1")); assert.equal(github._comments.length, 0); assert.equal(github._updatedComments.length, 0); }); @@ -168,7 +173,7 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses[0].state, "success"); + assert.equal(github._checkRuns[0].conclusion, "success"); assert.deepEqual(github._deletedCommentIds, [500]); assert.equal(github._comments.length, 0); assert.equal(github._updatedComments.length, 0); @@ -186,9 +191,9 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "success"); - assert.ok(github._statuses[0].description.includes("maintainer-authored")); + assert.equal(github._checkRuns.length, 1); + assert.equal(github._checkRuns[0].conclusion, "success"); + assert.ok(github._checkRuns[0].output.summary.includes("maintainer-authored")); assert.equal(github._comments.length, 0); assert.equal(github._updatedComments.length, 0); }); @@ -208,8 +213,8 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "success"); + assert.equal(github._checkRuns.length, 1); + assert.equal(github._checkRuns[0].conclusion, "success"); assert.equal(github._comments.length, 0); assert.equal(github._updatedComments.length, 0); }); @@ -230,8 +235,8 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "success"); + assert.equal(github._checkRuns.length, 1); + assert.equal(github._checkRuns[0].conclusion, "success"); assert.equal(github._comments.length, 0); assert.equal(github._updatedComments.length, 0); }); @@ -251,12 +256,11 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); - assert.ok(github._statuses[0].description.includes("/bundle/")); + // No check run created; the required check stays as "Expected" (yellow dot). + assert.equal(github._checkRuns.length, 0); }); - it("wildcard files present -> pending, mentions maintainer", async () => { + it("wildcard files present -> pending, no check run", async () => { const github = makeGithub({ reviews: [ { state: "APPROVED", user: { login: "randomreviewer" } }, @@ -268,12 +272,10 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); - assert.ok(github._statuses[0].description.includes("maintainer")); + assert.equal(github._checkRuns.length, 0); }); - it("no approvals at all -> pending", async () => { + it("no approvals at all -> pending, no check run", async () => { const github = makeGithub({ reviews: [], files: [{ filename: "cmd/pipelines/foo.go" }], @@ -283,42 +285,38 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); + assert.equal(github._checkRuns.length, 0); }); - it("team member approved -> success for team-owned path", async () => { + it("OWNERTEAMS member approved -> success for team-owned path", async () => { const github = makeGithub({ reviews: [ { state: "APPROVED", user: { login: "teamdev1" } }, ], files: [{ filename: "cmd/apps/main.go" }], - teamMembers: { "eng-apps-devex": ["teamdev1"] }, }); const core = makeCore(); const context = makeContext(); await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "success"); + assert.equal(github._checkRuns.length, 1); + assert.equal(github._checkRuns[0].conclusion, "success"); }); - it("non-team-member approval for team-owned path -> pending", async () => { + it("non-OWNERTEAMS-member approval for team-owned path -> pending", async () => { const github = makeGithub({ reviews: [ { state: "APPROVED", user: { login: "outsider" } }, ], files: [{ filename: "cmd/apps/main.go" }], - teamMembers: { "eng-apps-devex": [] }, }); const core = makeCore(); const context = makeContext(); await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); + assert.equal(github._checkRuns.length, 0); }); it("CHANGES_REQUESTED does not count as approval", async () => { @@ -333,8 +331,7 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); + assert.equal(github._checkRuns.length, 0); }); it("self-approval by PR author is excluded", async () => { @@ -349,8 +346,7 @@ describe("maintainer-approval", () => { await runModule({ github, context, core }); - assert.equal(github._statuses.length, 1); - assert.equal(github._statuses[0].state, "pending"); + assert.equal(github._checkRuns.length, 0); }); it("no * rule in OWNERS -> setFailed", async () => { @@ -516,7 +512,50 @@ describe("maintainer-approval", () => { assert.ok(body.includes("## Approval status: pending")); assert.ok(body.includes("`/cmd/pipelines/`")); assert.ok(body.includes("`/bundle/`")); - assert.ok(body.includes("approved by @jefferycheng1")); + assert.ok(body.includes("approved by `@jefferycheng1`")); assert.ok(body.includes("needs approval")); }); + + it("lists individual files when fewer than 4 in a group", async () => { + const github = makeGithub({ + reviews: [], + files: [ + { filename: "cmd/pipelines/foo.go" }, + { filename: "bundle/config.go" }, + { filename: "bundle/deploy.go" }, + ], + }); + const core = makeCore(); + const context = makeContext(); + + await runModule({ github, context, core }); + + assert.equal(github._comments.length, 1); + const body = github._comments[0].body; + assert.ok(body.includes("Files:"), "should list individual files"); + assert.ok(body.includes("`bundle/config.go`")); + assert.ok(body.includes("`bundle/deploy.go`")); + }); + + it("shows file count instead of listing when 4 or more files in a group", async () => { + const github = makeGithub({ + reviews: [], + files: [ + { filename: "cmd/pipelines/foo.go" }, + { filename: "bundle/a.go" }, + { filename: "bundle/b.go" }, + { filename: "bundle/c.go" }, + { filename: "bundle/d.go" }, + ], + }); + const core = makeCore(); + const context = makeContext(); + + await runModule({ github, context, core }); + + assert.equal(github._comments.length, 1); + const body = github._comments[0].body; + assert.ok(body.includes("4 files changed"), "should show count for bundle group"); + assert.ok(!body.includes("`bundle/a.go`"), "should not list individual bundle files"); + }); }); diff --git a/.github/workflows/maintainer-approval.yml b/.github/workflows/maintainer-approval.yml index b33fad48c58..b9f0b6e3053 100644 --- a/.github/workflows/maintainer-approval.yml +++ b/.github/workflows/maintainer-approval.yml @@ -5,9 +5,11 @@ on: types: [opened, synchronize, reopened, ready_for_review] pull_request_review: types: [submitted, dismissed] + merge_group: + types: [checks_requested] concurrency: - group: pr-approval-${{ github.event.pull_request.number }} + group: pr-approval-${{ github.event.pull_request.number || github.event.merge_group.head_sha }} cancel-in-progress: true defaults: @@ -15,15 +17,43 @@ defaults: shell: bash jobs: + # Auto-approve maintainer-approval for merge queue entries. + # PRs are already approved before entering the merge queue, + # so we just need to set the status on the merge queue commit. + merge-queue-approval: + if: ${{ github.event_name == 'merge_group' }} + runs-on: + group: databricks-deco-testing-runner-group + labels: ubuntu-latest-deco + permissions: + checks: write + steps: + - name: Auto-approve for merge queue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + await github.rest.checks.create({ + owner: context.repo.owner, + repo: context.repo.repo, + head_sha: context.sha, + name: 'maintainer-approval', + status: 'completed', + conclusion: 'success', + output: { + title: 'maintainer-approval', + summary: 'Auto-approved (merge queue)', + }, + }); + check: runs-on: group: databricks-deco-testing-runner-group labels: ubuntu-latest-deco - if: ${{ !github.event.pull_request.draft }} + if: ${{ github.event_name != 'merge_group' && !github.event.pull_request.draft }} timeout-minutes: 5 permissions: pull-requests: write - statuses: write + checks: write contents: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -32,7 +62,7 @@ jobs: persist-credentials: false fetch-depth: 0 - name: Check approval and suggest reviewers - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: retries: 3 script: |- diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ba47b635e97..2be5811c93c 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -42,7 +42,7 @@ jobs: fetch-depth: 0 - name: Setup Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: tools/go.mod @@ -77,13 +77,20 @@ jobs: # Only run if the target is in the list of targets from testmask if: ${{ contains(fromJSON(needs.testmask.outputs.targets), 'test') }} - name: "make test (${{matrix.os.name}}, ${{matrix.deployment}})" + name: "task test (${{matrix.os.name}}, ${{matrix.deployment}})" runs-on: ${{ matrix.os.runner }} + defaults: + run: + shell: bash + permissions: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -137,23 +144,29 @@ jobs: if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' || github.event_name == 'schedule' }} env: ENVFILTER: DATABRICKS_BUNDLE_ENGINE=${{ matrix.deployment }} - run: make test + run: go tool -modfile=tools/task/go.mod task test - name: Run tests with coverage - # Only run 'make cover' on push to main to make sure it does not get broken. + # Only run 'task cover' on push to main to make sure it does not get broken. if: ${{ github.event_name == 'push' }} env: ENVFILTER: DATABRICKS_BUNDLE_ENGINE=${{ matrix.deployment }} - run: make cover + run: go tool -modfile=tools/task/go.mod task cover - - name: Analyze slow tests - run: make slowest + - name: Upload gotestsum JSON output + # Always upload so we can inspect timing even if tests fail. + if: ${{ always() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: test-output-${{ matrix.os.name }}-${{ matrix.deployment }} + path: test-output.json + if-no-files-found: warn + retention-days: 7 - name: Check out.test.toml files are up to date - shell: bash run: | if ! git diff --exit-code; then - echo "ERROR: detected changed files in the repository; Most likely you have out.test.toml files that are out of date. Run 'make generate-out-test-toml' to update." + echo "ERROR: detected changed files in the repository; Most likely you have out.test.toml files that are out of date. Run 'go test ./acceptance -run \"^TestAccept$\" -only-out-test-toml' to update." exit 1 fi @@ -164,9 +177,13 @@ jobs: # Only run if the target is in the list of targets from testmask if: ${{ contains(fromJSON(needs.testmask.outputs.targets), 'test-exp-aitools') }} - name: "make test-exp-aitools (${{matrix.os.name}})" + name: "task test-exp-aitools (${{matrix.os.name}})" runs-on: ${{ matrix.os.runner }} + defaults: + run: + shell: bash + permissions: id-token: write contents: read @@ -201,7 +218,7 @@ jobs: - name: Run tests run: | - make test-exp-aitools + go tool -modfile=tools/task/go.mod task test-exp-aitools test-exp-ssh: needs: @@ -210,13 +227,20 @@ jobs: # Only run if the target is in the list of targets from testmask if: ${{ contains(fromJSON(needs.testmask.outputs.targets), 'test-exp-ssh') }} - name: "make test-exp-ssh (${{matrix.os.name}})" + name: "task test-exp-ssh (${{matrix.os.name}})" runs-on: ${{ matrix.os.runner }} + defaults: + run: + shell: bash + permissions: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -246,7 +270,7 @@ jobs: - name: Run tests run: | - make test-exp-ssh + go tool -modfile=tools/task/go.mod task test-exp-ssh test-pipelines: needs: @@ -255,13 +279,20 @@ jobs: # Only run if the target is in the list of targets from testmask if: ${{ contains(fromJSON(needs.testmask.outputs.targets), 'test-pipelines') }} - name: "make test-pipelines (${{matrix.os.name}})" + name: "task test-pipelines (${{matrix.os.name}})" runs-on: ${{ matrix.os.runner }} + defaults: + run: + shell: bash + permissions: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -291,7 +322,7 @@ jobs: - name: Run tests run: | - make test-pipelines + go tool -modfile=tools/task/go.mod task test-pipelines # This job groups the result of all the above test jobs. # It is a required check, so it blocks auto-merge and the merge queue. @@ -330,7 +361,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Setup Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod # Use different schema from regular job, to avoid overwriting the same key @@ -340,15 +371,15 @@ jobs: - name: Verify that the schema is up to date run: | - if ! ( make schema && git diff --exit-code ); then - echo "The schema is not up to date. Please run 'make schema' and commit the changes." + if ! ( go tool -modfile=tools/task/go.mod task --force generate-schema && git diff --exit-code ); then + echo "The schema is not up to date. Please run './task generate-schema' and commit the changes." exit 1 fi - name: Verify that the generated enum and required fields are up to date run: | - if ! ( make generate-validation && git diff --exit-code ); then - echo "The generated enum and required fields are not up to date. Please run 'make generate-validation' and commit the changes." + if ! ( go tool -modfile=tools/task/go.mod task --force generate-validation && git diff --exit-code ); then + echo "The generated enum and required fields are not up to date. Please run './task generate-validation' and commit the changes." exit 1 fi @@ -360,41 +391,70 @@ jobs: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" - name: Verify that python/codegen is up to date - working-directory: python run: |- - make codegen + go tool -modfile=tools/task/go.mod task pydabs-codegen if ! ( git diff --exit-code ); then - echo "Generated Python code is not up-to-date. Please run 'pushd python && make codegen' and commit the changes." + echo "Generated Python code is not up-to-date. Please run './task pydabs-codegen' and commit the changes." exit 1 fi - # Skip integration tests (temporarily disabled). - # Creates a passing check for PRs and auto-approves for merge groups. + # Trigger integration tests in a separate repository. + # Writes the same-org "Integration Tests" check run for skip/auto-approve + # paths on deco runners. The cross-org `gh workflow run` dispatch is split + # into the sibling `trigger-tests` job so it can run on emu-access runners + # that are allowlisted in the databricks-eng org. integration-trigger: + needs: + - testmask + if: >- (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]') || (github.event_name == 'merge_group') runs-on: - group: databricks-protected-runner-group-large - labels: linux-ubuntu-latest-large + group: databricks-deco-testing-runner-group + labels: ubuntu-latest-deco permissions: checks: write + contents: read + + environment: "test-trigger-is" steps: + - name: Generate GitHub App Token (check runs) + if: >- + (github.event_name == 'merge_group') || + (github.event_name == 'pull_request' && !contains(fromJSON(needs.testmask.outputs.targets), 'test') && !contains(fromJSON(needs.testmask.outputs.targets), 'test-exp-ssh')) + id: generate-check-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.DECO_TEST_APPROVAL_APP_ID }} + private-key: ${{ secrets.DECO_TEST_APPROVAL_PRIVATE_KEY }} + # DECO_TEST_APPROVAL is installed on the databricks org (not databricks-eng). + owner: databricks + repositories: cli + + # Skip integration tests if the primary "test" target is not triggered by this change. + # Use Checks API (not Statuses API) to match the required "Integration Tests" check. - name: Skip integration tests (pull request) - if: ${{ github.event_name == 'pull_request' }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + if: ${{ github.event_name == 'pull_request' && !contains(fromJSON(needs.testmask.outputs.targets), 'test') && !contains(fromJSON(needs.testmask.outputs.targets), 'test-exp-ssh') }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: + github-token: ${{ steps.generate-check-token.outputs.token }} script: | await github.rest.checks.create({ owner: context.repo.owner, @@ -405,14 +465,16 @@ jobs: conclusion: 'success', output: { title: 'Integration Tests', - summary: '⏭️ Skipped (integration test trigger is temporarily disabled)' + summary: '⏭️ Skipped (changes do not require integration tests)' } }); + # Auto-approve for merge group since tests already passed on the PR. - name: Auto-approve for merge group if: ${{ github.event_name == 'merge_group' }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: + github-token: ${{ steps.generate-check-token.outputs.token }} script: | await github.rest.checks.create({ owner: context.repo.owner, @@ -423,10 +485,59 @@ jobs: conclusion: 'success', output: { title: 'Integration Tests', - summary: '⏭️ Skipped (integration test trigger is temporarily disabled)' + summary: '⏭️ Auto-approved for merge queue (tests already passed on PR)' } }); + # Cross-org dispatch to databricks-eng/eng-dev-ecosystem. Must run on an + # emu-access runner because the databricks-eng org IP-allowlists only the + # release runner group, not deco. See databricks/databricks-sdk-go#1638. + trigger-tests: + needs: + - testmask + + if: >- + (github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' && (contains(fromJSON(needs.testmask.outputs.targets), 'test') || contains(fromJSON(needs.testmask.outputs.targets), 'test-exp-ssh'))) || + (github.event_name == 'push') + + runs-on: + group: databricks-release-runner-group-emu-access + labels: linux-ubuntu-latest-emu-access + + permissions: + contents: read + + environment: "test-trigger-is" + + steps: + - name: Generate GitHub App Token + id: generate-token + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 + with: + app-id: ${{ secrets.DECO_WORKFLOW_TRIGGER_APP_ID }} + private-key: ${{ secrets.DECO_WORKFLOW_TRIGGER_PRIVATE_KEY }} + owner: ${{ secrets.ORG_NAME }} + repositories: ${{ secrets.REPO_NAME }} + + - name: Trigger integration tests (pull request) + if: ${{ github.event_name == 'pull_request' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + run: |- + gh workflow run cli-isolated-pr.yml -R ${{ secrets.ORG_NAME }}/${{ secrets.REPO_NAME }} \ + --ref main \ + -f pull_request_number=${{ github.event.pull_request.number }} \ + -f commit_sha=${{ github.event.pull_request.head.sha }} + + - name: Trigger integration tests (push to main) + if: ${{ github.event_name == 'push' }} + env: + GH_TOKEN: ${{ steps.generate-token.outputs.token }} + run: |- + gh workflow run cli-isolated-nightly.yml -R ${{ secrets.ORG_NAME }}/${{ secrets.REPO_NAME }} \ + --ref main \ + -f commit_sha=${{ github.event.after }} + # Skip integration tests for dependabot PRs. # Dependabot has no access to the "test-trigger-is" environment secrets, # so we use the built-in GITHUB_TOKEN to mark the required "Integration @@ -445,7 +556,7 @@ jobs: steps: - name: Skip integration tests - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: |- await github.rest.checks.create({ diff --git a/.github/workflows/python_push.yml b/.github/workflows/python_push.yml index 23fc910c39d..be62bcd22be 100644 --- a/.github/workflows/python_push.yml +++ b/.github/workflows/python_push.yml @@ -32,15 +32,19 @@ jobs: - name: Checkout repository and submodules uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ matrix.pyVersion }} version: "0.6.5" - name: Run tests - working-directory: python - run: make test + run: go tool -modfile=tools/task/go.mod task pydabs-test python_linters: name: lint @@ -50,14 +54,18 @@ jobs: - name: Checkout repository and submodules uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" - name: Run lint - working-directory: python - run: make lint + run: go tool -modfile=tools/task/go.mod task pydabs-lint python_docs: name: docs @@ -67,11 +75,15 @@ jobs: - name: Checkout repository and submodules uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" - name: Run docs - working-directory: python - run: make docs + run: go tool -modfile=tools/task/go.mod task pydabs-docs diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 9851a963bee..1ebbf6f1859 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -10,6 +10,15 @@ on: - "bugbash-*" workflow_dispatch: + inputs: + tag: + description: "Tag to build (e.g. v1.2.3). Leave empty for a snapshot build of the current ref." + type: string + required: false + publish: + description: "Publish release artifacts to the GitHub release." + type: boolean + default: false jobs: cli: @@ -22,7 +31,7 @@ jobs: permissions: id-token: write - contents: read + contents: write steps: - name: Checkout repository @@ -30,12 +39,25 @@ jobs: with: fetch-depth: 0 fetch-tags: true + ref: ${{ inputs.tag || github.ref }} + + # Check out the workflow's own ref into a side directory so local + # composite actions (e.g. setup-jfrog) and the goreleaser config are + # available even when the built ref is an older tag that predates them. + - name: Checkout workflow ref for local actions + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + path: .workflow-actions + sparse-checkout: | + .github + .goreleaser.yaml - name: Setup JFrog - uses: ./.github/actions/setup-jfrog + uses: ./.workflow-actions/.github/actions/setup-jfrog - name: Setup Go - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod cache-dependency-path: | @@ -73,12 +95,24 @@ jobs: - name: Hide snapshot tag to outsmart GoReleaser run: git tag -d snapshot || true + # Overlay scripts from the workflow ref so goreleaser hooks resolve + # correctly even when building an older tag that predates them. + # Register both injected paths in .git/info/exclude so goreleaser's + # dirty-state check does not flag them as untracked files. + - name: Sync workflow scripts to working directory + run: | + mkdir -p .github/scripts + cp -r .workflow-actions/.github/scripts/. .github/scripts/ + printf '.workflow-actions/\n.github/scripts/\n' >> .git/info/exclude + # Use --snapshot for branch builds (non-tag refs). - name: Run GoReleaser - uses: goreleaser/goreleaser-action@ec59f474b9834571250b370d4735c50f8e2d1e29 # v7.0.0 + uses: goreleaser/goreleaser-action@e24998b8b67b290c2fa8b7c14fcfa7de2c5c9b8c # v7.1.0 with: version: v2.14.3 - args: release --skip=publish ${{ !startsWith(github.ref, 'refs/tags/') && '--snapshot' || '' }} + args: release ${{ !inputs.publish && '--skip=publish' || '' }} --config .workflow-actions/.goreleaser.yaml --skip=docker ${{ (!startsWith(github.ref, 'refs/tags/') && !inputs.tag) && '--snapshot' || '' }} + env: + GITHUB_TOKEN: ${{ github.token }} - name: Verify Windows binary signatures run: | @@ -90,14 +124,18 @@ jobs: echo done + - name: Stage bundle JSON schema for upload + run: cp bundle/schema/jsonschema.json dist/ + - name: Upload artifacts - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: cli path: | dist/*.zip dist/*.tar.gz dist/*SHA256SUMS* + dist/jsonschema.json wheel: runs-on: @@ -106,7 +144,7 @@ jobs: permissions: id-token: write - contents: read + contents: write steps: - name: Checkout repository @@ -114,21 +152,36 @@ jobs: with: fetch-depth: 0 fetch-tags: true + ref: ${{ inputs.tag || github.ref }} + + # Check out the workflow's own ref into a side directory so local + # composite actions (e.g. setup-jfrog) and the goreleaser config are + # available even when the built ref is an older tag that predates them. + - name: Checkout workflow ref for local actions + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + path: .workflow-actions + sparse-checkout: | + .github + .goreleaser.yaml - name: Setup JFrog - uses: ./.github/actions/setup-jfrog + uses: ./.workflow-actions/.github/actions/setup-jfrog - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" - name: Build wheel working-directory: python - run: make build + run: | + rm -rf build dist + uv build . - name: Upload Python wheel - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: wheel path: python/dist/* diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml new file mode 100644 index 00000000000..45d05bc7ae1 --- /dev/null +++ b/.github/workflows/release-docker.yml @@ -0,0 +1,114 @@ +name: release-docker + +# Publishes a Docker image for a specific CLI release tag to ghcr.io/databricks/cli. +# +# Why this is a separate workflow (not part of release-build): +# The release pipeline was simplified in April 2026 (commit 6a0ddd896) by +# consolidating two goreleaser configs into one. Docker publishing was +# intentionally removed from goreleaser at that point ("to be handled +# separately") because goreleaser's docker_manifests step was broken by +# Docker 29.x changing how buildx pushes single-platform images (they became +# OCI manifest lists, which goreleaser could not merge). Rather than pin the +# entire release runner to Docker 28.x indefinitely, Docker publishing was +# decoupled into this standalone workflow that uses `docker buildx imagetools` +# directly and is unaffected by the goreleaser/Docker compatibility issue. + +on: + workflow_dispatch: + inputs: + tag: + description: "Release tag to publish (e.g. v0.298.0)" + type: string + required: true + update_latest: + description: "Also update the 'latest' and 'latest-' tags" + type: boolean + default: true + +jobs: + docker: + runs-on: + group: databricks-protected-runner-group-large + labels: linux-ubuntu-latest-large + + permissions: + packages: write + contents: read + + steps: + - name: Checkout repository at release tag + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ inputs.tag }} + + - name: Set up QEMU for cross-platform builds + uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0 + + - name: Create a buildx builder with multi-platform support + run: docker buildx create --use --driver docker-container + + - name: Log in to GitHub Container Registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Strip leading 'v' from tag + id: version + run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" + env: + TAG: ${{ inputs.tag }} + + - name: Download CLI release binaries + run: | + VERSION="${{ steps.version.outputs.version }}" + TAG="${{ inputs.tag }}" + for ARCH in amd64 arm64; do + curl -sfL \ + "https://github.com/databricks/cli/releases/download/${TAG}/databricks_cli_${VERSION}_linux_${ARCH}.tar.gz" \ + -o "/tmp/databricks_${ARCH}.tar.gz" + mkdir -p "/tmp/cli_${ARCH}" + tar -xzf "/tmp/databricks_${ARCH}.tar.gz" -C "/tmp/cli_${ARCH}" databricks + done + + - name: Build and push amd64 image + run: | + cp /tmp/cli_amd64/databricks ./databricks + docker buildx build \ + --platform linux/amd64 \ + --build-arg ARCH=amd64 \ + --tag "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-amd64" \ + --push . + rm databricks + + - name: Build and push arm64 image + run: | + cp /tmp/cli_arm64/databricks ./databricks + docker buildx build \ + --platform linux/arm64 \ + --build-arg ARCH=arm64 \ + --tag "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-arm64" \ + --push . + rm databricks + + - name: Create and push version-pinned multi-arch manifest + run: | + docker buildx imagetools create \ + --tag "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}" \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-amd64" \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-arm64" + + - name: Update latest multi-arch manifest + if: inputs.update_latest + run: |- + docker buildx imagetools create \ + --tag ghcr.io/databricks/cli:latest-amd64 \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-amd64" + docker buildx imagetools create \ + --tag ghcr.io/databricks/cli:latest-arm64 \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-arm64" + docker buildx imagetools create \ + --tag ghcr.io/databricks/cli:latest \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-amd64" \ + "ghcr.io/databricks/cli:${{ steps.version.outputs.version }}-arm64" diff --git a/.github/workflows/release-prs.yml b/.github/workflows/release-prs.yml index 0048cf04b5d..c7f41dcb375 100644 --- a/.github/workflows/release-prs.yml +++ b/.github/workflows/release-prs.yml @@ -55,7 +55,7 @@ jobs: - name: Dispatch setup-cli release PR if: ${{ !inputs.dry_run }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.DECO_GITHUB_TOKEN }} script: | @@ -117,7 +117,7 @@ jobs: - name: Dispatch homebrew-tap release PR if: ${{ !inputs.dry_run }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.DECO_GITHUB_TOKEN }} script: | @@ -156,7 +156,7 @@ jobs: - name: Dispatch VS Code extension update PR if: ${{ !inputs.dry_run }} - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.DECO_GITHUB_TOKEN }} script: | diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index 94c8980b8e4..cbb9cfb591c 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -6,10 +6,12 @@ on: workflow_dispatch: # No inputs are required for the manual dispatch. + # NOTE: Temporarily disable automated releases. + # # Runs at 8:00 UTC on Monday, Tuesday, Wednesday, and Thursday. To enable automated # tagging for a repository, simply add it to the if block of the tag job. - schedule: - - cron: '0 8 * * MON,TUE,WED,THU' + # schedule: + # - cron: '0 8 * * MON,TUE,WED,THU' # Ensure that only a single instance of the workflow is running at a time. concurrency: diff --git a/.github/workflows/update-schema-docs.yml b/.github/workflows/update-schema-docs.yml new file mode 100644 index 00000000000..f47e191e49a --- /dev/null +++ b/.github/workflows/update-schema-docs.yml @@ -0,0 +1,121 @@ +name: update-schema-docs + +# Regenerate bundle/schema/jsonschema_for_docs.json after every release and +# publish it to the `docgen` branch. +# +# bundle/internal/schema/since_version.go derives `x-since-version` annotations +# from the list of `v*` git tags that exist when the schema is generated. The +# `docgen` branch is therefore stale by one release as soon as the next tag is +# pushed; this workflow keeps it current. + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+*" + + workflow_dispatch: + +permissions: + contents: write + # Required by setup-jfrog (GOPROXY exchange). + id-token: write + +jobs: + update-schema-docs: + runs-on: + group: databricks-protected-runner-group-large + labels: linux-ubuntu-latest-large + + steps: + - name: Checkout main + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + # Regen runs against `main`. fetch-depth: 0 + fetch-tags: true ensure + # since_version.go can resolve `git show :bundle/schema/jsonschema.json` + # for every historical release. + ref: main + fetch-depth: 0 + fetch-tags: true + + - name: Setup JFrog + uses: ./.github/actions/setup-jfrog + + - name: Setup Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: go.mod + cache-dependency-path: | + go.sum + bundle/internal/schema/*.* + + - name: Determine release tag + id: tag + env: + REF_TYPE: ${{ github.ref_type }} + REF_NAME: ${{ github.ref_name }} + run: | + if [ "$REF_TYPE" = "tag" ]; then + tag="$REF_NAME" + else + # git tag --list uses fnmatch (no `+`), so post-filter with grep + # to match the same shape as the trigger above. + tag=$(git tag --list 'v*' --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+' | head -n 1) + fi + if [ -z "$tag" ]; then + echo "Could not determine a release tag to publish for." >&2 + exit 1 + fi + echo "tag=$tag" >> "$GITHUB_OUTPUT" + echo "Publishing for tag $tag" + + - name: Regenerate jsonschema_for_docs.json + run: go tool -modfile=tools/task/go.mod task --force generate-schema-docs + + # Fail loudly if regeneration touches anything other than the docs schema. + # Anything else (annotations.yml, untracked files, ...) is a bug in the + # generator, not something we want to silently publish. + - name: Assert only jsonschema_for_docs.json changed on main + run: | + changed=$(git status --porcelain) + expected=" M bundle/schema/jsonschema_for_docs.json" + if [ -z "$changed" ]; then + echo "Regeneration produced no diff against main." + exit 0 + fi + if [ "$changed" != "$expected" ]; then + echo "Expected only bundle/schema/jsonschema_for_docs.json to be modified." + echo "Actual git status --porcelain:" + echo "$changed" + exit 1 + fi + + - name: Capture regenerated file + run: | + mkdir -p "$RUNNER_TEMP/regen" + cp bundle/schema/jsonschema_for_docs.json "$RUNNER_TEMP/regen/jsonschema_for_docs.json" + + - name: Check out docgen worktree + run: | + git fetch origin docgen + git worktree add "$RUNNER_TEMP/docgen" origin/docgen + + - name: Stage regenerated file on docgen + working-directory: ${{ runner.temp }}/docgen + run: | + mkdir -p bundle/schema + cp "$RUNNER_TEMP/regen/jsonschema_for_docs.json" bundle/schema/jsonschema_for_docs.json + git add bundle/schema/jsonschema_for_docs.json + + - name: Commit and push to docgen + working-directory: ${{ runner.temp }}/docgen + env: + TAG: ${{ steps.tag.outputs.tag }} + run: |- + if git diff --cached --quiet; then + echo "docgen already up to date for ${TAG}; nothing to commit." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git commit -m "Update jsonschema_for_docs.json for ${TAG}" + git push origin HEAD:docgen diff --git a/.gitignore b/.gitignore index b7c7726d934..7a2606681fe 100644 --- a/.gitignore +++ b/.gitignore @@ -28,17 +28,28 @@ __pycache__ .ruff_cache -# Test results from 'make test' +# Test results from 'task test' test-output.json +test-output-unit.json +test-output-unit-root.json +test-output-unit-tools.json +test-output-unit-codegen.json +test-output-acc.json -# Built by make for 'make fmt' and yamlcheck.py in acceptance tests +# Taskfile cache +.task/ + +# Snapshot binary from 'task snapshot' +.databricks/ + +# Built for 'task fmt' and yamlcheck.py in acceptance tests tools/yamlfmt tools/yamlfmt.exe -# Built by make for 'make lint' +# Built for 'task lint' tools/golangci-lint -# Built by make for test filtering +# Built for test filtering tools/testmask/testmask # Cache for tools/gh_report.py @@ -51,6 +62,9 @@ dist/ /pr-* /tmp/ +# Per-module golangci-lint TMPDIR (configured in Taskfile.yml) +/.tmp/ + # Go workspace file go.work go.work.sum diff --git a/.golangci.yaml b/.golangci.yaml index e64590f3659..6f41b1cee22 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -20,7 +20,28 @@ linters: - copyloopvar - forbidigo - depguard + - usestdlibvars + - nilerr + - fatcontext + - nosprintfhostport + - recvcheck + - usetesting + - dupword + - misspell + - nilnesserr + - durationcheck + - exptostd + - gocheckcompilerdirectives + - asciicheck + - reassign + - modernize settings: + modernize: + # omitzero changes JSON wire format for nested structs (unlike omitempty), + # so each site needs per-site review rather than a bulk rewrite. + disable: + - omitzero + depguard: rules: no-experimental-imports: @@ -34,6 +55,20 @@ linters: deny: - pkg: "github.com/databricks/cli/experimental" desc: "must not import experimental/ packages; use an interface or move the dependency" + no-legacy-rand: + deny: + - pkg: "math/rand$" + desc: "use math/rand/v2 instead of math/rand" + no-stdlib-log: + files: + - "**" + - "!**/bundle/docsgen/**" + - "!**/bundle/internal/schema/**" + - "!**/bundle/internal/tf/codegen/**" + - "!**/bundle/internal/validation/**" + deny: + - pkg: "log$" + desc: "use libs/log instead of the standard library log package" forbidigo: forbid: - pattern: 'term\.IsTerminal' @@ -46,6 +81,24 @@ linters: msg: Use env.UserHomeDir(ctx) from libs/env instead. - pattern: 'os\.Getenv' msg: Use env.Get(ctx) from the libs/env package instead of os.Getenv. + - pattern: 'sort\.Slice' + msg: Use slices.SortFunc from the standard library instead. + - pattern: 'sort\.SliceStable' + msg: Use slices.SortStableFunc from the standard library instead. + - pattern: 'sort\.Strings' + msg: Use slices.Sort from the standard library instead. + - pattern: 'sort\.Ints' + msg: Use slices.Sort from the standard library instead. + - pattern: 'sort\.Float64s' + msg: Use slices.Sort from the standard library instead. + - pattern: 'os\.IsNotExist' + msg: Use errors.Is(err, fs.ErrNotExist) instead. + - pattern: 'os\.IsExist' + msg: Use errors.Is(err, fs.ErrExist) instead. + - pattern: 'os\.IsPermission' + msg: Use errors.Is(err, fs.ErrPermission) instead. + - pattern: 'sync\.Once\b($|[^FV])' + msg: Use sync.OnceFunc, sync.OnceValue, or sync.OnceValues instead. analyze-types: true copyloopvar: check-alias: true diff --git a/.release_metadata.json b/.release_metadata.json index eed29191d29..85dfb733fb0 100644 --- a/.release_metadata.json +++ b/.release_metadata.json @@ -1,3 +1,3 @@ { - "timestamp": "2026-04-08 08:53:45+0000" + "timestamp": "2026-05-07 10:05:31+0000" } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 7386b4ec50e..3cdcc3406ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,42 +1,55 @@ This file provides guidance to AI assistants when working with code in this repository. +Rules prefixed `**RULE:**` are mandatory. `GOOD:` and `BAD:` labels on code snippets mark patterns to follow and patterns to avoid. This convention is a common best practice for AI-assistant rule files and is used consistently across `AGENTS.md` and `.agent/rules/*.md`. + # Project Overview This is the Databricks CLI, a command-line interface for interacting with Databricks workspaces and managing Declarative Automation Bundles (DABs), formerly known as Databricks Asset Bundles. The project is written in Go and follows a modular architecture. # General Rules -When moving code from one place to another, please don't unnecessarily change the code or omit parts. +**RULE: When moving code from one place to another, don't unnecessarily change or omit parts.** Keep refactors separate from content changes so reviewers can tell them apart. + +**RULE: Do not modify or remove existing comments in code you didn't write.** Comments often encode non-obvious context (a bug reference, a workaround, a reason the code is shaped a certain way) that is lost if rewritten. Leave them alone unless the user explicitly asks for a change. + +**RULE: Prefer simplicity over cleverness. Avoid speculative fallbacks and default values.** If you catch yourself adding a fallback branch "just in case," identify the correct path and use only that one. Reviewers in this repo reject speculative flexibility. + +**RULE: Keep each PR focused on one change.** If you notice an unrelated cleanup, bug fix, or refactor while making your primary change, leave it alone or put it in a separate PR. Reviewers consistently ask to split mixed PRs, especially when a dependency bump or schema diff rides along with a feature change. + +**RULE: Before adding a new helper, search the codebase for an existing one.** Common homes: `libs/` (shared utilities), `libs/databrickscfg/` (config), `libs/git/`, `libs/filer/`, `libs/cmdio/` (CLI I/O, spinners, prompts), `libs/env/` (env vars), `libs/testserver/`, `libs/structpath/` and `libs/dyn/` (path / dynamic values), `acceptance/bin/` (acceptance test helpers), `internal/mocks/` (generated mocks). A function that duplicates an existing name and signature in the same package is a compile error waiting to happen; grep before you name. # Development Commands ### Building and Testing -- `make build` - Build the CLI binary -- `make test` - Run unit tests for all packages +- `./task build` - Build the CLI binary +- `./task test` - Run unit and acceptance tests for all packages - `go test ./acceptance -run TestAccept/bundle/// -tail -test.v` - run a single acceptance test -- `make integration` - Run integration tests (requires environment variables) -- `make cover` - Generate test coverage reports +- `./task integration` - Run integration tests (requires environment variables) +- `./task cover` - Generate test coverage reports ### Code Quality -- `make lint` - Run linter on changed files only (uses lintdiff.py) -- `make lintfull` - Run full linter with fixes (golangci-lint) -- `make ws` - Run whitespace linter -- `make fmt` - Format code (Go, Python, YAML) -- `make checks` - Run quick checks (tidy, whitespace, links) +- `./task lint` - Run full linter across all Go modules (root, tools, codegen) +- `./task lint-q` - Run linter on changed files only (uses lintdiff.py, root module, with --fix) +- `./task ws` - Run whitespace linter +- `./task fmt` - Format all code (Go, Python, YAML) +- `./task fmt-q` - Format changed files only (incremental Go + Python + YAML) +- `./task checks` - Run quick checks (tidy, whitespace, links) ### Specialized Commands -- `make schema` - Generate bundle JSON schema -- `make docs` - Generate bundle documentation -- `make generate` - Generate CLI code from OpenAPI spec (requires universe repo) +- `./task generate-schema` - Generate bundle JSON schema +- `./task generate-docs` - Generate bundle documentation +- `./task generate-genkit` - Run genkit to generate CLI commands and tagging workflow (requires universe repo) +- `./task generate` - Run all generators ### Git Commands -Use `git rm` to remove and `git mv` to rename files instead of directly modifying files on FS. +**RULE: Use `git rm` to remove and `git mv` to rename files, instead of directly modifying files on the filesystem.** + +**RULE: When rebasing, prefix git commands so they never launch an interactive editor.** -If asked to rebase, always prefix each git command with appropriate settings so that it never launches interactive editor: ```sh GIT_EDITOR=true GIT_SEQUENCE_EDITOR=true VISUAL=true GIT_PAGER=cat git fetch origin main && GIT_EDITOR=true GIT_SEQUENCE_EDITOR=true VISUAL=true GIT_PAGER=cat git rebase origin/main @@ -77,20 +90,101 @@ GIT_EDITOR=true GIT_SEQUENCE_EDITOR=true VISUAL=true GIT_PAGER=cat git rebase or # Development Tips -- Use `make test-update` to regenerate acceptance test outputs after changes -- The CLI binary supports both `databricks` and `pipelines` command modes based on executable name -- Comments should explain "why", not "what" — reviewers consistently reject comments that merely restate the code +- Use `./task test-update` to regenerate acceptance test outputs after changes. +- The CLI binary supports both `databricks` and `pipelines` command modes based on executable name. + +**RULE: Comments should explain "why", not "what".** Reviewers consistently reject comments that merely restate the code. + +**RULE: When code relies on a non-obvious invariant, workaround, or backend quirk, add a short comment stating the reason.** The inverse of the rule above: noise comments are bad, but missing comments are the single most common thing reviewers catch. Triggers include: API quirks (PATCH-like semantics, no get-by-name, stripped prefixes), fields intentionally included or excluded (output-only, etag, `ForceSendFields`), branches that look dead but are kept as guards, and tests where the expectation isn't obvious from the assertions. + +GOOD: + +```go +// The Workspace API strips the "/Workspace" prefix from parent_path on GET, +// so we re-add it here to match the local configuration. +parentPath = "/Workspace" + parentPath +``` + +BAD: + +```go +parentPath = "/Workspace" + parentPath +``` # Common Mistakes -- Do NOT add dependencies without checking license compatibility. -- Do NOT use `os.Exit()` outside of `main.go`. -- Do NOT remove or skip failing tests to fix CI — fix the underlying issue. -- Do NOT leave debug print statements (`fmt.Println`, `log.Printf` for debugging) in committed code — always scrub before committing. +**RULE: When adding a direct Go dependency, annotate its license in `go.mod` and update `NOTICE`.** Before picking the SPDX identifier, read `internal/build/license_test.go` to see the current allowlist (the `spdxLicenses` map). That test is the source of truth and will fail CI if a direct `require` line lacks a matching SPDX suffix comment (e.g. `// MIT`). Also add a corresponding entry to `NOTICE` under the matching license section. If a dep's license isn't on the allowlist, discuss before adding. + +**RULE: Do not use `os.Exit()` outside of `main.go`.** `main.go` owns the exit path; calling `os.Exit()` elsewhere skips deferred cleanup and complicates testing. + +**RULE: Do not remove or skip failing tests to fix CI.** Fix the underlying issue instead. + +**RULE: Do not leave debug print statements in committed code.** `fmt.Println`, `log.Printf`, or similar. Always scrub before committing. + +**RULE: Do not add defensive `nil` checks for values the caller or framework is documented to always provide.** If a check exists "just in case", either remove it or attach a comment explaining why the invariant might be violated. Direct engine resource methods (`DoCreate`, `DoUpdate`, `RemapState`, etc.) never receive nil receivers or state from the framework, so extra nil-guards there are dead code. + +**RULE: Use a non-resolving TLD reserved by [RFC 2606 §2](https://datatracker.ietf.org/doc/html/rfc2606#section-2) (`.test`, `.example`, `.invalid`, `.localhost`) for any test fixture host — `Config.Host`, `databricks.yml`'s `workspace.host`, `.databrickscfg`.** Real domains hit the SDK well-known endpoint resolver and can stall tests for ~5 minutes per call when the runner network can't fast-fail the lookup. The repo convention is `.test` (the TLD RFC 2606 specifically reserves "for use in testing"). See PR #5125 for prior history. + +Where a panic is genuinely possible (e.g. `reflect.Type.Elem()` on a non-pointer, division by an empty slice's length), validate at the entry point and return an error. # Error Handling -- Wrap errors with context: `fmt.Errorf("failed to deploy %s: %w", name, err)` -- Use `logdiag.LogDiag` / `logdiag.LogError` for logging diagnostics. -- Return early on errors; avoid deeply nested if-else chains. -- Use `diag.Errorf` / `diag.Warningf` to create diagnostics with severity. +**RULE: Wrap errors with context using `%w`.** Preserves the error chain so `errors.Is` and `errors.As` keep working upstream. + +GOOD: + +```go +return fmt.Errorf("failed to deploy %s: %w", name, err) +``` + +BAD: + +```go +return fmt.Errorf("failed to deploy %s: %s", name, err) +``` + +**RULE: Return early on errors; avoid deeply nested if-else chains.** + +**RULE: Use `logdiag.LogDiag` and `logdiag.LogError` for logging diagnostics.** + +**RULE: Use `diag.Errorf` and `diag.Warningf` to create diagnostics with severity.** + +**RULE: Compare errors with `errors.Is` or `errors.As` against a sentinel or typed error. Never branch on `err.Error()` string content.** The SDK exposes sentinels like `apierr.ErrNotFound` and `apierr.ErrResourceDoesNotExist`; the CLI has its own helpers like `isResourceGone`. String-matching error messages breaks the moment the upstream wording changes. + +GOOD: + +```go +import "github.com/databricks/databricks-sdk-go/apierr" + +if errors.Is(err, apierr.ErrResourceDoesNotExist) { + return nil +} +``` + +BAD: + +```go +if err != nil && strings.Contains(err.Error(), "does not exist") { + return nil +} +``` + +# CLI UX and validation + +**RULE: Reject incompatible inputs early with an actionable error. Never silently ignore a flag or config field the current mode can't honor.** If a flag is incompatible with another flag or with a mode, return an error at flag-parse or validation time that tells the user which flag pair is at fault and what to do. If a config field applies only to certain resource types or engines, return a validation error, not a warning that gets lost in log output. + +GOOD: + +```go +if opts.Bind && opts.Resource != "dashboards" { + return fmt.Errorf("--bind is only supported for dashboards, got %q", opts.Resource) +} +``` + +BAD: + +```go +if opts.Bind && opts.Resource != "dashboards" { + // silently drop the flag; user can't tell why nothing happened +} +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 8529eb0a27a..750bc39ad7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,92 @@ # Version changelog +## Release v0.299.1 (2026-05-07) + +### CLI + +* `databricks api` now works against unified hosts. Adds `--account` to scope a call to the account API and `--workspace-id` to override the workspace routing identifier per call. A `?o=` query parameter on the path (the SPOG URL convention used by the Databricks UI) is also recognized as a per-call workspace override, so URLs pasted from the browser route correctly. +* JSON output for single objects now uses standard `"key": "value"` spacing (matching list output and `encoding/json` defaults). + +### Bundles +* Validate that resource keys do not contain variable references ([#5169](https://github.com/databricks/cli/pull/5169)) +* engine/direct: Drop the deployment state entry on a recreate before the follow-up `Create`, so a `Create` failure no longer leaves a broken state with `invalid state: empty id` on the next `bundle plan` ([#5173](https://github.com/databricks/cli/pull/5173)). +* `bundle debug list-targets`: skip nil entries in the targets map instead of panicking when a target is declared with a null value ([#5203](https://github.com/databricks/cli/pull/5203)). + +### Dependency updates + +* Added `github.com/jackc/pgx/v5` v5.9.1 (MIT) as a new dependency. Used by an experimental Postgres command added in this release; the package is dormant for users who do not invoke that command. + + +## Release v0.299.0 (2026-04-29) + +### CLI + +* Moved file-based OAuth token cache management from the SDK to the CLI. No user-visible change; part of a three-PR sequence that makes the CLI the sole owner of its token cache ([#5056](https://github.com/databricks/cli/pull/5056)). +* Remove the `--experimental-is-unified-host` flag and stop reading `experimental_is_unified_host` from `.databrickscfg` profiles and the `DATABRICKS_EXPERIMENTAL_IS_UNIFIED_HOST` env var. Unified hosts are now detected exclusively from `/.well-known/databricks-config` discovery. The `experimental_is_unified_host` field is retained as a no-op in `databricks.yml` for schema compatibility ([#5047](https://github.com/databricks/cli/pull/5047)). +* Added interactive pagination for list commands that have a row template (jobs, clusters, apps, pipelines, etc.). When stdin, stdout, and stderr are all TTYs, `databricks list` now streams 50 rows at a time and prompts `[space] more [enter] all [q|esc] quit`. ENTER can be interrupted by `q`/`esc`/`Ctrl+C` between pages. Colors and alignment match the existing non-paged output; column widths stay stable across pages. Piped output and `--output json` are unchanged ([#5015](https://github.com/databricks/cli/pull/5015)). +* Added experimental OS-native secure token storage opt-in via `DATABRICKS_AUTH_STORAGE=secure`. Legacy file-backed token storage remains the default ([#5008](https://github.com/databricks/cli/pull/5008), [#5013](https://github.com/databricks/cli/pull/5013)). +* Fixed a panic in `databricks warehouses update-default-warehouse-override` when invoked without all required positional arguments (e.g. picking a warehouse from the interactive drop-down and then hitting an index-out-of-range crash). The command now validates arguments up front and returns a usage error. Fixes [#5070](https://github.com/databricks/cli/issues/5070) via [#5079](https://github.com/databricks/cli/pull/5079). + +### Bundles + +* Translate relative paths in `alert_task.workspace_path` on job tasks to fully qualified workspace paths, matching the behavior of other task path fields. Applies to both regular tasks and `for_each_task` nested tasks ([#4836](https://github.com/databricks/cli/pull/4836)). + +### Dependency updates + +* Added `github.com/zalando/go-keyring` as a new dependency (dormant until a later release enables experimental secure-storage for OAuth tokens) ([#5008](https://github.com/databricks/cli/pull/5008)). + + +## Release v0.298.0 (2026-04-22) + +### CLI +* Added `--limit` flag to all paginated list commands for client-side result capping ([#4984](https://github.com/databricks/cli/pull/4984)). On `jobs list` and `jobs list-runs` the former API page-size flag was renamed to `--page-size` (hidden) to avoid collision. +* Accept `yes` in addition to `y` for confirmation prompts, and show `[y/N]` to indicate that no is the default. +* Cache `/.well-known/databricks-config` lookups under `~/.cache/databricks//host-metadata/` so repeat CLI invocations against the same host skip the ~700ms discovery round trip. +* Deprecated `auth env`. The command is hidden from help listings and prints a deprecation warning to stderr; it will be removed in a future release. + +### Bundles +* Remove `experimental-jobs-as-code` template, superseded by `pydabs` ([#4999](https://github.com/databricks/cli/pull/4999)). +* Prompt before destroying or recreating Lakebase resources (database instances, synced database tables, postgres projects and branches) ([#5052](https://github.com/databricks/cli/pull/5052)). +* Treat deleted resources as not running in the `fail-on-active-runs` check ([#5044](https://github.com/databricks/cli/pull/5044)). +* engine/direct: Added support for Vector Search Endpoints ([#4887](https://github.com/databricks/cli/pull/4887)). +* engine/direct: Exclude deploy-only fields (e.g. `lifecycle`) from the Apps update mask so requests that change both `description` and `lifecycle.started` in the same deploy no longer fail with `INVALID_PARAMETER_VALUE` ([#5042](https://github.com/databricks/cli/pull/5042), [#5051](https://github.com/databricks/cli/pull/5051)). +* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks ([#4990](https://github.com/databricks/cli/pull/4990)). + +### Dependency updates +* Bump `github.com/databricks/databricks-sdk-go` from v0.126.0 to v0.128.0 ([#4984](https://github.com/databricks/cli/pull/4984), [#5031](https://github.com/databricks/cli/pull/5031)). +* Bump Go toolchain to 1.25.9 ([#5004](https://github.com/databricks/cli/pull/5004)). + + +## Release v0.297.2 (2026-04-19) + +### Notable Changes +* This release includes a fix for `error downloading Terraform: unable to verify checksums signature: openpgp: key expired` error +observed when running `databricks bundle deploy` command. + +### Bundles +* Use hardcoded ArmoredPublicKey for TF binary installation ([#5019](https://github.com/databricks/cli/pull/5019)) + +## Release v0.297.1 (2026-04-17) + +### Dependency updates +* Bump Go toolchain to 1.25.9 ([#5004](https://github.com/databricks/cli/pull/5004)) + +## Release v0.297.0 (2026-04-15) + +### CLI +* Auth commands now accept a profile name as a positional argument ([#4840](https://github.com/databricks/cli/pull/4840)) + +* Add `auth logout` command for clearing cached OAuth tokens and optionally removing profiles ([#4613](https://github.com/databricks/cli/pull/4613), [#4616](https://github.com/databricks/cli/pull/4616), [#4647](https://github.com/databricks/cli/pull/4647)) + +### Bundles +* Added support for lifecycle.started option for apps ([#4672](https://github.com/databricks/cli/pull/4672)) +* engine/direct: Fix permissions for resources.models ([#4941](https://github.com/databricks/cli/pull/4941)) +* Fix resource references not correctly resolved in apps config section ([#4964](https://github.com/databricks/cli/pull/4964)) +* Allow run_as for dashboards with embed_credentials set to false ([#4961](https://github.com/databricks/cli/pull/4961)) +* direct: Pass changed fields into update mask for apps instead of wildcard ([#4963](https://github.com/databricks/cli/pull/4963)) +* engine/direct: Fix deploy of configurations with dots in maps keys ([#4977](https://github.com/databricks/cli/pull/4977)) + + ## Release v0.296.0 (2026-04-08) ### Notable Changes diff --git a/Makefile b/Makefile deleted file mode 100644 index b31bb73d088..00000000000 --- a/Makefile +++ /dev/null @@ -1,300 +0,0 @@ -.PHONY: default -default: checks fmt lint - -# Default packages to test (all) -TEST_PACKAGES = ./acceptance/internal ./libs/... ./internal/... ./cmd/... ./bundle/... ./experimental/ssh/... . - -# Default acceptance test filter (all) -ACCEPTANCE_TEST_FILTER = "" - -GO_TOOL ?= go tool -modfile=tools/go.mod -GOTESTSUM_FORMAT ?= pkgname-and-test-fails -GOTESTSUM_CMD ?= ${GO_TOOL} gotestsum --format ${GOTESTSUM_FORMAT} --no-summary=skipped --jsonfile test-output.json --rerun-fails -LOCAL_TIMEOUT ?= 30m - - -.PHONY: lintfull -lintfull: ./tools/golangci-lint - ./tools/golangci-lint run --fix - -.PHONY: lint -lint: ./tools/golangci-lint - ./tools/lintdiff.py ./tools/golangci-lint run --fix - -.PHONY: tidy -tidy: - @# not part of golangci-lint, apparently - go mod tidy - -.PHONY: lintcheck -lintcheck: ./tools/golangci-lint - ./tools/golangci-lint run ./... - -.PHONY: fmtfull -fmtfull: ./tools/golangci-lint ./tools/yamlfmt - ruff format -n - ./tools/golangci-lint fmt - ./tools/yamlfmt . - -.PHONY: fmt -fmt: ./tools/golangci-lint ./tools/yamlfmt - ruff format -n - ./tools/lintdiff.py ./tools/golangci-lint fmt - ./tools/yamlfmt . - -# pre-building yamlfmt because it is invoked from tests and scripts -tools/yamlfmt: tools/go.mod tools/go.sum - go build -modfile=tools/go.mod -o tools/yamlfmt github.com/google/yamlfmt/cmd/yamlfmt - -tools/yamlfmt.exe: tools/go.mod tools/go.sum - go build -modfile=tools/go.mod -o tools/yamlfmt.exe github.com/google/yamlfmt/cmd/yamlfmt - -# pre-building golangci-lint because it's faster to run pre-built version -tools/golangci-lint: tools/go.mod tools/go.sum - go build -modfile=tools/go.mod -o tools/golangci-lint github.com/golangci/golangci-lint/v2/cmd/golangci-lint - -.PHONY: ws -ws: - ./tools/validate_whitespace.py - -.PHONY: wsfix -wsfix: - ./tools/validate_whitespace.py --fix - -.PHONY: links -links: - ./tools/update_github_links.py - -# Checks other than 'fmt' and 'lint'; these are fast, so can be run first -.PHONY: checks -checks: tidy ws links - - -.PHONY: install-pythons -install-pythons: - uv python install 3.9 3.10 3.11 3.12 3.13 - -# Run short unit and acceptance tests (testing.Short() is true). -.PHONY: test -test: test-unit test-acc - -# Run all unit and acceptance tests. -.PHONY: test-slow -test-slow: test-slow-unit test-slow-acc - -.PHONY: test-unit -test-unit: - ${GOTESTSUM_CMD} --packages "${TEST_PACKAGES}" -- -timeout=${LOCAL_TIMEOUT} -short - -.PHONY: test-slow-unit -test-slow-unit: - ${GOTESTSUM_CMD} --packages "${TEST_PACKAGES}" -- -timeout=${LOCAL_TIMEOUT} - -.PHONY: test-acc -test-acc: - ${GOTESTSUM_CMD} --packages ./acceptance/... -- -timeout=${LOCAL_TIMEOUT} -short -run ${ACCEPTANCE_TEST_FILTER} - -.PHONY: test-slow-acc -test-slow-acc: - ${GOTESTSUM_CMD} --packages ./acceptance/... -- -timeout=${LOCAL_TIMEOUT} -run ${ACCEPTANCE_TEST_FILTER} - -# Updates acceptance test output (local tests) -.PHONY: test-update -test-update: - -go test ./acceptance -run '^TestAccept$$' -update -timeout=${LOCAL_TIMEOUT} - -# Updates acceptance test output for template tests only -.PHONY: test-update-templates -test-update-templates: - -go test ./acceptance -run '^TestAccept/bundle/templates' -update -timeout=${LOCAL_TIMEOUT} - -# Regenerate out.test.toml files without running tests -.PHONY: generate-out-test-toml -generate-out-test-toml: - go test ./acceptance -run '^TestAccept$$' -only-out-test-toml -timeout=${LOCAL_TIMEOUT} - -# Updates acceptance test output (integration tests, requires access) -.PHONY: test-update-aws -test-update-aws: - deco env run -i -n aws-prod-ucws -- env DATABRICKS_TEST_SKIPLOCAL=1 go test ./acceptance -run ^TestAccept$$ -update -timeout=1h -v - -.PHONY: test-update-all -test-update-all: test-update test-update-aws - -.PHONY: slowest -slowest: - ${GO_TOOL} gotestsum tool slowest --jsonfile test-output.json --threshold 1s --num 50 - -.PHONY: cover -cover: - rm -fr ./acceptance/build/cover/ - VERBOSE_TEST=1 ${GOTESTSUM_CMD} --packages "${TEST_PACKAGES}" -- -coverprofile=coverage.txt -timeout=${LOCAL_TIMEOUT} - VERBOSE_TEST=1 CLI_GOCOVERDIR=build/cover ${GOTESTSUM_CMD} --packages ./acceptance/... -- -timeout=${LOCAL_TIMEOUT} -run ${ACCEPTANCE_TEST_FILTER} - rm -fr ./acceptance/build/cover-merged/ - mkdir -p acceptance/build/cover-merged/ - go tool covdata merge -i $$(printf '%s,' acceptance/build/cover/* | sed 's/,$$//') -o acceptance/build/cover-merged/ - go tool covdata textfmt -i acceptance/build/cover-merged -o coverage-acceptance.txt - -.PHONY: showcover -showcover: - go tool cover -html=coverage.txt - -.PHONY: acc-showcover -acc-showcover: - go tool cover -html=coverage-acceptance.txt - -.PHONY: build -build: tidy - go build - -# builds the binary in a VM environment (such as Parallels Desktop) where your files are mirrored from the host os -.PHONY: build-vm -build-vm: tidy - go build -buildvcs=false - -.PHONY: snapshot -snapshot: - go build -o .databricks/databricks - -# Produce release binaries and archives in the dist folder without uploading them anywhere. -# Useful for "databricks ssh" development, as it needs to upload linux releases to the /Workspace. -.PHONY: snapshot-release -snapshot-release: - goreleaser release --clean --skip docker --snapshot - -.PHONY: schema -schema: - go run ./bundle/internal/schema ./bundle/internal/schema ./bundle/schema/jsonschema.json - -.PHONY: schema-for-docs -schema-for-docs: - go run ./bundle/internal/schema ./bundle/internal/schema ./bundle/schema/jsonschema_for_docs.json --docs - -.PHONY: docs -docs: - go run ./bundle/docsgen ./bundle/internal/schema ./bundle/docsgen - -INTEGRATION = go run -modfile=tools/go.mod ./tools/testrunner/main.go ${GO_TOOL} gotestsum --format github-actions --rerun-fails --jsonfile output.json --packages "./acceptance ./integration/..." -- -parallel 4 -timeout=2h - -.PHONY: integration -integration: install-pythons - $(INTEGRATION) - -.PHONY: integration-short -integration-short: install-pythons - DATABRICKS_TEST_SKIPLOCAL=1 VERBOSE_TEST=1 $(INTEGRATION) -short - -.PHONY: dbr-integration -dbr-integration: install-pythons - DBR_ENABLED=true go test -v -timeout 4h -run TestDbrAcceptance$$ ./acceptance - -# DBR acceptance tests - run on Databricks Runtime using serverless compute -# These require deco env run for authentication -# Set DBR_TEST_VERBOSE=1 for detailed output (e.g., DBR_TEST_VERBOSE=1 make dbr-test) -.PHONY: dbr-test -dbr-test: - deco env run -i -n aws-prod-ucws -- make dbr-integration - -.PHONY: generate-validation -generate-validation: - go run ./bundle/internal/validation/. - gofmt -w -s ./bundle/internal/validation/generated - -# Rule to generate the CLI from a new version of the OpenAPI spec. -# I recommend running this rule from Arca because of faster build times -# because of better caching and beefier machines, but it should also work -# fine from your local mac. -# -# By default, this rule will use the universe directory in your home -# directory. You can override this by setting the UNIVERSE_DIR -# environment variable. -# -# Example: -# UNIVERSE_DIR=/Users/shreyas.goenka/universe make generate -UNIVERSE_DIR ?= $(HOME)/universe -GENKIT_BINARY := $(UNIVERSE_DIR)/bazel-bin/openapi/genkit/genkit_/genkit - -.PHONY: generate -generate: - @echo "Checking out universe at SHA: $$(cat .codegen/_openapi_sha)" - cd $(UNIVERSE_DIR) && git cat-file -e $$(cat $(PWD)/.codegen/_openapi_sha) 2>/dev/null || git fetch --filter=blob:none origin master && git checkout $$(cat $(PWD)/.codegen/_openapi_sha) - @echo "Building genkit..." - cd $(UNIVERSE_DIR) && bazel build //openapi/genkit - @echo "Generating CLI code..." - $(GENKIT_BINARY) update-sdk - cat .gitattributes.manual .gitattributes > .gitattributes.tmp && mv .gitattributes.tmp .gitattributes - -go test ./acceptance -run TestAccept/bundle/refschema -update &> /dev/null - @echo "Updating direct engine config..." - make generate-direct - go test ./bundle/internal/schema - -.codegen/openapi.json: .codegen/_openapi_sha - wget -O $@.tmp "https://openapi.dev.databricks.com/$$(cat $<)/specs/all-internal.json" && mv $@.tmp $@ && touch $@ - -.PHONY: generate-direct -generate-direct: generate-direct-apitypes generate-direct-resources - -.PHONY: generate-direct-apitypes -generate-direct-apitypes: bundle/direct/dresources/apitypes.generated.yml - -.PHONY: generate-direct-resources -generate-direct-resources: bundle/direct/dresources/resources.generated.yml - -.PHONY: generate-direct-clean -generate-direct-clean: - rm -f bundle/direct/dresources/apitypes.generated.yml bundle/direct/dresources/resources.generated.yml - -bundle/direct/dresources/apitypes.generated.yml: ./bundle/direct/tools/generate_apitypes.py .codegen/openapi.json acceptance/bundle/refschema/out.fields.txt - python3 $^ > $@ - -bundle/direct/dresources/resources.generated.yml: ./bundle/direct/tools/generate_resources.py .codegen/openapi.json bundle/direct/dresources/apitypes.generated.yml bundle/direct/dresources/apitypes.yml acceptance/bundle/refschema/out.fields.txt - python3 $^ > $@ - -.PHONY: test-exp-aitools -test-exp-aitools: - make test TEST_PACKAGES="./experimental/aitools/..." ACCEPTANCE_TEST_FILTER="TestAccept/apps" - -.PHONY: test-exp-ssh -test-exp-ssh: - make test TEST_PACKAGES="./experimental/ssh/..." ACCEPTANCE_TEST_FILTER="TestAccept/ssh" - -.PHONY: test-pipelines -test-pipelines: - make test TEST_PACKAGES="./cmd/pipelines/..." ACCEPTANCE_TEST_FILTER="TestAccept/pipelines" - - -# Benchmarks: - -.PHONY: bench1k -bench1k: - BENCHMARK_PARAMS="--jobs 1000" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m - -.PHONY: bench100 -bench100: - BENCHMARK_PARAMS="--jobs 100" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m - -# small benchmark to quickly test benchmark-related code -.PHONY: bench10 -bench10: - BENCHMARK_PARAMS="--jobs 10" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m - -bench1k.log: - make bench1k | tee $@ - -bench100.log: - make bench100 | tee $@ - -bench10.log: - make bench10 | tee $@ - -.PHONY: bench1k_summary -bench1k_summary: bench1k.log - ./tools/bench_parse.py $< - -.PHONY: bench100_summary -bench100_summary: bench100.log - ./tools/bench_parse.py $< - -.PHONY: bench10_summary -bench10_summary: bench10.log - ./tools/bench_parse.py $< diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 93917532709..b6bdb4d965b 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -1,17 +1,19 @@ # NEXT CHANGELOG -## Release v0.297.0 - -### Notable Changes +## Release v0.299.2 ### CLI -* Auth commands now accept a profile name as a positional argument ([#4840](https://github.com/databricks/cli/pull/4840)) -* Add `auth logout` command for clearing cached OAuth tokens and optionally removing profiles ([#4613](https://github.com/databricks/cli/pull/4613), [#4616](https://github.com/databricks/cli/pull/4616), [#4647](https://github.com/databricks/cli/pull/4647)) +* `databricks auth describe` now reports where U2M (`databricks-cli`) tokens are stored: `plaintext` (`~/.databricks/token-cache.json`) or `secure` (OS keyring), and the source of the choice (env var, config setting, or default). +* Marked the default profile in the interactive pickers shown by `databricks auth switch`, `databricks auth logout`, `databricks auth token`, and `databricks auth login`, and moved it to the top of the list. `databricks auth login` and `databricks auth logout` now offer the same selectors as `databricks auth token` and `databricks auth switch` respectively. ### Bundles -* Added support for lifecycle.started option for apps ([#4672](https://github.com/databricks/cli/pull/4672)) +* Stop applying `presets.name_prefix` (and the dev-mode `[dev ]` rename) to `vector_search_endpoints` ([#5209](https://github.com/databricks/cli/pull/5209)). + +* Fix `bundle generate` job to preserve nested notebook directory structure ([#4596](https://github.com/databricks/cli/pull/4596)) +* Propagate authentication environment (including `DATABRICKS_CONFIG_PROFILE`) to the `experimental.python` subprocess so bundle validate/deploy no longer fails with a multi-profile host ambiguity error when several profiles in `~/.databrickscfg` share the same host. +* Fixed `--force-pull` on `bundle summary` and `bundle open` so the flag bypasses the local state cache and reads state from the workspace. ### Dependency updates -### API Changes +* Bump Go toolchain to 1.25.10 ([#5213](https://github.com/databricks/cli/pull/5213)). diff --git a/NOTICE b/NOTICE index 50bf0eec278..2c90b58f2d3 100644 --- a/NOTICE +++ b/NOTICE @@ -17,11 +17,11 @@ Copyright (c) 2011-2019 Canonical Ltd Copyright (c) 2006-2011 Kirill Simonov License - https://github.com/yaml/go-yaml/blob/v3/LICENSE -—-- +--- -This software contains code from the following open source projects, licensed under the MPL 2.0 license: +This Software contains code from the following open source projects, licensed under the MPL 2.0 license: -hashicopr/go-version - https://github.com/hashicorp/go-version +hashicorp/go-version - https://github.com/hashicorp/go-version Copyright 2014 HashiCorp, Inc. License - https://github.com/hashicorp/go-version/blob/main/LICENSE @@ -29,9 +29,9 @@ hashicorp/hc-install - https://github.com/hashicorp/hc-install Copyright 2020 HashiCorp, Inc. License - https://github.com/hashicorp/hc-install/blob/main/LICENSE -hashicopr/terraform-exec - https://github.com/hashicorp/terraform-exec +hashicorp/terraform-exec - https://github.com/hashicorp/terraform-exec Copyright 2020 HashiCorp, Inc. -LIcense - https://github.com/hashicorp/terraform-exec/blob/main/LICENSE +License - https://github.com/hashicorp/terraform-exec/blob/main/LICENSE hashicorp/terraform-json - https://github.com/hashicorp/terraform-json Copyright 2019 HashiCorp, Inc. @@ -43,24 +43,24 @@ License - https://github.com/hashicorp/terraform/blob/v1.5.5/LICENSE --- -This software contains code from the following open source projects, licensed under the BSD (2-clause) license: +This Software contains code from the following open source projects, licensed under the BSD (2-clause) license: pkg/browser - https://github.com/pkg/browser Copyright (c) 2014, Dave Cheney License - https://github.com/pkg/browser/blob/master/LICENSE -gorilla/websocket - github.com/gorilla/websocket +gorilla/websocket - https://github.com/gorilla/websocket Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. License - https://github.com/gorilla/websocket/blob/main/LICENSE --- -This software contains code from the following open source projects, licensed under the BSD (3-clause) license: +This Software contains code from the following open source projects, licensed under the BSD (3-clause) license: spf13/pflag - https://github.com/spf13/pflag Copyright (c) 2012 Alex Ogier. All rights reserved. Copyright (c) 2012 The Go Authors. All rights reserved. -License - https://raw.githubusercontent.com/spf13/pflag/master/LICENSE +License - https://github.com/spf13/pflag/blob/master/LICENSE google/uuid - https://github.com/google/uuid Copyright (c) 2009,2014 Google Inc. All rights reserved. @@ -70,7 +70,52 @@ manifoldco/promptui - https://github.com/manifoldco/promptui Copyright (c) 2017, Arigato Machine Inc. All rights reserved. License - https://github.com/manifoldco/promptui/blob/master/LICENSE.md -—-- +hexops/gotextdiff - https://github.com/hexops/gotextdiff +Copyright (c) 2009 The Go Authors. All rights reserved. +License - https://github.com/hexops/gotextdiff/blob/main/LICENSE + +dario.cat/mergo - https://github.com/darccio/mergo +Copyright (c) 2013 Dario Castañé. All rights reserved. +Copyright (c) 2012 The Go Authors. All rights reserved. +License - https://github.com/darccio/mergo/blob/master/LICENSE + +palantir/pkg - https://github.com/palantir/pkg +Copyright (c) 2016, Palantir Technologies, Inc. +License - https://github.com/palantir/pkg/blob/master/LICENSE + +quasilyte/go-ruleguard - https://github.com/quasilyte/go-ruleguard +Copyright (c) 2022, Iskander (Alex) Sharipov / quasilyte +License - https://github.com/quasilyte/go-ruleguard/blob/master/LICENSE + +tailscale/hujson - https://github.com/tailscale/hujson +Copyright (c) 2019 Tailscale Inc. All rights reserved. +License - https://github.com/tailscale/hujson/blob/master/LICENSE + +golang.org/x/crypto - https://github.com/golang/crypto +Copyright 2009 The Go Authors. +License - https://github.com/golang/crypto/blob/master/LICENSE + +golang.org/x/mod - https://github.com/golang/mod +Copyright 2009 The Go Authors. +License - https://github.com/golang/mod/blob/master/LICENSE + +golang.org/x/oauth2 - https://github.com/golang/oauth2 +Copyright 2009 The Go Authors. +License - https://github.com/golang/oauth2/blob/master/LICENSE + +golang.org/x/sync - https://github.com/golang/sync +Copyright 2009 The Go Authors. +License - https://github.com/golang/sync/blob/master/LICENSE + +golang.org/x/sys - https://github.com/golang/sys +Copyright 2009 The Go Authors. +License - https://github.com/golang/sys/blob/master/LICENSE + +golang.org/x/text - https://github.com/golang/text +Copyright 2009 The Go Authors. +License - https://github.com/golang/text/blob/master/LICENSE + +--- This Software contains code from the following open source projects, licensed under the MIT license: @@ -78,13 +123,17 @@ google/jsonschema-go - https://github.com/google/jsonschema-go Copyright 2025 Google LLC License - https://github.com/google/jsonschema-go/blob/main/LICENSE +jackc/pgx - https://github.com/jackc/pgx +Copyright (c) 2013-2021 Jack Christensen +License - https://github.com/jackc/pgx/blob/master/LICENSE + charmbracelet/bubbles - https://github.com/charmbracelet/bubbles Copyright (c) 2020-2025 Charmbracelet, Inc License - https://github.com/charmbracelet/bubbles/blob/master/LICENSE charmbracelet/bubbletea - https://github.com/charmbracelet/bubbletea Copyright (c) 2020-2025 Charmbracelet, Inc -License - https://github.com/charmbracelet/bubbletea/blob/master/LICENSE +License - https://github.com/charmbracelet/bubbletea/blob/main/LICENSE charmbracelet/huh - https://github.com/charmbracelet/huh Copyright (c) 2023 Charmbracelet, Inc. @@ -94,57 +143,31 @@ charmbracelet/lipgloss - https://github.com/charmbracelet/lipgloss Copyright (c) 2021-2025 Charmbracelet, Inc License - https://github.com/charmbracelet/lipgloss/blob/master/LICENSE -fatih/color - https://github.com/fatih/color -Copyright (c) 2013 Fatih Arslan -License - https://github.com/fatih/color/blob/main/LICENSE.md - Masterminds/semver - https://github.com/Masterminds/semver Copyright (C) 2014-2019, Matt Butcher and Matt Farina License - https://github.com/Masterminds/semver/blob/master/LICENSE.txt mattn/go-isatty - https://github.com/mattn/go-isatty Copyright (c) Yasuhiro MATSUMOTO -https://github.com/mattn/go-isatty/blob/master/LICENSE - -nwidger/jsoncolor - https://github.com/nwidger/jsoncolor -Copyright (c) 2016 Niels Widger -License - https://github.com/nwidger/jsoncolor/blob/master/LICENSE +License - https://github.com/mattn/go-isatty/blob/master/LICENSE sabhiram/go-gitignore - https://github.com/sabhiram/go-gitignore Copyright (c) 2015 Shaba Abhiram License - https://github.com/sabhiram/go-gitignore/blob/master/LICENSE - stretchr/testify - https://github.com/stretchr/testify Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors. License - https://github.com/stretchr/testify/blob/master/LICENSE -whilp/git-urls - https://github.com/whilp/git-urls -Copyright (c) 2020 Will Maier -License - https://github.com/whilp/git-urls/blob/master/LICENSE - -github.com/wI2L/jsondiff v0.6.1 -Copyright (c) 2020-2024 William Poussier -License - https://github.com/wI2L/jsondiff/blob/master/LICENSE - -https://github.com/hexops/gotextdiff -Copyright (c) 2009 The Go Authors. All rights reserved. -License - https://github.com/hexops/gotextdiff/blob/main/LICENSE - -https://github.com/BurntSushi/toml +BurntSushi/toml - https://github.com/BurntSushi/toml Copyright (c) 2013 TOML authors -https://github.com/BurntSushi/toml/blob/master/COPYING - -dario.cat/mergo -Copyright (c) 2013 Dario Castañé. All rights reserved. -Copyright (c) 2012 The Go Authors. All rights reserved. -https://github.com/darccio/mergo/blob/master/LICENSE - -https://github.com/gorilla/mux -Copyright (c) 2023 The Gorilla Authors. All rights reserved. -https://github.com/gorilla/mux/blob/main/LICENSE +License - https://github.com/BurntSushi/toml/blob/master/COPYING go-yaml/yaml - https://github.com/yaml/go-yaml Copyright (c) 2011-2019 Canonical Ltd Copyright (c) 2006-2011 Kirill Simonov License - https://github.com/yaml/go-yaml/blob/v3/LICENSE + +zalando/go-keyring - https://github.com/zalando/go-keyring +Copyright (c) 2016 Zalando SE +License - https://github.com/zalando/go-keyring/blob/master/LICENSE diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 00000000000..38f690bd56a --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,933 @@ +version: '3' + +vars: + # Absolute path so tasks with `dir:` (lint-go-tools, lint-go-codegen) can use it. + GO_TOOL: go tool -modfile={{.ROOT_DIR}}/tools/go.mod + EXE_EXT: '{{if eq OS "windows"}}.exe{{end}}' + TEST_PACKAGES: ./acceptance/internal ./libs/... ./internal/... ./cmd/... ./bundle/... ./experimental/ssh/... . + ACCEPTANCE_TEST_FILTER: "" + # Single brace-expansion glob covering every //go:embed target in the repo, + # computed by grepping `//go:embed` directives. Evaluated lazily by Task so + # tasks that don't reference it pay nothing. testdata/ dirs are covered by + # a separate static `**/testdata/**` glob, not this script. + # Limitation: git grep only scans tracked files; new //go:embed directives in + # untracked files are missed until the file is staged or committed. + EMBED_SOURCES: + sh: 'python3 tools/list_embeds.py' + +# pydabs-* tasks live in python/Taskfile.yml so `task pydabs-foo` works when +# run from python/. Flattened so they keep their `pydabs-` names at the root. +includes: + pydabs: + taskfile: ./python/Taskfile.yml + dir: ./python + flatten: true + excludes: [default] + +tasks: + default: + desc: Quick dev loop (checks, formatters, incremental lint, tests). Use `full` for non-incremental linters. + cmds: + - task: checks + - task: fmt-q + - task: lint-q + - task: test-unit + # test-update regenerates golden files so the loop ends with a clean tree + # rather than a diff. Intentional: the loop is for local development, not CI. + - task: test-update + + full: + desc: More complete dev loop (full rather than incremental formatters and linters) + cmds: + - task: checks + - task: fmt + - task: lint + - task: test-unit + # See comment in `default` above. + - task: test-update + + all: + desc: Run regeneration (except for genkit), all checks, lints and test updates + cmds: + # Skips generate-genkit (expensive, requires universe checkout, outputs are + # committed). Run `./task generate` explicitly when codegen inputs change. + - task: generate-refschema + - task: generate-schema + - task: generate-schema-docs + - task: generate-validation + - task: generate-docs + - task: generate-direct + - task: pydabs-codegen + - task: pydabs-lint + - task: pydabs-test + - task: checks + - task: fmt + - task: lint + - task: test-update-all + + # --- Linting --- + # + # Naming convention: the plain name is the full variant (default). Quick + # / incremental variants carry a `-q` suffix on the top-level namespace + # (e.g. `lint-q-go-root`). `./task` (default) uses the -q variants for + # speed; `./task all` uses the full variants. + + lint: + desc: Lint all Go files (root + tools + codegen modules) + cmds: + - task: lint-go + + lint-q: + desc: Lint changed Go files in root module (diff vs main, with --fix) + cmds: + - task: lint-q-go-root + + # `golangci-lint run` typechecks, so it stops at go.mod boundaries. We have + # one task per Go module; `lint-go` composes them to cover the whole repo. + # Children run in parallel — each uses its own TMPDIR (set inline on the + # golangci-lint command below) to avoid the shared /tmp lock that serializes + # concurrent golangci-lint invocations. This matters in two scenarios: + # 1. siblings of `lint-go` running in parallel on subprojects here, and + # 2. `lint-go` invocations in sibling worktrees running at the same time. + # The TMPDIR must live under the repo but NOT equal {{.ROOT_DIR}} itself + # (the Go toolchain refuses a go.mod inside os.TempDir, which would break + # golangci-lint's typechecker). + lint-go: + desc: Lint Go files across all modules (root, tools, codegen) + deps: ['lint-go-root', 'lint-go-tools', 'lint-go-codegen'] + + lint-go-root: + desc: Lint Go files in the root module + vars: + TMPDIR: '{{.ROOT_DIR}}/.tmp/golangci-lint-root' + sources: &ROOT_LINT_SOURCES + - "**/*.go" + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - .golangci.yaml + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + cmds: + - cmd: mkdir -p "{{.TMPDIR}}" + silent: true + - TMPDIR="{{.TMPDIR}}" {{.GO_TOOL}} golangci-lint run ./... + + lint-go-tools: + desc: Lint Go files in tools/ module + dir: tools + vars: + TMPDIR: '{{.ROOT_DIR}}/.tmp/golangci-lint-tools' + sources: + - "**/*.go" + - '{{.ROOT_DIR}}/.golangci.yaml' + - go.mod + - go.sum + cmds: + - cmd: mkdir -p "{{.TMPDIR}}" + silent: true + # gocritic is disabled because root's ruleguard rules path is cwd-relative + # and cannot be resolved from this nested module. + - TMPDIR="{{.TMPDIR}}" {{.GO_TOOL}} golangci-lint run --disable gocritic ./... + + lint-go-codegen: + desc: Lint Go files in bundle/internal/tf/codegen module + dir: bundle/internal/tf/codegen + vars: + TMPDIR: '{{.ROOT_DIR}}/.tmp/golangci-lint-codegen' + sources: + - "**/*.go" + - '{{.ROOT_DIR}}/.golangci.yaml' + - go.mod + - go.sum + cmds: + - cmd: mkdir -p "{{.TMPDIR}}" + silent: true + # gocritic is disabled because root's ruleguard rules path is cwd-relative + # and cannot be resolved from this nested module. + - TMPDIR="{{.TMPDIR}}" {{.GO_TOOL}} golangci-lint run --disable gocritic ./... + + lint-q-go-root: + desc: Lint changed Go files in root module (diff vs main, with --fix). Does not check tools/ or bundle/internal/tf/codegen — use `lint` for full coverage. + vars: + TMPDIR: '{{.ROOT_DIR}}/.tmp/golangci-lint-root' + sources: + - "**/*.go" + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - .golangci.yaml + - go.mod + - go.sum + - tools/lintdiff.py + - "{{.EMBED_SOURCES}}" + - "**/testdata/**" + cmds: + - cmd: mkdir -p "{{.TMPDIR}}" + silent: true + - TMPDIR="{{.TMPDIR}}" ./tools/lintdiff.py {{.GO_TOOL}} golangci-lint run --fix + + # --- Formatting --- + + fmt: + desc: Format all files (Python, Go, YAML) + deps: ['fmt-python', 'fmt-go', 'fmt-yaml'] + + fmt-q: + desc: Format changed files (Python, incremental Go, YAML) + deps: ['fmt-python', 'fmt-q-go', 'fmt-yaml'] + + fmt-python: + desc: Format Python files + sources: + - "**/*.py" + cmds: + # Pinned to match the version used by the `ruff format --check` step in + # .github/workflows/check.yml — newer ruff versions reformat files that + # CI considers already formatted. + - "uvx ruff@0.9.1 format -n" + + # `golangci-lint fmt` walks the filesystem and doesn't typecheck, so it + # formats files across all nested modules (tools/, bundle/internal/tf/codegen/) + # in a single invocation. + fmt-go: + desc: Format all Go files + sources: &FMT_GO_SOURCES + - "**/*.go" + - .golangci.yaml + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + cmds: + - "{{.GO_TOOL}} golangci-lint fmt" + + fmt-q-go: + desc: Format changed Go files (diff vs main) + sources: + - "**/*.go" + - .golangci.yaml + - go.mod + - go.sum + - tools/lintdiff.py + - "{{.EMBED_SOURCES}}" + cmds: + - "./tools/lintdiff.py {{.GO_TOOL}} golangci-lint fmt" + + fmt-yaml: + desc: Format YAML files + sources: + - "**/*.yml" + - "**/*.yaml" + - yamlfmt.yml + - tools/go.mod + - tools/go.sum + cmds: + - "{{.GO_TOOL}} yamlfmt ." + + # --- Code checks --- + + tidy: + desc: Run go mod tidy across all Go modules (root, tools, codegen) + deps: ['tidy-root', 'tidy-tools', 'tidy-codegen'] + + tidy-root: + desc: Run go mod tidy in root module + sources: + - go.mod + - go.sum + - "**/*.go" + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - "{{.EMBED_SOURCES}}" + cmds: + - "go mod tidy" + + tidy-tools: + desc: Run go mod tidy in tools/ module + dir: tools + sources: + - go.mod + - go.sum + - "**/*.go" + cmds: + - "go mod tidy" + + tidy-codegen: + desc: Run go mod tidy in bundle/internal/tf/codegen module + dir: bundle/internal/tf/codegen + sources: + - go.mod + - go.sum + - "**/*.go" + cmds: + - "go mod tidy" + + ws: + desc: Fix whitespace issues + cmds: + - "./tools/validate_whitespace.py --fix" + + links: + desc: Update GitHub links in docs + sources: + - "**/*.md" + - tools/update_github_links.py + cmds: + - "./tools/update_github_links.py" + + deadcode: + desc: Check for dead code + sources: + - "**/*.go" + - go.mod + - go.sum + cmds: + - ./tools/check_deadcode.py + + checks: + desc: Run quick checks (tidy, whitespace, links, deadcode) + # Sequential: `tidy` rewrites go.mod/go.sum and any future tidy work + # touching more paths should not race with whitespace/link scanners. + cmds: + - task: tidy + - task: ws + - task: links + - task: deadcode + + install-pythons: + desc: Install Python 3.9-3.13 via uv + cmds: + - "uv python install 3.9 3.10 3.11 3.12 3.13" + + # --- Building --- + + # The root binary only imports bundle/, cmd/, experimental/, internal/, libs/, + # so changes to test-only trees (acceptance/, integration/), separate modules + # (tools/, bundle/internal/tf/codegen/), and _test.go files don't affect the build. + build-yamlfmt: + desc: Build the yamlfmt binary used by acceptance tests + dir: tools + sources: + - go.mod + - go.sum + generates: + - yamlfmt{{.EXE_EXT}} + cmds: + - go build -o yamlfmt{{.EXE_EXT}} github.com/google/yamlfmt/cmd/yamlfmt + + build: + desc: Build the CLI binary + deps: ['tidy-root'] + sources: &BUILD_SOURCES + - "**/*.go" + - exclude: "**/*_test.go" + - exclude: acceptance/** + - exclude: integration/** + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - cli + cmds: + - "go build" + + snapshot: + desc: Build snapshot binary to .databricks/databricks + sources: *BUILD_SOURCES + generates: + - .databricks/databricks + cmds: + - "go build -o .databricks/databricks" + + snapshot-release: + desc: Build release binaries locally without uploading + # Same as BUILD_SOURCES + .goreleaser.yaml (list concat is not expressible + # via YAML anchor alone, so the shared block is duplicated here). + sources: + - "**/*.go" + - exclude: "**/*_test.go" + - exclude: acceptance/** + - exclude: integration/** + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - go.mod + - go.sum + - .goreleaser.yaml + - .github/scripts/sign-windows.sh + - "{{.EMBED_SOURCES}}" + generates: + - dist/** + cmds: + - "goreleaser release --clean --skip docker --snapshot" + + # --- Testing --- + + test: + desc: Run unit and acceptance tests + deps: + - task: test-unit + - task: test-acc + vars: + ACCEPTANCE_TEST_FILTER: "{{.ACCEPTANCE_TEST_FILTER}}" + sources: + - test-output-unit.json + - test-output-acc.json + generates: + - test-output.json + cmds: + - cat test-output-unit.json test-output-acc.json > test-output.json + + test-unit: + desc: Run unit tests across all Go modules (root, tools, codegen) + deps: ['test-unit-root', 'test-unit-tools', 'test-unit-codegen'] + sources: + - test-output-unit-root.json + - tools/test-output-unit-tools.json + - bundle/internal/tf/codegen/test-output-unit-codegen.json + generates: + - test-output-unit.json + cmds: + - cat test-output-unit-root.json tools/test-output-unit-tools.json bundle/internal/tf/codegen/test-output-unit-codegen.json > test-output-unit.json + + test-unit-root: + desc: Run unit tests in root module + sources: + - "**/*.go" + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - exclude: integration/** + - exclude: acceptance/** + - acceptance/internal/** + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + - "**/testdata/**" + # libs/patchwheel passes these to uv --find-links. + - libs/vendored_py_packages/** + # libs/git tests load .gitignore rules from the real repo. + - "**/.gitignore" + # bundle/tests/** are fixture bundles loaded by sibling *_test.go files. + - bundle/tests/** + # internal/build reads these at test time. + - NOTICE + - .codegen/_openapi_sha + # bundle/internal/schema TestRequiredAnnotationsForNewFields reads these. + - bundle/internal/schema/annotations*.yml + generates: + - test-output-unit-root.json + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-unit-root.json \ + --rerun-fails \ + --packages "{{.TEST_PACKAGES}}" \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + + test-unit-tools: + desc: Run unit tests in tools/ module + dir: tools + sources: + - "**/*.go" + - go.mod + - go.sum + # testmask/targets_test.go reads ../../Taskfile.yml + - ../Taskfile.yml + generates: + # Stays inside the task's `dir:` because gotestsum resolves --jsonfile + # from each package's binary cwd (not gotestsum's cwd) — `../` paths + # land at unpredictable depths. Parent test-unit reads from this path. + - test-output-unit-tools.json + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-unit-tools.json \ + --rerun-fails \ + --packages ./... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + + test-unit-codegen: + desc: Run unit tests in bundle/internal/tf/codegen module + dir: bundle/internal/tf/codegen + sources: + - "**/*.go" + - go.mod + - go.sum + generates: + # See comment on test-unit-tools' generates. + - test-output-unit-codegen.json + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-unit-codegen.json \ + --rerun-fails \ + --packages ./... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + + test-acc: + desc: Run acceptance tests + # Sources mirror `build` (acceptance_test.go builds the CLI in-process via BuildCLI) + # plus acceptance/**. For test-acc the checked-in out.* files are golden inputs: + # changing them must re-run the test. test-update* excludes out.* because they + # are outputs there — see &ACC_SOURCES_UPDATE below. + sources: + - "**/*.go" + - exclude: "**/*_test.go" + - exclude: integration/** + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + # acceptance/install_terraform.py parses the provider version from this file. + - bundle/internal/tf/codegen/schema/version.go + - acceptance/** + # Pydabs wheel is built in-process by TestInprocessMode (cd python && uv build). + - python/** + - libs/vendored_py_packages/** + # TestInprocessMode builds yamlfmt via tools/go.mod. + - tools/go.mod + - tools/go.sum + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - test-output-acc.json + # Materialized test config is rewritten on every run (acceptance_test.go). + # Other out.* files are read-only inputs in non-update mode — see test-update. + - acceptance/**/out.test.toml + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-acc.json \ + --rerun-fails \ + --packages ./acceptance/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m}{{if .ACCEPTANCE_TEST_FILTER}} -run "{{.ACCEPTANCE_TEST_FILTER}}"{{end}} + + test-update: + desc: Update acceptance test output (local) + # Excludes out* because the task rewrites them; keeping them in sources would + # invalidate the checksum and force a re-run on every invocation. + # Note: output.txt / output.*.txt are outputs here (Phase 0 regenerates them + # so Phase 1 tests can read the fresh versions via $TESTDIR) — not inputs. + sources: &ACC_SOURCES_UPDATE + - "**/*.go" + - exclude: "**/*_test.go" + - exclude: integration/** + - exclude: tools/** + - exclude: bundle/internal/tf/codegen/** + - bundle/internal/tf/codegen/schema/version.go + - acceptance/** + - python/** + - libs/vendored_py_packages/** + - tools/go.mod + - tools/go.sum + - exclude: acceptance/**/out* + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: &ACC_GENERATES_UPDATE + - acceptance/**/out* + cmds: + - "go test ./acceptance -run '^TestAccept$' -update -timeout=${LOCAL_TIMEOUT:-30m}" + + test-update-templates: + desc: Update acceptance test template output + sources: *ACC_SOURCES_UPDATE + generates: *ACC_GENERATES_UPDATE + cmds: + - "go test ./acceptance -run '^TestAccept/bundle/templates' -update -timeout=${LOCAL_TIMEOUT:-30m}" + + test-update-aws: + desc: Update acceptance test output (integration, requires deco access) + sources: *ACC_SOURCES_UPDATE + generates: *ACC_GENERATES_UPDATE + cmds: + - "deco env run -i -n aws-prod-ucws -- env DATABRICKS_TEST_SKIPLOCAL=1 go test ./acceptance -run ^TestAccept$ -update -timeout=1h -v" + + test-update-all: + desc: Update all acceptance test outputs + # Sequential: both tasks overwrite the same acceptance output files. + cmds: + - task: test-update + - task: test-update-aws + + slowest: + desc: Show 50 slowest tests from last run + cmds: + - "{{.GO_TOOL}} gotestsum tool slowest --jsonfile test-output.json --threshold 1s --num 50" + + cover: + desc: Run tests with coverage + generates: + - test-output.json + cmds: + - rm -fr ./acceptance/build/cover/ + - | + VERBOSE_TEST=1 {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-unit.json \ + --rerun-fails \ + --packages "{{.TEST_PACKAGES}}" \ + -- -coverprofile=coverage.txt -timeout=${LOCAL_TIMEOUT:-30m} + - | + VERBOSE_TEST=1 CLI_GOCOVERDIR=build/cover {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --jsonfile test-output-acc.json \ + --rerun-fails \ + --packages ./acceptance/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m}{{if .ACCEPTANCE_TEST_FILTER}} -run "{{.ACCEPTANCE_TEST_FILTER}}"{{end}} + - cat test-output-unit.json test-output-acc.json > test-output.json + - rm -fr ./acceptance/build/cover-merged/ + - mkdir -p acceptance/build/cover-merged/ + - "go tool covdata merge -i $(printf '%s,' acceptance/build/cover/* | sed 's/,$//') -o acceptance/build/cover-merged/" + - go tool covdata textfmt -i acceptance/build/cover-merged -o coverage-acceptance.txt + + showcover: + desc: Open unit test coverage report in browser + cmds: + - go tool cover -html=coverage.txt + + showcover-acc: + desc: Open acceptance test coverage report in browser + cmds: + - go tool cover -html=coverage-acceptance.txt + + # --- Specialized test suites --- + + # The `sources:` on each test:* subproject target is the single source of truth for: + # 1. Taskfile's own checksum-based caching (skip re-run if nothing changed) + # 2. CI triggering — tools/testmask reads these sources to decide which CI jobs to run + # Keep patterns narrow and specific. Changes to files outside these paths trigger the + # generic `test` target (the catch-all) instead. + + test-exp-aitools: + desc: Run experimental aitools unit and acceptance tests + sources: + - experimental/aitools/** + - acceptance/apps/** + - "{{.EMBED_SOURCES}}" + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./experimental/aitools/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./acceptance/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} -run "TestAccept/apps" + + test-exp-ssh: + desc: Run experimental SSH unit and acceptance tests + sources: + - experimental/ssh/** + - acceptance/ssh/** + - "{{.EMBED_SOURCES}}" + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./experimental/ssh/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./acceptance/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} -run "TestAccept/ssh" + + test-pipelines: + desc: Run pipelines unit and acceptance tests + sources: + - cmd/pipelines/** + - acceptance/pipelines/** + cmds: + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./cmd/pipelines/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} + - | + {{.GO_TOOL}} gotestsum \ + --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ + --no-summary=skipped \ + --packages ./acceptance/... \ + -- -timeout=${LOCAL_TIMEOUT:-30m} -run "TestAccept/pipelines" + + # --- Integration tests --- + + integration: + desc: Run integration tests (requires Databricks workspace) + deps: [install-pythons] + cmds: + - | + go run -modfile=tools/go.mod ./tools/testrunner/main.go \ + {{.GO_TOOL}} gotestsum \ + --format github-actions \ + --rerun-fails \ + --jsonfile output.json \ + --packages "./acceptance ./integration/..." \ + -- -parallel 4 -timeout=2h + + integration-short: + desc: Run short integration tests + deps: [install-pythons] + cmds: + - | + DATABRICKS_TEST_SKIPLOCAL=1 VERBOSE_TEST=1 \ + go run -modfile=tools/go.mod ./tools/testrunner/main.go \ + {{.GO_TOOL}} gotestsum \ + --format github-actions \ + --rerun-fails \ + --jsonfile output.json \ + --packages "./acceptance ./integration/..." \ + -- -parallel 4 -timeout=2h -short + + dbr-integration: + desc: Run DBR acceptance tests on Databricks Runtime + deps: [install-pythons] + cmds: + - "DBR_ENABLED=true go test -v -timeout 4h -run TestDbrAcceptance$ ./acceptance" + + dbr-test: + desc: Run DBR tests via deco env (requires deco + aws-prod-ucws access) + cmds: + - "deco env run -i -n aws-prod-ucws -- ./task dbr-integration" + + # --- Code generation --- + # + # Each generator declares tight `sources:` so Task's checksum cache re-runs only + # when inputs actually change. The reflection-based generators (refschema, + # schema, schema-docs, docs, validation) pick up SDK type changes via go.mod / + # go.sum. The aggregator `generate` orchestrates all of them; individual tasks + # can be invoked standalone. + + generate: + desc: Run all generators (genkit, refschema, schema, docs, validation, direct, pydabs) + cmds: + # Runs first: regenerates CLI command stubs from the OpenAPI spec at + # .codegen/_openapi_sha. SDK version bumps (go.mod/go.sum) are a manual + # step outside this task; TestConsistentDatabricksSdkVersion (run inside + # generate-genkit) asserts the two stay in sync. + - task: generate-genkit + # Refreshes acceptance/bundle/refschema/out.fields.txt, which feeds + # generate-direct-apitypes and generate-direct-resources below. + - task: generate-refschema + - task: generate-schema + - task: generate-schema-docs + - task: generate-validation + - task: generate-docs + - task: generate-direct + - task: pydabs-codegen + + # Drives genkit from a universe checkout. Genkit writes CLI command files into + # cmd/workspace and cmd/account, refreshes .gitattributes and + # .codegen/_openapi_sha, and emits .github/workflows/tagging.yml + + # tagging.py (+ lock) in the repo root plus a next-changelog workflow we + # don't keep. Genkit does NOT modify go.mod/go.sum — SDK bumps are a manual + # `go get` step before running this task. The cmds below then post-process + # genkit's output: assert the SDK version matches the OpenAPI SHA, drop the + # next-changelog workflow, relocate tagging.py under internal/genkit/ and + # rewrite the tagging.yml workflow to match, then yamlfmt + whitespace fix + # so the tree is clean. + generate-genkit: + desc: Run genkit to generate CLI commands and tagging workflow (requires universe repo) + sources: + - .codegen/_openapi_sha + - .gitattributes.manual + - go.mod + - go.sum + vars: + UNIVERSE_DIR: + sh: echo "${UNIVERSE_DIR:-$HOME/universe}" + cmds: + - | + echo "Checking out universe at SHA: $(cat .codegen/_openapi_sha)" + cd {{.UNIVERSE_DIR}} + if ! git cat-file -e $(cat {{.ROOT_DIR}}/.codegen/_openapi_sha) 2>/dev/null; then + git fetch --filter=blob:none origin master + fi + git checkout $(cat {{.ROOT_DIR}}/.codegen/_openapi_sha) + - echo "Building genkit..." + - cd {{.UNIVERSE_DIR}} && bazel build //openapi/genkit + - echo "Generating CLI code..." + - "{{.UNIVERSE_DIR}}/bazel-bin/openapi/genkit/genkit_/genkit update-sdk" + - "cat .gitattributes.manual .gitattributes > .gitattributes.tmp && mv .gitattributes.tmp .gitattributes" + - go test -timeout 240s -run TestConsistentDatabricksSdkVersion github.com/databricks/cli/internal/build + - rm .github/workflows/next-changelog.yml + - mv tagging.py internal/genkit/tagging.py + - mv tagging.py.lock internal/genkit/tagging.py.lock + - | + if [ "$(uname)" = "Darwin" ]; then + sed -i '' 's|tagging.py|internal/genkit/tagging.py|g' .github/workflows/tagging.yml + else + sed -i 's|tagging.py|internal/genkit/tagging.py|g' .github/workflows/tagging.yml + fi + - "{{.GO_TOOL}} yamlfmt .github/workflows/tagging.yml" + - task: ws + + # Refreshes out.fields.txt, which records the field paths / types emitted by + # `bundle debug refschema` (see cmd/bundle/debug/refschema.go). It reflects + # over types reachable from bundle/, so any bundle change or SDK bump (go.mod + # / go.sum) can affect the output. + generate-refschema: + desc: Regenerate acceptance/bundle/refschema/out.fields.txt + sources: + - bundle/**/*.go + - exclude: bundle/**/*_test.go + - acceptance/bundle/refschema/script + - acceptance/bundle/refschema/test.toml + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - acceptance/bundle/refschema/out.fields.txt + cmds: + - go test ./acceptance -run TestAccept/bundle/refschema -update &> /dev/null + + generate-schema: + desc: Generate bundle JSON schema + sources: &SCHEMA_SOURCES + - "**/*.go" + - bundle/internal/schema/annotations*.yml + - exclude: "**/*_test.go" + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - bundle/schema/jsonschema.json + - bundle/internal/schema/annotations.yml + cmds: + - "go run ./bundle/internal/schema ./bundle/internal/schema ./bundle/schema/jsonschema.json" + + generate-schema-docs: + desc: Generate bundle JSON schema for documentation + sources: *SCHEMA_SOURCES + generates: + - bundle/schema/jsonschema_for_docs.json + - bundle/internal/schema/annotations.yml + cmds: + # since_version.go reads `git tag --list 'v*'` to compute sinceVersion + # annotations. Without tags (e.g. shallow clone), those annotations are + # silently dropped from the output. Restore the fetch that lived in the + # old tools/post-generate.sh. + - git fetch origin 'refs/tags/v*:refs/tags/v*' + - "go run ./bundle/internal/schema ./bundle/internal/schema ./bundle/schema/jsonschema_for_docs.json --docs" + + generate-docs: + desc: Generate bundle documentation + sources: + - "**/*.go" + - bundle/docsgen/templates/** + - bundle/internal/schema/annotations*.yml + - exclude: "**/*_test.go" + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - bundle/docsgen/output/reference.md + - bundle/docsgen/output/resources.md + cmds: + - "go run ./bundle/docsgen ./bundle/internal/schema ./bundle/docsgen" + + generate-validation: + desc: Generate enum and required field validation code + sources: + - "**/*.go" + - exclude: "**/*_test.go" + - go.mod + - go.sum + - "{{.EMBED_SOURCES}}" + generates: + - bundle/internal/validation/generated/**/*.go + cmds: + - "sh -c 'go run ./bundle/internal/validation/. && gofmt -w -s ./bundle/internal/validation/generated'" + + generate-direct: + desc: Generate direct engine config (apitypes + resources) + deps: ['generate-direct-apitypes', 'generate-direct-resources'] + + generate-direct-apitypes: + desc: Generate direct engine API types YAML + deps: ['generate-openapi-json'] + sources: + - bundle/direct/tools/generate_apitypes.py + - .codegen/openapi.json + - acceptance/bundle/refschema/out.fields.txt + generates: + - bundle/direct/dresources/apitypes.generated.yml + cmds: + - "sh -c 'python3 bundle/direct/tools/generate_apitypes.py .codegen/openapi.json acceptance/bundle/refschema/out.fields.txt > bundle/direct/dresources/apitypes.generated.yml'" + + generate-direct-resources: + desc: Generate direct engine resources YAML + deps: ['generate-direct-apitypes'] + sources: + - bundle/direct/tools/generate_resources.py + - .codegen/openapi.json + - bundle/direct/dresources/apitypes.generated.yml + - bundle/direct/dresources/apitypes.yml + - acceptance/bundle/refschema/out.fields.txt + generates: + - bundle/direct/dresources/resources.generated.yml + cmds: + - "sh -c 'python3 bundle/direct/tools/generate_resources.py .codegen/openapi.json bundle/direct/dresources/apitypes.generated.yml bundle/direct/dresources/apitypes.yml acceptance/bundle/refschema/out.fields.txt > bundle/direct/dresources/resources.generated.yml'" + + generate-openapi-json: + desc: Download OpenAPI spec (triggered by _openapi_sha change) + sources: + - .codegen/_openapi_sha + generates: + - .codegen/openapi.json + cmds: + - "wget -O .codegen/openapi.json.tmp \"https://openapi.dev.databricks.com/$(cat .codegen/_openapi_sha)/specs/all-internal.json\" && mv .codegen/openapi.json.tmp .codegen/openapi.json" + + # pydabs-* tasks are defined in python/Taskfile.yml (included above). + + # --- Benchmarks --- + + bench-1k: + desc: Benchmark with 1000 jobs + cmds: + - 'BENCHMARK_PARAMS="--jobs 1000" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m | tee bench1k.log' + + bench-100: + desc: Benchmark with 100 jobs + cmds: + - 'BENCHMARK_PARAMS="--jobs 100" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m | tee bench100.log' + + bench-10: + desc: Benchmark with 10 jobs (quick) + cmds: + - 'BENCHMARK_PARAMS="--jobs 10" go test ./acceptance -v -tail -run TestAccept/bundle/benchmarks -timeout=120m | tee bench10.log' + + bench-1k-summary: + desc: Run 1k benchmark and print summary + cmds: + - task: bench-1k + - ./tools/bench_parse.py bench1k.log + + bench-100-summary: + desc: Run 100 benchmark and print summary + cmds: + - task: bench-100 + - ./tools/bench_parse.py bench100.log + + bench-10-summary: + desc: Run 10 benchmark and print summary + cmds: + - task: bench-10 + - ./tools/bench_parse.py bench10.log diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 7cbc5d55d80..67ec78ffded 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -10,6 +10,8 @@ import ( "flag" "fmt" "io" + "io/fs" + "maps" "net/http" "os" "os/exec" @@ -17,7 +19,6 @@ import ( "regexp" "runtime" "slices" - "sort" "strconv" "strings" "sync" @@ -32,7 +33,6 @@ import ( "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/testdiff" "github.com/databricks/cli/libs/testserver" - "github.com/databricks/cli/libs/utils" "github.com/stretchr/testify/require" ) @@ -237,7 +237,11 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { execPath = filepath.Join(cwd, "bin", "callserver.py") } else { if UseVersion != "" { - execPath = DownloadCLI(t, buildDir, UseVersion) + version := UseVersion + if version == "latest" { + version = resolveLatestVersion(t, buildDir) + } + execPath = DownloadCLI(t, buildDir, version) } else { execPath = BuildCLI(t, buildDir, coverDir, runtime.GOOS, runtime.GOARCH) } @@ -275,8 +279,9 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { t.Setenv("UV_CACHE_DIR", uvCache) // UV_CACHE_DIR only applies to packages but not Python installations. - // UV_PYTHON_INSTALL_DIR ensures we cache Python downloads as well - uvInstall := filepath.Join(uvCache, "python_installs") + // UV_PYTHON_INSTALL_DIR points to the actual managed Python directory so + // uv finds pre-installed versions without falling back to system PATH search. + uvInstall := getUVPythonInstallDir(t) t.Setenv("UV_PYTHON_INSTALL_DIR", uvInstall) cloudEnv := os.Getenv("CLOUD_ENV") @@ -367,9 +372,11 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { // Generate materialized config for this test. // We do this before skipping the test, so the configs are generated for all tests. - materializedConfig, err := internal.GenerateMaterializedConfig(config) - require.NoError(t, err) - testutil.WriteFile(t, filepath.Join(dir, internal.MaterializedConfigFile), materializedConfig) + materializedConfig := internal.GenerateMaterializedConfig(&config) + outPath := filepath.Join(dir, internal.MaterializedConfigFile) + if existing, _ := os.ReadFile(outPath); string(existing) != materializedConfig { + testutil.WriteFile(t, outPath, materializedConfig) + } // If only regenerating out.test.toml, skip the actual test execution if OnlyOutTestToml { @@ -486,7 +493,7 @@ func getTests(t *testing.T) []string { }) require.NoError(t, err) - sort.Strings(testDirs) + slices.Sort(testDirs) return testDirs } @@ -512,10 +519,6 @@ func getSkipReason(config *internal.TestConfig, configPath string) string { return "Disabled because RunsOnDbr is not set in " + configPath } - if isTruePtr(config.Slow) && testing.Short() { - return "Disabled via Slow setting in " + configPath - } - isEnabled, isPresent := config.GOOS[runtime.GOOS] if isPresent && !isEnabled { return fmt.Sprintf("Disabled via GOOS.%s setting in %s", runtime.GOOS, configPath) @@ -609,7 +612,7 @@ func runTest(t *testing.T, if KeepTmp { tempDirBase := filepath.Join(os.TempDir(), "acceptance") _ = os.Mkdir(tempDirBase, 0o755) - tmpDir, err = os.MkdirTemp(tempDirBase, "") + tmpDir, err = os.MkdirTemp(tempDirBase, "") //nolint:usetesting // KeepTmp: dir must persist after test for debugging require.NoError(t, err) t.Logf("Created directory: %s", tmpDir) } else if WorkspaceTmpDir { @@ -818,7 +821,7 @@ func buildTestEnv(configEnv map[string]string, customEnv []string) []string { env := make([]string, 0, len(configEnv)+len(customEnv)) // Add config.Env first (but skip keys that exist in customEnv) - for _, key := range utils.SortedKeys(configEnv) { + for _, key := range slices.Sorted(maps.Keys(configEnv)) { if hasKey(customEnv, key) { continue } @@ -1052,6 +1055,35 @@ func CreateReleaseArtifact(t *testing.T, cwd, releasesDir, coverDir, osName, arc t.Logf("Created %s %s release: %s", osName, arch, zipPath) } +// resolveLatestVersion returns the latest released CLI version (e.g. "0.293.0"), +// using a file-based cache in buildDir valid for 1 hour. +func resolveLatestVersion(t *testing.T, buildDir string) string { + cachePath := filepath.Join(buildDir, "latest_version.txt") + if info, err := os.Stat(cachePath); err == nil && time.Since(info.ModTime()) < time.Hour { + data, err := os.ReadFile(cachePath) + require.NoError(t, err) + if version := strings.TrimSpace(string(data)); version != "" { + return version + } + } + + const url = "https://api.github.com/repos/databricks/cli/releases/latest" + resp, err := http.Get(url) + require.NoError(t, err) + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, "failed to fetch %s: %s", url, resp.Status) + + var release struct { + TagName string `json:"tag_name"` + } + require.NoError(t, json.NewDecoder(resp.Body).Decode(&release)) + version := strings.TrimPrefix(release.TagName, "v") + require.NotEmpty(t, version, "empty tag_name in GitHub latest release response") + + require.NoError(t, os.WriteFile(cachePath, []byte(version), 0o644)) + return version +} + // DownloadCLI downloads a released CLI binary archive for the given version, // extracts the executable, and returns its path. func DownloadCLI(t *testing.T, buildDir, version string) string { @@ -1272,6 +1304,20 @@ func getUVDefaultCacheDir(t *testing.T) string { } } +// getUVPythonInstallDir returns the directory where uv stores managed Python installations. +// Must be called before HOME is overridden in tests, so that uv resolves the real install path. +func getUVPythonInstallDir(t *testing.T) string { + cmd := exec.Command("uv", "python", "dir") + out, err := cmd.Output() + if err != nil { + t.Logf("uv python dir failed: %v; falling back to cache-based path", err) + cacheDir, err2 := os.UserCacheDir() + require.NoError(t, err2) + return filepath.Join(cacheDir, "uv", "python_installs") + } + return strings.TrimSpace(string(out)) +} + func RunCommand(t *testing.T, args []string, dir string, env []string) { start := time.Now() cmd := exec.Command(args[0], args[1:]...) @@ -1462,11 +1508,7 @@ func prepareWheelBuildDirectory(t *testing.T, dir string) string { } func BuildYamlfmt(t *testing.T) { - // Using make here instead of "go build" directly cause it's faster when it's already built - args := []string{ - "make", "-s", "tools/yamlfmt" + exeSuffix, - } - RunCommand(t, args, "..", []string{}) + RunCommand(t, []string{"go", "tool", "-modfile=tools/task/go.mod", "task", "build-yamlfmt"}, "..", []string{}) } // setupTerraform installs terraform and configures environment variables for tests. @@ -1487,12 +1529,12 @@ func setupTerraform(t *testing.T, cwd, buildDir string, repls *testdiff.Replacem func loadUserReplacements(t *testing.T, repls *testdiff.ReplacementsContext, tmpDir string) { b, err := os.ReadFile(filepath.Join(tmpDir, userReplacementsFilename)) - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return } require.NoError(t, err) - lines := strings.Split(string(b), "\n") - for _, line := range lines { + lines := strings.SplitSeq(string(b), "\n") + for line := range lines { line = strings.TrimSpace(line) if len(line) == 0 { continue diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/app/app.py b/acceptance/apps/deploy/bundle-no-args-with-flags/app/app.py new file mode 100644 index 00000000000..3cf9504c98e --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/app/app.py @@ -0,0 +1,2 @@ +# Minimal app for testing +print("Hello from app") diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/databricks.yml b/acceptance/apps/deploy/bundle-no-args-with-flags/databricks.yml new file mode 100644 index 00000000000..a50c8d54aa7 --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/databricks.yml @@ -0,0 +1,8 @@ +bundle: + name: test-bundle + +resources: + apps: + myapp: + name: myapp + source_code_path: ./app diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/out.test.toml b/acceptance/apps/deploy/bundle-no-args-with-flags/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/output.txt b/acceptance/apps/deploy/bundle-no-args-with-flags/output.txt new file mode 100644 index 00000000000..5c655deef1e --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/output.txt @@ -0,0 +1,25 @@ + +>>> [CLI] apps deploy --skip-validation --auto-approve --force-lock --fail-on-active-runs +Deploying project... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! +✓ Getting the status of the app myapp +✓ App is in RUNNING state +✓ App compute is in STOPPED state +✓ Starting the app myapp +✓ App is starting... +✓ App is started! +✓ Deployment succeeded +You can access the app at myapp-123.cloud.databricksapps.com +✔ Deployment complete! + +>>> [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/test-bundle/default + +Deleting files... +Destroy complete! diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/script b/acceptance/apps/deploy/bundle-no-args-with-flags/script new file mode 100644 index 00000000000..559a1859522 --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/script @@ -0,0 +1,6 @@ +# Test: apps deploy in a bundle directory with bundle-style deploy flags +# Expected: flags are accepted and the bundle deploy pipeline completes + +trace $CLI apps deploy --skip-validation --auto-approve --force-lock --fail-on-active-runs + +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/apps/deploy/bundle-no-args-with-flags/test.toml b/acceptance/apps/deploy/bundle-no-args-with-flags/test.toml new file mode 100644 index 00000000000..4f08257f91b --- /dev/null +++ b/acceptance/apps/deploy/bundle-no-args-with-flags/test.toml @@ -0,0 +1,37 @@ +Local = true +Cloud = false + +Ignore = [ + '.databricks', +] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] + +[[Server]] +Pattern = "POST /api/2.0/apps/myapp/deployments" +Response.Body = ''' +{ + "deployment_id": "dep-123", + "source_code_path": "/Workspace/apps/myapp", + "mode": "SNAPSHOT", + "status": { + "state": "SUCCEEDED", + "message": "Deployment succeeded" + } +} +''' + +[[Server]] +Pattern = "GET /api/2.0/apps/myapp/deployments/dep-123" +Response.Body = ''' +{ + "deployment_id": "dep-123", + "source_code_path": "/Workspace/apps/myapp", + "mode": "SNAPSHOT", + "status": { + "state": "SUCCEEDED", + "message": "Deployment succeeded" + } +} +''' diff --git a/acceptance/apps/deploy/bundle-no-args/out.test.toml b/acceptance/apps/deploy/bundle-no-args/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/apps/deploy/bundle-no-args/out.test.toml +++ b/acceptance/apps/deploy/bundle-no-args/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/apps/deploy/bundle-with-appname/out.test.toml b/acceptance/apps/deploy/bundle-with-appname/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/apps/deploy/bundle-with-appname/out.test.toml +++ b/acceptance/apps/deploy/bundle-with-appname/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/apps/deploy/bundle-with-appname/output.txt b/acceptance/apps/deploy/bundle-with-appname/output.txt index ad046cf583c..59e34f264c4 100644 --- a/acceptance/apps/deploy/bundle-with-appname/output.txt +++ b/acceptance/apps/deploy/bundle-with-appname/output.txt @@ -1,11 +1,11 @@ >>> [CLI] apps deploy test-app --no-wait { - "deployment_id":"dep-123", - "mode":"SNAPSHOT", - "source_code_path":"/Workspace/apps/test-app", + "deployment_id": "dep-123", + "mode": "SNAPSHOT", + "source_code_path": "/Workspace/apps/test-app", "status": { - "message":"Deployment pending", - "state":"PENDING" + "message": "Deployment pending", + "state": "PENDING" } } diff --git a/acceptance/apps/deploy/no-bundle-no-args/out.test.toml b/acceptance/apps/deploy/no-bundle-no-args/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/apps/deploy/no-bundle-no-args/out.test.toml +++ b/acceptance/apps/deploy/no-bundle-no-args/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/apps/deploy/no-bundle-with-appname/out.test.toml b/acceptance/apps/deploy/no-bundle-with-appname/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/apps/deploy/no-bundle-with-appname/out.test.toml +++ b/acceptance/apps/deploy/no-bundle-with-appname/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/apps/deploy/no-bundle-with-appname/output.txt b/acceptance/apps/deploy/no-bundle-with-appname/output.txt index ad046cf583c..59e34f264c4 100644 --- a/acceptance/apps/deploy/no-bundle-with-appname/output.txt +++ b/acceptance/apps/deploy/no-bundle-with-appname/output.txt @@ -1,11 +1,11 @@ >>> [CLI] apps deploy test-app --no-wait { - "deployment_id":"dep-123", - "mode":"SNAPSHOT", - "source_code_path":"/Workspace/apps/test-app", + "deployment_id": "dep-123", + "mode": "SNAPSHOT", + "source_code_path": "/Workspace/apps/test-app", "status": { - "message":"Deployment pending", - "state":"PENDING" + "message": "Deployment pending", + "state": "PENDING" } } diff --git a/acceptance/auth/bundle_and_profile/databricks.yml b/acceptance/auth/bundle_and_profile/databricks.yml index 975661395ac..20f9134411e 100644 --- a/acceptance/auth/bundle_and_profile/databricks.yml +++ b/acceptance/auth/bundle_and_profile/databricks.yml @@ -11,4 +11,4 @@ targets: host: $DATABRICKS_HOST prod: workspace: - host: https://bar.com + host: https://bar.test diff --git a/acceptance/auth/bundle_and_profile/out.test.toml b/acceptance/auth/bundle_and_profile/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/auth/bundle_and_profile/out.test.toml +++ b/acceptance/auth/bundle_and_profile/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/bundle_and_profile/output.txt b/acceptance/auth/bundle_and_profile/output.txt index 88deef12567..ce88f0519bf 100644 --- a/acceptance/auth/bundle_and_profile/output.txt +++ b/acceptance/auth/bundle_and_profile/output.txt @@ -13,7 +13,7 @@ === Inside the bundle, profile flag not matching bundle host. Should use profile from the flag and not the bundle. >>> errcode [CLI] current-user me -p profile_name -Warn: Failed to resolve host metadata: (redacted). Falling back to user config. +Warn: [hostmetadata] failed to fetch host metadata for https://non.existing.subdomain.databricks.com, will skip for 1m0s Error: Get "https://non.existing.subdomain.databricks.com/api/2.0/preview/scim/v2/Me": (redacted) Exit code: 1 @@ -73,13 +73,13 @@ Validation OK! === Bundle commands load bundle configuration with -t and -p flag, validation not OK (profile host don't match bundle host) >>> errcode [CLI] bundle validate -t prod -p DEFAULT -Warn: Failed to resolve host metadata: (redacted). Falling back to user config. -Error: cannot resolve bundle auth configuration: the host in the profile ([DATABRICKS_TARGET]) doesn’t match the host configured in the bundle (https://bar.com) +Warn: [hostmetadata] failed to fetch host metadata for https://bar.test, will skip for 1m0s +Error: cannot resolve bundle auth configuration: the host in the profile ([DATABRICKS_TARGET]) doesn’t match the host configured in the bundle (https://bar.test) Name: test-auth Target: prod Workspace: - Host: https://bar.com + Host: https://bar.test Found 1 error diff --git a/acceptance/auth/bundle_and_profile/test.toml b/acceptance/auth/bundle_and_profile/test.toml index 477e83a18db..92458e9d303 100644 --- a/acceptance/auth/bundle_and_profile/test.toml +++ b/acceptance/auth/bundle_and_profile/test.toml @@ -9,10 +9,6 @@ New='DATABRICKS_TARGET' Old='DATABRICKS_URL' New='DATABRICKS_TARGET' -[[Repls]] -Old='Warn: Failed to resolve host metadata: .*\. Falling back to user config\.' -New='Warn: Failed to resolve host metadata: (redacted). Falling back to user config.' - [[Repls]] Old='Get "https://non.existing.subdomain.databricks.com/api/2.0/preview/scim/v2/Me": .*' New='Get "https://non.existing.subdomain.databricks.com/api/2.0/preview/scim/v2/Me": (redacted)' diff --git a/acceptance/auth/credentials/basic/out.test.toml b/acceptance/auth/credentials/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/auth/credentials/basic/out.test.toml +++ b/acceptance/auth/credentials/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/credentials/basic/output.txt b/acceptance/auth/credentials/basic/output.txt index c5747c9e47c..93c6060cffc 100644 --- a/acceptance/auth/credentials/basic/output.txt +++ b/acceptance/auth/credentials/basic/output.txt @@ -1,4 +1,4 @@ { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } diff --git a/acceptance/auth/credentials/oauth/out.test.toml b/acceptance/auth/credentials/oauth/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/auth/credentials/oauth/out.test.toml +++ b/acceptance/auth/credentials/oauth/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/credentials/oauth/output.txt b/acceptance/auth/credentials/oauth/output.txt index c5747c9e47c..93c6060cffc 100644 --- a/acceptance/auth/credentials/oauth/output.txt +++ b/acceptance/auth/credentials/oauth/output.txt @@ -1,4 +1,4 @@ { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } diff --git a/acceptance/auth/credentials/pat/out.test.toml b/acceptance/auth/credentials/pat/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/auth/credentials/pat/out.test.toml +++ b/acceptance/auth/credentials/pat/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/credentials/pat/output.txt b/acceptance/auth/credentials/pat/output.txt index c5747c9e47c..93c6060cffc 100644 --- a/acceptance/auth/credentials/pat/output.txt +++ b/acceptance/auth/credentials/pat/output.txt @@ -1,4 +1,4 @@ { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } diff --git a/acceptance/auth/credentials/unified-host/out.requests.txt b/acceptance/auth/credentials/unified-host/out.requests.txt deleted file mode 100644 index e94814526d8..00000000000 --- a/acceptance/auth/credentials/unified-host/out.requests.txt +++ /dev/null @@ -1,48 +0,0 @@ -{ - "headers": { - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]" - ] - }, - "method": "GET", - "path": "/.well-known/databricks-config" -} -{ - "headers": { - "Authorization": [ - "Bearer dapi-unified-token" - ], - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/current-user_me cmd-exec-id/[UUID] interactive/none auth/pat" - ], - "X-Databricks-Org-Id": [ - "[NUMID]" - ] - }, - "method": "GET", - "path": "/api/2.0/preview/scim/v2/Me" -} -{ - "headers": { - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]" - ] - }, - "method": "GET", - "path": "/.well-known/databricks-config" -} -{ - "headers": { - "Authorization": [ - "Bearer dapi-unified-token" - ], - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/current-user_me cmd-exec-id/[UUID] interactive/none auth/pat" - ], - "X-Databricks-Org-Id": [ - "[NUMID]" - ] - }, - "method": "GET", - "path": "/api/2.0/preview/scim/v2/Me" -} diff --git a/acceptance/auth/credentials/unified-host/out.test.toml b/acceptance/auth/credentials/unified-host/out.test.toml deleted file mode 100644 index d560f1de043..00000000000 --- a/acceptance/auth/credentials/unified-host/out.test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/credentials/unified-host/output.txt b/acceptance/auth/credentials/unified-host/output.txt deleted file mode 100644 index af071887d05..00000000000 --- a/acceptance/auth/credentials/unified-host/output.txt +++ /dev/null @@ -1,12 +0,0 @@ - -=== With workspace_id -{ - "id":"[USERID]", - "userName":"[USERNAME]" -} - -=== Without workspace_id (should error) -{ - "id":"[USERID]", - "userName":"[USERNAME]" -} diff --git a/acceptance/auth/credentials/unified-host/script b/acceptance/auth/credentials/unified-host/script deleted file mode 100644 index f785987219b..00000000000 --- a/acceptance/auth/credentials/unified-host/script +++ /dev/null @@ -1,12 +0,0 @@ -# Test unified host authentication with PAT token -export DATABRICKS_TOKEN=dapi-unified-token -export DATABRICKS_ACCOUNT_ID=test-account-123 -export DATABRICKS_WORKSPACE_ID=1234567890 -export DATABRICKS_EXPERIMENTAL_IS_UNIFIED_HOST=true - -title "With workspace_id\n" -$CLI current-user me - -title "Without workspace_id (should error)\n" -unset DATABRICKS_WORKSPACE_ID -errcode $CLI current-user me diff --git a/acceptance/auth/credentials/unified-host/test.toml b/acceptance/auth/credentials/unified-host/test.toml deleted file mode 100644 index fd0cd964213..00000000000 --- a/acceptance/auth/credentials/unified-host/test.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Test unified host authentication with PAT tokens -# Include X-Databricks-Org-Id header to verify workspace_id is sent -IncludeRequestHeaders = ["Authorization", "User-Agent", "X-Databricks-Org-Id"] diff --git a/acceptance/auth/host-metadata-cache/out.test.toml b/acceptance/auth/host-metadata-cache/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/auth/host-metadata-cache/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/auth/host-metadata-cache/output.txt b/acceptance/auth/host-metadata-cache/output.txt new file mode 100644 index 00000000000..0b99e9579c8 --- /dev/null +++ b/acceptance/auth/host-metadata-cache/output.txt @@ -0,0 +1,32 @@ + +=== First invocation populates the cache +{ + "profiles": [ + { + "name": "cached", + "host": "[DATABRICKS_URL]", + "cloud": "aws", + "auth_type": "", + "valid": false + } + ] +} + +=== Second invocation should read from the cache +{ + "profiles": [ + { + "name": "cached", + "host": "[DATABRICKS_URL]", + "cloud": "aws", + "auth_type": "", + "valid": false + } + ] +} + +=== Only one /.well-known/databricks-config request recorded +{ + "method": "GET", + "path": "/.well-known/databricks-config" +} diff --git a/acceptance/auth/host-metadata-cache/script b/acceptance/auth/host-metadata-cache/script new file mode 100644 index 00000000000..f7a5f2fe0f8 --- /dev/null +++ b/acceptance/auth/host-metadata-cache/script @@ -0,0 +1,19 @@ +sethome "./home" +export DATABRICKS_CACHE_DIR="$TEST_TMP_DIR/cache" + +# Point a profile at the mock server so auth profiles triggers a host metadata +# fetch. Without a profile the command does nothing and the cache is never read. +cat > "./home/.databrickscfg" < +""" + +import sys + +if len(sys.argv) < 2: + sys.stderr.write("Usage: echo_browser.py \n") + sys.exit(1) + +print(sys.argv[1]) diff --git a/acceptance/bin/gron.py b/acceptance/bin/gron.py index df7e36bd6e8..af16da51e2c 100755 --- a/acceptance/bin/gron.py +++ b/acceptance/bin/gron.py @@ -55,9 +55,35 @@ def gron(obj, path="json", noindex=False): print(f"{path} = {json.dumps(obj)};") +def sort_arrays(obj, keys): + """Recursively sort arrays whose dict key matches one in `keys`. + + Sort uses a canonical JSON repr so the order is content-determined and stable + across runs. Arrays not under a matching key keep their original order. + """ + if isinstance(obj, dict): + for k, v in obj.items(): + if isinstance(v, list): + items = [sort_arrays(item, keys) for item in v] + if k in keys: + items.sort(key=lambda x: json.dumps(x, sort_keys=True)) + obj[k] = items + else: + obj[k] = sort_arrays(v, keys) + return obj + elif isinstance(obj, list): + return [sort_arrays(item, keys) for item in obj] + return obj + + def main(): parser = argparse.ArgumentParser() parser.add_argument("--noindex", action="store_true") + parser.add_argument( + "--sort-arrays", + default="", + help="Comma-separated dict keys whose array values should be sorted by content (e.g. acls,access_control_list)", + ) parser.add_argument("file", nargs="?") args = parser.parse_args() @@ -70,6 +96,10 @@ def main(): if len(data) == 1: data = data[0] + if args.sort_arrays: + keys = set(args.sort_arrays.split(",")) + data = sort_arrays(data, keys) + gron(data, noindex=args.noindex) diff --git a/acceptance/bin/sort_acls_json.py b/acceptance/bin/sort_acls_json.py deleted file mode 100755 index a882d87de39..00000000000 --- a/acceptance/bin/sort_acls_json.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -""" -Sort ACLs in JSON files recursively to ensure consistent ordering. - -This script reads JSON from stdin, recursively finds all "acls" arrays, -sorts them by principal, and outputs the normalized JSON pretty-printed. - -Usage: - cat file.json | sort_acls_json.py - sort_acls_json.py < file.json -""" - -import json -import sys - - -def sort_acls_recursive(obj): - """Recursively traverse the object and sort any 'acls' arrays by principal.""" - if isinstance(obj, dict): - result = {} - for key, value in obj.items(): - if key == "acls" and isinstance(value, list): - result[key] = sorted(value, key=repr) - else: - result[key] = sort_acls_recursive(value) - return result - elif isinstance(obj, list): - return [sort_acls_recursive(item) for item in obj] - else: - return obj - - -def main(): - raw = sys.stdin.read() - try: - data = json.loads(raw) - except Exception: - print("Not json:\n" + raw, flush=True) - raise - normalized = sort_acls_recursive(data) - print(json.dumps(normalized, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/acceptance/bundle/apps/app_yaml/out.test.toml b/acceptance/bundle/apps/app_yaml/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/apps/app_yaml/out.test.toml +++ b/acceptance/bundle/apps/app_yaml/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/artifact_and_app_same_path/databricks.yml b/acceptance/bundle/apps/artifact_and_app_same_path/databricks.yml new file mode 100644 index 00000000000..54361a671c4 --- /dev/null +++ b/acceptance/bundle/apps/artifact_and_app_same_path/databricks.yml @@ -0,0 +1,13 @@ +bundle: + name: test-bundle + +artifacts: + my_artifact: + type: whl + path: ./src/app + +resources: + apps: + my_app: + name: my-app + source_code_path: ./src/app diff --git a/acceptance/bundle/apps/artifact_and_app_same_path/out.test.toml b/acceptance/bundle/apps/artifact_and_app_same_path/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/apps/artifact_and_app_same_path/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/artifact_and_app_same_path/output.txt b/acceptance/bundle/apps/artifact_and_app_same_path/output.txt new file mode 100644 index 00000000000..e2fbf7a9888 --- /dev/null +++ b/acceptance/bundle/apps/artifact_and_app_same_path/output.txt @@ -0,0 +1,6 @@ + +>>> [CLI] bundle validate -o json +/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/src/app + +>>> [CLI] bundle validate -o json +[TEST_TMP_DIR]/src/app diff --git a/acceptance/bundle/apps/artifact_and_app_same_path/script b/acceptance/bundle/apps/artifact_and_app_same_path/script new file mode 100644 index 00000000000..08c84800a8a --- /dev/null +++ b/acceptance/bundle/apps/artifact_and_app_same_path/script @@ -0,0 +1,2 @@ +trace $CLI bundle validate -o json | jq -r '.resources.apps.my_app.source_code_path' +trace $CLI bundle validate -o json | jq -r '.artifacts.my_artifact.path' diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/my_jobs_as_code/__init__.py b/acceptance/bundle/apps/artifact_and_app_same_path/src/app/test.py similarity index 100% rename from acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/my_jobs_as_code/__init__.py rename to acceptance/bundle/apps/artifact_and_app_same_path/src/app/test.py diff --git a/acceptance/bundle/apps/artifact_and_app_same_path/test.toml b/acceptance/bundle/apps/artifact_and_app_same_path/test.toml new file mode 100644 index 00000000000..a5b2fe28197 --- /dev/null +++ b/acceptance/bundle/apps/artifact_and_app_same_path/test.toml @@ -0,0 +1,5 @@ +RecordRequests = false + +Ignore = [ + '.databricks', +] diff --git a/acceptance/bundle/apps/compute_size/out.test.toml b/acceptance/bundle/apps/compute_size/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/apps/compute_size/out.test.toml +++ b/acceptance/bundle/apps/compute_size/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/compute_size/out.update.direct.txt b/acceptance/bundle/apps/compute_size/out.update.direct.txt index 61b95d763b9..6e02b630e61 100644 --- a/acceptance/bundle/apps/compute_size/out.update.direct.txt +++ b/acceptance/bundle/apps/compute_size/out.update.direct.txt @@ -7,5 +7,5 @@ Deployment complete! >>> [CLI] apps get app-[UNIQUE_NAME] { - "compute_size": "LARGE" + "compute_size": "MEDIUM" } diff --git a/acceptance/bundle/apps/git_source/out.test.toml b/acceptance/bundle/apps/git_source/out.test.toml index 6feb8784c89..8f6c4a03c57 100644 --- a/acceptance/bundle/apps/git_source/out.test.toml +++ b/acceptance/bundle/apps/git_source/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/job_permissions/out.test.toml b/acceptance/bundle/apps/job_permissions/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/apps/job_permissions/out.test.toml +++ b/acceptance/bundle/apps/job_permissions/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/job_permissions_warning/out.test.toml b/acceptance/bundle/apps/job_permissions_warning/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/apps/job_permissions_warning/out.test.toml +++ b/acceptance/bundle/apps/job_permissions_warning/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/apps/value_from_warning/out.test.toml b/acceptance/bundle/apps/value_from_warning/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/apps/value_from_warning/out.test.toml +++ b/acceptance/bundle/apps/value_from_warning/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifact_path_with_volume/volume_doesnot_exist/out.test.toml b/acceptance/bundle/artifacts/artifact_path_with_volume/volume_doesnot_exist/out.test.toml index 7190c9b30bf..2d812727e32 100644 --- a/acceptance/bundle/artifacts/artifact_path_with_volume/volume_doesnot_exist/out.test.toml +++ b/acceptance/bundle/artifacts/artifact_path_with_volume/volume_doesnot_exist/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifact_path_with_volume/volume_not_deployed/out.test.toml b/acceptance/bundle/artifacts/artifact_path_with_volume/volume_not_deployed/out.test.toml index 7190c9b30bf..2d812727e32 100644 --- a/acceptance/bundle/artifacts/artifact_path_with_volume/volume_not_deployed/out.test.toml +++ b/acceptance/bundle/artifacts/artifact_path_with_volume/volume_not_deployed/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifact_upload_for_volumes/out.test.toml b/acceptance/bundle/artifacts/artifact_upload_for_volumes/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/artifact_upload_for_volumes/out.test.toml +++ b/acceptance/bundle/artifacts/artifact_upload_for_volumes/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifact_upload_for_workspace/databricks.yml b/acceptance/bundle/artifacts/artifact_upload_for_workspace/databricks.yml index 261b90ed90a..49f500e5ddf 100644 --- a/acceptance/bundle/artifacts/artifact_upload_for_workspace/databricks.yml +++ b/acceptance/bundle/artifacts/artifact_upload_for_workspace/databricks.yml @@ -16,7 +16,7 @@ resources: entry_point: "run" libraries: - whl: whl/*.whl - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl - task_key: TestTask2 for_each_task: inputs: "[1]" @@ -28,11 +28,11 @@ resources: entry_point: "run" libraries: - whl: whl/*.whl - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl environments: - environment_key: "test_env" spec: client: "1" dependencies: - whl/source.whl - - /Workspace/Users/foo@bar.com/mywheel.whl + - /Workspace/Users/foo@bar.test/mywheel.whl diff --git a/acceptance/bundle/artifacts/artifact_upload_for_workspace/out.test.toml b/acceptance/bundle/artifacts/artifact_upload_for_workspace/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/artifact_upload_for_workspace/out.test.toml +++ b/acceptance/bundle/artifacts/artifact_upload_for_workspace/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifact_upload_for_workspace/output.txt b/acceptance/bundle/artifacts/artifact_upload_for_workspace/output.txt index 9336d675fd9..dd81ba7de2a 100644 --- a/acceptance/bundle/artifacts/artifact_upload_for_workspace/output.txt +++ b/acceptance/bundle/artifacts/artifact_upload_for_workspace/output.txt @@ -16,7 +16,7 @@ Deployment complete! "whl": "/Workspace/foo/bar/artifacts/.internal/source.whl" }, { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -35,7 +35,7 @@ Deployment complete! "whl": "/Workspace/foo/bar/artifacts/.internal/source.whl" }, { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -63,7 +63,7 @@ Deployment complete! "client": "1", "dependencies": [ "/Workspace/foo/bar/artifacts/.internal/source.whl", - "/Workspace/Users/foo@bar.com/mywheel.whl" + "/Workspace/Users/foo@bar.test/mywheel.whl" ] } } diff --git a/acceptance/bundle/artifacts/artifact_upload_with_no_library_reference/out.test.toml b/acceptance/bundle/artifacts/artifact_upload_with_no_library_reference/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/artifact_upload_with_no_library_reference/out.test.toml +++ b/acceptance/bundle/artifacts/artifact_upload_with_no_library_reference/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/artifacts_dynamic_version/out.test.toml b/acceptance/bundle/artifacts/artifacts_dynamic_version/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/artifacts_dynamic_version/out.test.toml +++ b/acceptance/bundle/artifacts/artifacts_dynamic_version/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/build_and_files/out.test.toml b/acceptance/bundle/artifacts/build_and_files/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/build_and_files/out.test.toml +++ b/acceptance/bundle/artifacts/build_and_files/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/build_and_files_whl/out.test.toml b/acceptance/bundle/artifacts/build_and_files_whl/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/build_and_files_whl/out.test.toml +++ b/acceptance/bundle/artifacts/build_and_files_whl/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/glob_exact_whl/out.test.toml b/acceptance/bundle/artifacts/glob_exact_whl/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/glob_exact_whl/out.test.toml +++ b/acceptance/bundle/artifacts/glob_exact_whl/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/globs_in_files/out.test.toml b/acceptance/bundle/artifacts/globs_in_files/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/globs_in_files/out.test.toml +++ b/acceptance/bundle/artifacts/globs_in_files/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/globs_in_files_in_include/out.test.toml b/acceptance/bundle/artifacts/globs_in_files_in_include/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/globs_in_files_in_include/out.test.toml +++ b/acceptance/bundle/artifacts/globs_in_files_in_include/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/globs_invalid/out.test.toml b/acceptance/bundle/artifacts/globs_invalid/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/globs_invalid/out.test.toml +++ b/acceptance/bundle/artifacts/globs_invalid/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/issue_3109/out.test.toml b/acceptance/bundle/artifacts/issue_3109/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/issue_3109/out.test.toml +++ b/acceptance/bundle/artifacts/issue_3109/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/nil_artifacts/out.test.toml b/acceptance/bundle/artifacts/nil_artifacts/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/nil_artifacts/out.test.toml +++ b/acceptance/bundle/artifacts/nil_artifacts/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/same_name_libraries/out.test.toml b/acceptance/bundle/artifacts/same_name_libraries/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/same_name_libraries/out.test.toml +++ b/acceptance/bundle/artifacts/same_name_libraries/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/bash/out.test.toml b/acceptance/bundle/artifacts/shell/bash/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/artifacts/shell/bash/out.test.toml +++ b/acceptance/bundle/artifacts/shell/bash/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/basic/out.test.toml b/acceptance/bundle/artifacts/shell/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/shell/basic/out.test.toml +++ b/acceptance/bundle/artifacts/shell/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/cmd/out.test.toml b/acceptance/bundle/artifacts/shell/cmd/out.test.toml index d820d4a4ecc..8471d88c7f3 100644 --- a/acceptance/bundle/artifacts/shell/cmd/out.test.toml +++ b/acceptance/bundle/artifacts/shell/cmd/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = false - -[GOOS] - darwin = false - linux = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.darwin = false +GOOS.linux = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/default/out.test.toml b/acceptance/bundle/artifacts/shell/default/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/artifacts/shell/default/out.test.toml +++ b/acceptance/bundle/artifacts/shell/default/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/err-bash/out.test.toml b/acceptance/bundle/artifacts/shell/err-bash/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/shell/err-bash/out.test.toml +++ b/acceptance/bundle/artifacts/shell/err-bash/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/err-sh/out.test.toml b/acceptance/bundle/artifacts/shell/err-sh/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/shell/err-sh/out.test.toml +++ b/acceptance/bundle/artifacts/shell/err-sh/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/invalid/out.test.toml b/acceptance/bundle/artifacts/shell/invalid/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/shell/invalid/out.test.toml +++ b/acceptance/bundle/artifacts/shell/invalid/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/shell/sh/out.test.toml b/acceptance/bundle/artifacts/shell/sh/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/artifacts/shell/sh/out.test.toml +++ b/acceptance/bundle/artifacts/shell/sh/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/unique_name_libraries/out.test.toml b/acceptance/bundle/artifacts/unique_name_libraries/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/unique_name_libraries/out.test.toml +++ b/acceptance/bundle/artifacts/unique_name_libraries/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/upload_multiple_libraries/databricks.yml b/acceptance/bundle/artifacts/upload_multiple_libraries/databricks.yml index 08c336a6f1a..d1a3aac188d 100644 --- a/acceptance/bundle/artifacts/upload_multiple_libraries/databricks.yml +++ b/acceptance/bundle/artifacts/upload_multiple_libraries/databricks.yml @@ -16,11 +16,11 @@ resources: entry_point: "run" libraries: - whl: whl/*.whl - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl environments: - environment_key: "test_env" spec: client: "1" dependencies: - whl/*.whl - - /Workspace/Users/foo@bar.com/mywheel.whl + - /Workspace/Users/foo@bar.test/mywheel.whl diff --git a/acceptance/bundle/artifacts/upload_multiple_libraries/out.test.toml b/acceptance/bundle/artifacts/upload_multiple_libraries/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/upload_multiple_libraries/out.test.toml +++ b/acceptance/bundle/artifacts/upload_multiple_libraries/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/upload_multiple_libraries/output.txt b/acceptance/bundle/artifacts/upload_multiple_libraries/output.txt index 3e377269800..fa725a29d86 100644 --- a/acceptance/bundle/artifacts/upload_multiple_libraries/output.txt +++ b/acceptance/bundle/artifacts/upload_multiple_libraries/output.txt @@ -28,7 +28,7 @@ Deployment complete! "whl": "/Workspace/foo/bar/artifacts/.internal/source4.whl" }, { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -62,7 +62,7 @@ Deployment complete! "/Workspace/foo/bar/artifacts/.internal/source2.whl", "/Workspace/foo/bar/artifacts/.internal/source3.whl", "/Workspace/foo/bar/artifacts/.internal/source4.whl", - "/Workspace/Users/foo@bar.com/mywheel.whl" + "/Workspace/Users/foo@bar.test/mywheel.whl" ] } } diff --git a/acceptance/bundle/artifacts/whl_change_version/out.test.toml b/acceptance/bundle/artifacts/whl_change_version/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_change_version/out.test.toml +++ b/acceptance/bundle/artifacts/whl_change_version/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_dbfs/out.test.toml b/acceptance/bundle/artifacts/whl_dbfs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_dbfs/out.test.toml +++ b/acceptance/bundle/artifacts/whl_dbfs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_dynamic/out.test.toml b/acceptance/bundle/artifacts/whl_dynamic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_dynamic/out.test.toml +++ b/acceptance/bundle/artifacts/whl_dynamic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_explicit/out.test.toml b/acceptance/bundle/artifacts/whl_explicit/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_explicit/out.test.toml +++ b/acceptance/bundle/artifacts/whl_explicit/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_implicit/out.test.toml b/acceptance/bundle/artifacts/whl_implicit/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_implicit/out.test.toml +++ b/acceptance/bundle/artifacts/whl_implicit/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_implicit_custom_path/out.test.toml b/acceptance/bundle/artifacts/whl_implicit_custom_path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_implicit_custom_path/out.test.toml +++ b/acceptance/bundle/artifacts/whl_implicit_custom_path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_implicit_notebook/out.test.toml b/acceptance/bundle/artifacts/whl_implicit_notebook/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_implicit_notebook/out.test.toml +++ b/acceptance/bundle/artifacts/whl_implicit_notebook/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_multiple/out.test.toml b/acceptance/bundle/artifacts/whl_multiple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_multiple/out.test.toml +++ b/acceptance/bundle/artifacts/whl_multiple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_no_cleanup/out.test.toml b/acceptance/bundle/artifacts/whl_no_cleanup/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_no_cleanup/out.test.toml +++ b/acceptance/bundle/artifacts/whl_no_cleanup/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_prebuilt_multiple/out.test.toml b/acceptance/bundle/artifacts/whl_prebuilt_multiple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_prebuilt_multiple/out.test.toml +++ b/acceptance/bundle/artifacts/whl_prebuilt_multiple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_prebuilt_outside/out.test.toml b/acceptance/bundle/artifacts/whl_prebuilt_outside/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_prebuilt_outside/out.test.toml +++ b/acceptance/bundle/artifacts/whl_prebuilt_outside/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_prebuilt_outside_dynamic/out.test.toml b/acceptance/bundle/artifacts/whl_prebuilt_outside_dynamic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_prebuilt_outside_dynamic/out.test.toml +++ b/acceptance/bundle/artifacts/whl_prebuilt_outside_dynamic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/artifacts/whl_via_environment_key/out.test.toml b/acceptance/bundle/artifacts/whl_via_environment_key/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/artifacts/whl_via_environment_key/out.test.toml +++ b/acceptance/bundle/artifacts/whl_via_environment_key/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/benchmarks/deploy/out.test.toml b/acceptance/bundle/benchmarks/deploy/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/benchmarks/deploy/out.test.toml +++ b/acceptance/bundle/benchmarks/deploy/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/benchmarks/plan/out.test.toml b/acceptance/bundle/benchmarks/plan/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/benchmarks/plan/out.test.toml +++ b/acceptance/bundle/benchmarks/plan/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/benchmarks/validate/out.test.toml b/acceptance/bundle/benchmarks/validate/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/benchmarks/validate/out.test.toml +++ b/acceptance/bundle/benchmarks/validate/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/bundle_tag/id/out.test.toml b/acceptance/bundle/bundle_tag/id/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/bundle_tag/id/out.test.toml +++ b/acceptance/bundle/bundle_tag/id/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/bundle_tag/url/out.test.toml b/acceptance/bundle/bundle_tag/url/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/bundle_tag/url/out.test.toml +++ b/acceptance/bundle/bundle_tag/url/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/bundle_tag/url_ref/out.test.toml b/acceptance/bundle/bundle_tag/url_ref/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/bundle_tag/url_ref/out.test.toml +++ b/acceptance/bundle/bundle_tag/url_ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/config-remote-sync/cli_defaults/out.test.toml b/acceptance/bundle/config-remote-sync/cli_defaults/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/cli_defaults/out.test.toml +++ b/acceptance/bundle/config-remote-sync/cli_defaults/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/config_edits/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/config_edits/databricks.yml.tmpl index d7a7aa4d75e..00cf799e5df 100644 --- a/acceptance/bundle/config-remote-sync/config_edits/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/config_edits/databricks.yml.tmpl @@ -15,6 +15,7 @@ resources: targets: default: + mode: development resources: jobs: my_job: diff --git a/acceptance/bundle/config-remote-sync/config_edits/out.test.toml b/acceptance/bundle/config-remote-sync/config_edits/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/config_edits/out.test.toml +++ b/acceptance/bundle/config-remote-sync/config_edits/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/config_edits/output.txt b/acceptance/bundle/config-remote-sync/config_edits/output.txt index 2a59c6f563e..ce51b3c7a65 100644 --- a/acceptance/bundle/config-remote-sync/config_edits/output.txt +++ b/acceptance/bundle/config-remote-sync/config_edits/output.txt @@ -35,14 +35,14 @@ Resource: resources.jobs.my_job >>> diff.py databricks.yml.backup databricks.yml --- databricks.yml.backup +++ databricks.yml -@@ -24,5 +24,5 @@ +@@ -25,5 +25,5 @@ - success@example.com on_failure: - - config-failure@example.com + - remote-failure@example.com parameters: - name: catalog -@@ -35,8 +35,6 @@ +@@ -36,8 +36,6 @@ unit: DAYS tags: - env: config-production diff --git a/acceptance/bundle/config-remote-sync/flushed_cache/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/flushed_cache/databricks.yml.tmpl index 18d37e00e94..16d5646d970 100644 --- a/acceptance/bundle/config-remote-sync/flushed_cache/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/flushed_cache/databricks.yml.tmpl @@ -13,3 +13,7 @@ resources: spark_version: $DEFAULT_SPARK_VERSION node_type_id: $NODE_TYPE_ID num_workers: 1 + +targets: + default: + mode: development diff --git a/acceptance/bundle/config-remote-sync/flushed_cache/out.test.toml b/acceptance/bundle/config-remote-sync/flushed_cache/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/flushed_cache/out.test.toml +++ b/acceptance/bundle/config-remote-sync/flushed_cache/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl index d81d2dde273..f8b1ebd23d3 100644 --- a/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl @@ -39,3 +39,7 @@ resources: parameters: - {name: catalog, default: main} - {name: schema, default: dev} + +targets: + default: + mode: development diff --git a/acceptance/bundle/config-remote-sync/formatting_preserved/out.test.toml b/acceptance/bundle/config-remote-sync/formatting_preserved/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/formatting_preserved/out.test.toml +++ b/acceptance/bundle/config-remote-sync/formatting_preserved/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt b/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt index f085cab46f6..6cba3ca53af 100644 --- a/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt +++ b/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt @@ -42,11 +42,13 @@ Resource: resources.jobs.my_job + Main processing task that runs the notebook. notebook_task: notebook_path: /Users/{{workspace_user_name}}/notebook -@@ -40,2 +39,3 @@ +@@ -40,4 +39,5 @@ - {name: catalog, default: main} - {name: schema, default: dev} + timeout_seconds: 3600 + targets: + >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.jobs.my_job diff --git a/acceptance/bundle/config-remote-sync/job_fields/out.test.toml b/acceptance/bundle/config-remote-sync/job_fields/out.test.toml index 152a1f10a9b..1773f7accf5 100644 --- a/acceptance/bundle/config-remote-sync/job_fields/out.test.toml +++ b/acceptance/bundle/config-remote-sync/job_fields/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/job_multiple_tasks/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/job_multiple_tasks/databricks.yml.tmpl index e149ab8a057..f5ead70f496 100644 --- a/acceptance/bundle/config-remote-sync/job_multiple_tasks/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/job_multiple_tasks/databricks.yml.tmpl @@ -76,3 +76,7 @@ resources: spark_version: $DEFAULT_SPARK_VERSION node_type_id: $NODE_TYPE_ID num_workers: 1 + +targets: + default: + mode: development diff --git a/acceptance/bundle/config-remote-sync/job_multiple_tasks/out.test.toml b/acceptance/bundle/config-remote-sync/job_multiple_tasks/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/job_multiple_tasks/out.test.toml +++ b/acceptance/bundle/config-remote-sync/job_multiple_tasks/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt index 0d8b9275a96..6c45a66bed1 100644 --- a/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt +++ b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt @@ -8,7 +8,8 @@ Deployment complete! Detected changes in 2 resource(s): Resource: resources.jobs.my_job - tasks[task_key='c_task'].depends_on[0].task_key: replace + tasks[task_key='c_task'].depends_on[task_key='b_task']: add + tasks[task_key='c_task'].depends_on[task_key='d_task']: remove tasks[task_key='c_task'].new_cluster.num_workers: replace tasks[task_key='c_task'].timeout_seconds: add tasks[task_key='d_task']: remove @@ -83,8 +84,10 @@ Resource: resources.jobs.rename_task_job tasks[task_key='a_task'].notebook_task.notebook_path: replace tasks[task_key='b_task']: remove tasks[task_key='b_task_renamed']: add - tasks[task_key='c_task'].depends_on[0].task_key: replace - tasks[task_key='d_task'].depends_on[0].task_key: replace + tasks[task_key='c_task'].depends_on[task_key='b_task']: remove + tasks[task_key='c_task'].depends_on[task_key='b_task_renamed']: add + tasks[task_key='d_task'].depends_on[task_key='b_task']: remove + tasks[task_key='d_task'].depends_on[task_key='b_task_renamed']: add tasks[task_key='synced_task']: add @@ -122,7 +125,7 @@ Resource: resources.jobs.rename_task_job + - task_key: b_task_renamed notebook_task: notebook_path: /Users/{{workspace_user_name}}/c_task -@@ -79,7 +79,14 @@ +@@ -79,9 +79,16 @@ - task_key: a_task notebook_task: - notebook_path: /Users/{{workspace_user_name}}/a_task @@ -139,6 +142,8 @@ Resource: resources.jobs.rename_task_job + notebook_path: ./synced_notebook.py + task_key: synced_task + targets: + >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.jobs.my_job diff --git a/acceptance/bundle/config-remote-sync/job_params_variables/out.test.toml b/acceptance/bundle/config-remote-sync/job_params_variables/out.test.toml index 152a1f10a9b..1773f7accf5 100644 --- a/acceptance/bundle/config-remote-sync/job_params_variables/out.test.toml +++ b/acceptance/bundle/config-remote-sync/job_params_variables/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/job_pipeline_task/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/job_pipeline_task/databricks.yml.tmpl index e915fcbcaeb..64da4e1669d 100644 --- a/acceptance/bundle/config-remote-sync/job_pipeline_task/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/job_pipeline_task/databricks.yml.tmpl @@ -17,3 +17,7 @@ resources: pipeline_task: pipeline_id: ${resources.pipelines.my_pipeline.id} full_refresh: false + +targets: + default: + mode: development diff --git a/acceptance/bundle/config-remote-sync/job_pipeline_task/out.test.toml b/acceptance/bundle/config-remote-sync/job_pipeline_task/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/job_pipeline_task/out.test.toml +++ b/acceptance/bundle/config-remote-sync/job_pipeline_task/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/job_pipeline_task/output.txt b/acceptance/bundle/config-remote-sync/job_pipeline_task/output.txt index 625902a6e7d..54a1e342788 100644 --- a/acceptance/bundle/config-remote-sync/job_pipeline_task/output.txt +++ b/acceptance/bundle/config-remote-sync/job_pipeline_task/output.txt @@ -4,7 +4,7 @@ Updating deployment state... Deployment complete! === Modify pipeline_task full_refresh to True -=== Modify pipeline development to True +=== Modify pipeline continuous to True === Detect and save changes Detected changes in 2 resource(s): @@ -12,7 +12,7 @@ Resource: resources.jobs.my_job tasks[task_key='run_pipeline'].pipeline_task.full_refresh: replace Resource: resources.pipelines.my_pipeline - development: replace + continuous: add @@ -21,19 +21,20 @@ Resource: resources.pipelines.my_pipeline >>> diff.py databricks.yml.backup databricks.yml --- databricks.yml.backup +++ databricks.yml -@@ -6,5 +6,5 @@ - my_pipeline: - name: test-pipeline-[UNIQUE_NAME] -- development: false -+ development: true - libraries: - - notebook: -@@ -17,3 +17,3 @@ +@@ -11,4 +11,5 @@ + path: /Users/{{workspace_user_name}}/notebook + ++ continuous: true + jobs: + my_job: +@@ -17,5 +18,5 @@ pipeline_task: pipeline_id: ${resources.pipelines.my_pipeline.id} - full_refresh: false + full_refresh: true + targets: + >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: delete resources.jobs.my_job diff --git a/acceptance/bundle/config-remote-sync/job_pipeline_task/script b/acceptance/bundle/config-remote-sync/job_pipeline_task/script index 867fae764da..d5587a201e1 100755 --- a/acceptance/bundle/config-remote-sync/job_pipeline_task/script +++ b/acceptance/bundle/config-remote-sync/job_pipeline_task/script @@ -17,9 +17,12 @@ edit_resource.py jobs $job_id </variable-overrides.json + file_override_var: + description: "Set from variable-overrides.json" + # Used by job environments below. Resolves to a pip-style spec; the same + # spec is then targeted by both an Add (new environment_key) and a Replace + # (appending another dep to the existing environment). + my_env_dep: + default: nonexistent-test-pkg==1.2.3 + # Complex variable to verify no panics + cluster_config: + type: complex + default: + node_type_id: Standard_DS3_v2 + num_workers: 2 + +resources: + pipelines: + my_pipeline: + name: test-pipeline-$UNIQUE_NAME + development: false + libraries: + - notebook: + path: /Users/{{workspace_user_name}}/notebook + # Three deps cover the compound-interpolation cases: + # [0] pure ${var.X}, untouched in the test → stays as ${var.my_env_dep} + # [1] compound with ${workspace.file_path} → trimmed/extended in the + # script; substring substitution must preserve the variable + # [2] hardcoded literal that happens to equal ${var.my_env_dep}'s + # resolved value → must stay literal (no false-positive promotion) + environment: + dependencies: + - ${var.my_env_dep} + - --editable ${workspace.file_path} + - nonexistent-test-pkg==1.2.3 + + jobs: + my_job: + parameters: + - name: catalog + default: ${var.my_catalog} + - name: env + default: ${var.target_env} + - name: file_val + default: ${var.file_override_var} + # Bundle reference: ${bundle.target} resolves to "default". + - name: target_name + default: ${bundle.target} + # Both use variables that resolve to the same value ("raw_data"). + # Tests disambiguation: original reference is preserved on Replace. + - name: landing + default: ${var.landing_schema} + - name: curated + default: ${var.curated_schema} + tasks: + - task_key: main + notebook_task: + notebook_path: /Users/{{workspace_user_name}}/notebook + base_parameters: + # Compound interpolation: mixes ${var.X} and ${bundle.X} refs. + source_path: /mnt/${var.my_catalog}/${bundle.target}/raw/landing + # Resource reference: ${resources.pipelines.my_pipeline.id} resolves to + # the deployed pipeline's ID. Used to test that resource refs are + # treated the same as other reference kinds by the sync logic. + - task_key: run_pipeline + pipeline_task: + pipeline_id: ${resources.pipelines.my_pipeline.id} + full_refresh: false + # Job environments use ${var.my_env_dep} via a pure variable reference. + # The script adds a separate environments entry and modifies the existing + # one to verify the variable survives both code paths. + environments: + - environment_key: default + spec: + environment_version: "4" + dependencies: + - ${var.my_env_dep} + +targets: + default: + mode: development + variables: + target_env: production diff --git a/acceptance/bundle/config-remote-sync/resolve_variables/out.test.toml b/acceptance/bundle/config-remote-sync/resolve_variables/out.test.toml new file mode 100644 index 00000000000..1773f7accf5 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/resolve_variables/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/resolve_variables/output.txt b/acceptance/bundle/config-remote-sync/resolve_variables/output.txt new file mode 100644 index 00000000000..520745de4ed --- /dev/null +++ b/acceptance/bundle/config-remote-sync/resolve_variables/output.txt @@ -0,0 +1,123 @@ +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Modify pipeline environment dependencies +=== Add and replace parameters remotely +=== Detect and save changes +Detected changes in 2 resource(s): + +Resource: resources.jobs.my_job + environments[environment_key='default'].spec.dependencies: replace + environments[environment_key='secondary']: add + parameters[name='catalog'].default: replace + parameters[name='data_catalog']: add + parameters[name='deploy_env']: add + parameters[name='deploy_target']: add + parameters[name='env'].default: replace + parameters[name='file_sourced']: add + parameters[name='region']: add + parameters[name='some_schema']: add + tags['deployment']: add + tags['dev']: remove + tasks[task_key='main'].notebook_task.base_parameters['source_path']: replace + tasks[task_key='run_pipeline'].pipeline_task.full_refresh: replace + tasks[task_key='run_pipeline_again']: add + tasks[task_key='secondary']: add + +Resource: resources.pipelines.my_pipeline + environment.dependencies: replace + + + +=== Configuration changes + +>>> diff.py databricks.yml.backup databricks.yml +--- databricks.yml.backup ++++ databricks.yml +@@ -45,14 +45,14 @@ + dependencies: + - ${var.my_env_dep} +- - --editable ${workspace.file_path} ++ - ${workspace.file_path}/extra + - nonexistent-test-pkg==1.2.3 +- ++ - another-pkg==2.0 + jobs: + my_job: + parameters: + - name: catalog ++ default: staging_catalog ++ - name: env + default: ${var.my_catalog} +- - name: env +- default: ${var.target_env} + - name: file_val + default: ${var.file_override_var} +@@ -66,4 +66,16 @@ + - name: curated + default: ${var.curated_schema} ++ - default: ${var.my_catalog} ++ name: data_catalog ++ - default: ${var.target_env} ++ name: deploy_env ++ - default: ${bundle.target} ++ name: deploy_target ++ - default: ${var.file_override_var} ++ name: file_sourced ++ - default: us-west-2 ++ name: region ++ - default: raw_data ++ name: some_schema + tasks: + - task_key: main +@@ -72,5 +84,5 @@ + base_parameters: + # Compound interpolation: mixes ${var.X} and ${bundle.X} refs. +- source_path: /mnt/${var.my_catalog}/${bundle.target}/raw/landing ++ source_path: /mnt/${var.my_catalog}/${bundle.target}/raw/landing_v2 + # Resource reference: ${resources.pipelines.my_pipeline.id} resolves to + # the deployed pipeline's ID. Used to test that resource refs are +@@ -79,5 +91,13 @@ + pipeline_task: + pipeline_id: ${resources.pipelines.my_pipeline.id} +- full_refresh: false ++ full_refresh: true ++ - pipeline_task: ++ pipeline_id: ${resources.pipelines.my_pipeline.id} ++ task_key: run_pipeline_again ++ - notebook_task: ++ base_parameters: ++ source_path: /mnt/${var.my_catalog}/${bundle.target}/raw/landing_v2 ++ notebook_path: /Users/{{workspace_user_name}}/notebook ++ task_key: secondary + # Job environments use ${var.my_env_dep} via a pure variable reference. + # The script adds a separate environments entry and modifies the existing +@@ -89,4 +109,12 @@ + dependencies: + - ${var.my_env_dep} ++ - nonexistent-other-pkg==2.0 ++ - environment_key: secondary ++ spec: ++ dependencies: ++ - ${var.my_env_dep} ++ environment_version: "4" ++ tags: ++ deployment: main + + targets: + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.my_job + delete resources.pipelines.my_pipeline + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them: + delete resources.pipelines.my_pipeline + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/config-remote-sync/resolve_variables/script b/acceptance/bundle/config-remote-sync/resolve_variables/script new file mode 100755 index 00000000000..449fa9407f2 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/resolve_variables/script @@ -0,0 +1,129 @@ +#!/bin/bash + +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +$CLI bundle deploy +job_id="$(read_id.py my_job)" +pipeline_id="$(read_id.py my_pipeline)" + +title "Modify pipeline environment dependencies" +edit_resource.py pipelines $pipeline_id < restored to \${var.my_catalog} +r["parameters"].append({"name": "data_catalog", "default": "main"}) +# "raw_data" matches two variables (landing_schema, curated_schema) -> ambiguous, stays hardcoded +r["parameters"].append({"name": "some_schema", "default": "raw_data"}) +# "us-west-2" matches no variable -> stays hardcoded +r["parameters"].append({"name": "region", "default": "us-west-2"}) +# "production" matches target_env (set in target, not default) -> restored to \${var.target_env} +r["parameters"].append({"name": "deploy_env", "default": "production"}) +# "from-overrides-file" matches file_override_var (set via variable-overrides.json) -> restored +r["parameters"].append({"name": "file_sourced", "default": "from-overrides-file"}) +# "default" matches sibling target_name's \${bundle.target} -> restored +r["parameters"].append({"name": "deploy_target", "default": "default"}) + +# Add a new task with the same structure as the existing one (different task_key). +# The sibling's source_path uses compound interpolation with \${var.my_catalog} and +# \${bundle.target}; the new task's source_path matches the resolved template, so +# both variables should be restored via compound-sibling alignment. +r["tasks"].append({ + "task_key": "secondary", + "notebook_task": { + "notebook_path": r["tasks"][0]["notebook_task"]["notebook_path"], + "base_parameters": {"source_path": "/mnt/main/default/raw/landing"} + } +}) + +# Change full_refresh on the existing run_pipeline task. The sibling field +# pipeline_id uses \${resources.pipelines.my_pipeline.id} and must stay +# intact in the YAML because the Replace only affects full_refresh. +for t in r["tasks"]: + if t.get("task_key") == "run_pipeline": + t["pipeline_task"]["full_refresh"] = True + +# Add a new pipeline task that triggers the same pipeline. The sibling +# run_pipeline has pipeline_id = \${resources.pipelines.my_pipeline.id}; the +# new task uses the same resolved ID, so the sibling rule should restore +# the resource reference. +pipeline_id = next(t for t in r["tasks"] if t["task_key"] == "run_pipeline")["pipeline_task"]["pipeline_id"] +r["tasks"].append({ + "task_key": "run_pipeline_again", + "pipeline_task": { + "pipeline_id": pipeline_id, + } +}) + +# --- Replace operations (original ref) --- +# Change "catalog" param (originally \${var.my_catalog} = "main") to unrelated value -> hardcoded +for p in r["parameters"]: + if p["name"] == "catalog": + p["default"] = "staging_catalog" + +# Re-target to a different variable: "env" was \${var.target_env} (= "production"). +# New value "main" doesn't match target_env but uniquely matches \${var.my_catalog}, +# so the field is re-targeted to \${var.my_catalog} via the fallback lookup. +for p in r["parameters"]: + if p["name"] == "env": + p["default"] = "main" + +# --- Non-sequence Add (false-positive prevention) --- +# Add tags to the job. Tags is a map (not a sequence), so Add restoration is +# skipped entirely: "main" stays hardcoded even though it matches \${var.my_catalog}. +r["tags"] = {"deployment": "main"} + +# Compound interpolation: change only the suffix of source_path. +# Both \${var.my_catalog}="main" and \${bundle.target}="default" are unchanged; +# only the literal suffix changes. Expected: both refs preserved, suffix updated. +for t in r["tasks"]: + bp = t.get("notebook_task", {}).get("base_parameters", {}) + if "source_path" in bp: + bp["source_path"] = "/mnt/main/default/raw/landing_v2" + +# --- Environments coverage --- +# Add a new environments entry. The sibling at environment_key='default' has +# dependencies = ["\${var.my_env_dep}"]; the new entry uses the same resolved +# value, so sibling-based restoration should restore the ref. +r["environments"].append({ + "environment_key": "secondary", + "spec": { + "environment_version": "4", + "dependencies": ["nonexistent-test-pkg==1.2.3"], + }, +}) + +# Replace the existing environment's dependencies: keep the variable-backed +# entry and append an unrelated literal. Existing \${var.my_env_dep} must be +# preserved at index 0; the new dep stays hardcoded. +for e in r["environments"]: + if e["environment_key"] == "default": + e["spec"]["dependencies"].append("nonexistent-other-pkg==2.0") +EOF + +title "Detect and save changes" +echo +cp databricks.yml databricks.yml.backup +$CLI bundle config-remote-sync --save + +title "Configuration changes" +echo +trace diff.py databricks.yml.backup databricks.yml +rm databricks.yml.backup diff --git a/acceptance/bundle/config-remote-sync/resolve_variables/test.toml b/acceptance/bundle/config-remote-sync/resolve_variables/test.toml new file mode 100644 index 00000000000..3174477f7d2 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/resolve_variables/test.toml @@ -0,0 +1,11 @@ +Cloud = true +RequiresUnityCatalog = true + +RecordRequests = false +Ignore = [".databricks", "databricks.yml", "databricks.yml.backup"] + +[Env] +DATABRICKS_BUNDLE_ENABLE_EXPERIMENTAL_YAML_SYNC = "true" + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/target_override/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/target_override/databricks.yml.tmpl index 77996e03abf..cbcaddfc596 100644 --- a/acceptance/bundle/config-remote-sync/target_override/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/target_override/databricks.yml.tmpl @@ -15,6 +15,7 @@ resources: targets: dev: + mode: development resources: jobs: my_job: diff --git a/acceptance/bundle/config-remote-sync/target_override/out.test.toml b/acceptance/bundle/config-remote-sync/target_override/out.test.toml index 382d99ed10b..579b1e4a3c9 100644 --- a/acceptance/bundle/config-remote-sync/target_override/out.test.toml +++ b/acceptance/bundle/config-remote-sync/target_override/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/target_override/output.txt b/acceptance/bundle/config-remote-sync/target_override/output.txt index 4bf571881ea..fa32ecade2c 100644 --- a/acceptance/bundle/config-remote-sync/target_override/output.txt +++ b/acceptance/bundle/config-remote-sync/target_override/output.txt @@ -19,7 +19,7 @@ Resource: resources.jobs.my_job >>> diff.py databricks.yml.backup databricks.yml --- databricks.yml.backup +++ databricks.yml -@@ -19,5 +19,6 @@ +@@ -20,5 +20,6 @@ jobs: my_job: - max_concurrent_runs: 2 diff --git a/acceptance/bundle/config-remote-sync/validation_errors/out.test.toml b/acceptance/bundle/config-remote-sync/validation_errors/out.test.toml index c4500f378d8..4b5914daa2c 100644 --- a/acceptance/bundle/config-remote-sync/validation_errors/out.test.toml +++ b/acceptance/bundle/config-remote-sync/validation_errors/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/debug/list-targets/databricks.yml b/acceptance/bundle/debug/list-targets/databricks.yml new file mode 100644 index 00000000000..1de1692658b --- /dev/null +++ b/acceptance/bundle/debug/list-targets/databricks.yml @@ -0,0 +1,27 @@ +bundle: + name: test-list-targets + +variables: + is_default: + default: false + target_mode: + default: production + target_host: + default: https://var.example.com + +targets: + dev: + default: true + mode: development + workspace: + host: https://dev.example.com + staging: + prod: + mode: production + workspace: + host: https://prod.example.com + with_vars: + default: ${var.is_default} + mode: ${var.target_mode} + workspace: + host: ${var.target_host} diff --git a/acceptance/bundle/debug/list-targets/out.test.toml b/acceptance/bundle/debug/list-targets/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/debug/list-targets/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/debug/list-targets/output.txt b/acceptance/bundle/debug/list-targets/output.txt new file mode 100644 index 00000000000..a936dcf5033 --- /dev/null +++ b/acceptance/bundle/debug/list-targets/output.txt @@ -0,0 +1,31 @@ + +>>> [CLI] bundle debug list-targets +dev (default) development https://dev.example.com +prod production https://prod.example.com +staging +with_vars ${var.target_mode} ${var.target_host} + +>>> [CLI] bundle debug list-targets --output json +{ + "targets": [ + { + "name": "dev", + "default": true, + "mode": "development", + "host": "https://dev.example.com" + }, + { + "name": "prod", + "mode": "production", + "host": "https://prod.example.com" + }, + { + "name": "staging" + }, + { + "name": "with_vars", + "mode": "${var.target_mode}", + "host": "${var.target_host}" + } + ] +} \ No newline at end of file diff --git a/acceptance/bundle/debug/list-targets/script b/acceptance/bundle/debug/list-targets/script new file mode 100644 index 00000000000..fb699dee0cc --- /dev/null +++ b/acceptance/bundle/debug/list-targets/script @@ -0,0 +1,2 @@ +trace $CLI bundle debug list-targets +trace $CLI bundle debug list-targets --output json diff --git a/acceptance/bundle/debug/out.test.toml b/acceptance/bundle/debug/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/debug/out.test.toml +++ b/acceptance/bundle/debug/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/empty-bundle/out.test.toml b/acceptance/bundle/deploy/empty-bundle/out.test.toml index 0940cf4b56e..72e8a7a4dfe 100644 --- a/acceptance/bundle/deploy/empty-bundle/out.test.toml +++ b/acceptance/bundle/deploy/empty-bundle/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENABLE_EXPERIMENTAL_YAML_SYNC = ["", "true"] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENABLE_EXPERIMENTAL_YAML_SYNC = ["", "true"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/experimental-python/out.test.toml b/acceptance/bundle/deploy/experimental-python/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deploy/experimental-python/out.test.toml +++ b/acceptance/bundle/deploy/experimental-python/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/fail-on-active-runs/out.test.toml b/acceptance/bundle/deploy/fail-on-active-runs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deploy/fail-on-active-runs/out.test.toml +++ b/acceptance/bundle/deploy/fail-on-active-runs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/files/no-snapshot-sync/out.test.toml b/acceptance/bundle/deploy/files/no-snapshot-sync/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/deploy/files/no-snapshot-sync/out.test.toml +++ b/acceptance/bundle/deploy/files/no-snapshot-sync/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/mlops-stacks/out.test.toml b/acceptance/bundle/deploy/mlops-stacks/out.test.toml index 3cdb920b677..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deploy/mlops-stacks/out.test.toml +++ b/acceptance/bundle/deploy/mlops-stacks/out.test.toml @@ -1,5 +1,3 @@ -Local = false +Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/mlops-stacks/test.toml b/acceptance/bundle/deploy/mlops-stacks/test.toml index ee4df390fc3..f4178c308eb 100644 --- a/acceptance/bundle/deploy/mlops-stacks/test.toml +++ b/acceptance/bundle/deploy/mlops-stacks/test.toml @@ -1,5 +1,5 @@ Cloud=true -Local=false +Local=true Badness = "the newly initialized bundle from the 'mlops-stacks' template contains two validation warnings in the configuration" @@ -7,14 +7,6 @@ Ignore = [ "config.json" ] -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] -# On direct, this fails with -# +Endpoint: PUT [DATABRICKS_URL]/api/2.0/permissions/registered-models/%5Bdev%20[USERNAME]%5D%20dev-project_name_[UNIQUE_NAME]-model -# +HTTP Status: 400 Bad Request -# +API error_code: INVALID_PARAMETER_VALUE -# +API message: '[dev [USERNAME]] dev-project_name_[UNIQUE_NAME]-model' is not a valid registered model ID. - - [[Repls]] Old = "aws|azure|gcp" New = "[CLOUD_ENV_BASE]" diff --git a/acceptance/bundle/deploy/pipeline-config-dots/databricks.yml b/acceptance/bundle/deploy/pipeline-config-dots/databricks.yml new file mode 100644 index 00000000000..d2e5139504d --- /dev/null +++ b/acceptance/bundle/deploy/pipeline-config-dots/databricks.yml @@ -0,0 +1,24 @@ +bundle: + name: test-bundle + +variables: + AZURE: + type: complex + default: + subscription: test-sub-123 + +resources: + schemas: + my_schema: + catalog_name: main + name: test-schema + + pipelines: + my_pipeline: + name: test-pipeline + libraries: + - file: + path: pipeline.py + configuration: + europris.swipe.egress_streaming_schema: "${resources.schemas.my_schema.catalog_name}.${resources.schemas.my_schema.name}" + europris.azure.subscription: ${var.AZURE.subscription} diff --git a/acceptance/bundle/deploy/pipeline-config-dots/out.test.toml b/acceptance/bundle/deploy/pipeline-config-dots/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/deploy/pipeline-config-dots/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/pipeline-config-dots/output.txt b/acceptance/bundle/deploy/pipeline-config-dots/output.txt new file mode 100644 index 00000000000..48b6576f221 --- /dev/null +++ b/acceptance/bundle/deploy/pipeline-config-dots/output.txt @@ -0,0 +1,32 @@ + +>>> [CLI] bundle validate +Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default + +Validation OK! + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.pipelines.my_pipeline + delete resources.schemas.my_schema + +This action will result in the deletion of the following UC schemas. Any underlying data may be lost: + delete resources.schemas.my_schema + +This action will result in the deletion of the following Lakeflow Spark Declarative Pipelines along with the +Streaming Tables (STs) and Materialized Views (MVs) managed by them: + delete resources.pipelines.my_pipeline + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/deploy/pipeline-config-dots/pipeline.py b/acceptance/bundle/deploy/pipeline-config-dots/pipeline.py new file mode 100644 index 00000000000..2ae28399f5f --- /dev/null +++ b/acceptance/bundle/deploy/pipeline-config-dots/pipeline.py @@ -0,0 +1 @@ +pass diff --git a/acceptance/bundle/deploy/pipeline-config-dots/script b/acceptance/bundle/deploy/pipeline-config-dots/script new file mode 100644 index 00000000000..82f07ae34dc --- /dev/null +++ b/acceptance/bundle/deploy/pipeline-config-dots/script @@ -0,0 +1,8 @@ +trace $CLI bundle validate + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +trace $CLI bundle deploy diff --git a/acceptance/bundle/deploy/python-notebook/out.test.toml b/acceptance/bundle/deploy/python-notebook/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deploy/python-notebook/out.test.toml +++ b/acceptance/bundle/deploy/python-notebook/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/readplan/basic/out.test.toml b/acceptance/bundle/deploy/readplan/basic/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/basic/out.test.toml +++ b/acceptance/bundle/deploy/readplan/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/cli-version-mismatch/out.test.toml b/acceptance/bundle/deploy/readplan/cli-version-mismatch/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/cli-version-mismatch/out.test.toml +++ b/acceptance/bundle/deploy/readplan/cli-version-mismatch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/invalid-plan/out.test.toml b/acceptance/bundle/deploy/readplan/invalid-plan/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/invalid-plan/out.test.toml +++ b/acceptance/bundle/deploy/readplan/invalid-plan/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/lineage-mismatch/out.test.toml b/acceptance/bundle/deploy/readplan/lineage-mismatch/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/lineage-mismatch/out.test.toml +++ b/acceptance/bundle/deploy/readplan/lineage-mismatch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/plan-not-found/out.test.toml b/acceptance/bundle/deploy/readplan/plan-not-found/out.test.toml index a84c0304e60..f7c4cf648a9 100644 --- a/acceptance/bundle/deploy/readplan/plan-not-found/out.test.toml +++ b/acceptance/bundle/deploy/readplan/plan-not-found/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/plan-version-mismatch/out.test.toml b/acceptance/bundle/deploy/readplan/plan-version-mismatch/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/plan-version-mismatch/out.test.toml +++ b/acceptance/bundle/deploy/readplan/plan-version-mismatch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/serial-mismatch/out.test.toml b/acceptance/bundle/deploy/readplan/serial-mismatch/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/serial-mismatch/out.test.toml +++ b/acceptance/bundle/deploy/readplan/serial-mismatch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/readplan/terraform-error/out.test.toml b/acceptance/bundle/deploy/readplan/terraform-error/out.test.toml index 90061dedb10..65156e0457c 100644 --- a/acceptance/bundle/deploy/readplan/terraform-error/out.test.toml +++ b/acceptance/bundle/deploy/readplan/terraform-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/bundle/deploy/readplan/unknown-field/out.test.toml b/acceptance/bundle/deploy/readplan/unknown-field/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deploy/readplan/unknown-field/out.test.toml +++ b/acceptance/bundle/deploy/readplan/unknown-field/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/snapshot-comparison/out.test.toml b/acceptance/bundle/deploy/snapshot-comparison/out.test.toml index a9f28de48a5..42c0997090a 100644 --- a/acceptance/bundle/deploy/snapshot-comparison/out.test.toml +++ b/acceptance/bundle/deploy/snapshot-comparison/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/bundle/deploy/spark-jar-task/databricks.yml.tmpl b/acceptance/bundle/deploy/spark-jar-task/databricks.yml.tmpl new file mode 100644 index 00000000000..9078bcbdbbd --- /dev/null +++ b/acceptance/bundle/deploy/spark-jar-task/databricks.yml.tmpl @@ -0,0 +1,26 @@ +bundle: + name: spark-jar-task-$UNIQUE_NAME + +artifacts: + my_java_code: + path: ./myjar + build: "javac PrintArgs.java && jar cvfm PrintArgs.jar META-INF/MANIFEST.MF PrintArgs.class" + files: + - source: ./myjar/PrintArgs.jar + +resources: + jobs: + jar_job: + name: "[${bundle.target}] Test Spark Jar Job $UNIQUE_NAME" + tasks: + - task_key: TestSparkJarTask + new_cluster: + num_workers: 1 + spark_version: 16.4.x-scala2.12 + node_type_id: $NODE_TYPE_ID + instance_pool_id: $TEST_INSTANCE_POOL_ID + data_security_mode: NONE + spark_jar_task: + main_class_name: PrintArgs + libraries: + - jar: ./myjar/PrintArgs.jar diff --git a/integration/bundle/bundles/spark_jar_task/template/{{.project_name}}/META-INF/MANIFEST.MF b/acceptance/bundle/deploy/spark-jar-task/myjar/META-INF/MANIFEST.MF similarity index 100% rename from integration/bundle/bundles/spark_jar_task/template/{{.project_name}}/META-INF/MANIFEST.MF rename to acceptance/bundle/deploy/spark-jar-task/myjar/META-INF/MANIFEST.MF diff --git a/integration/bundle/bundles/spark_jar_task/template/{{.project_name}}/PrintArgs.java b/acceptance/bundle/deploy/spark-jar-task/myjar/PrintArgs.java similarity index 100% rename from integration/bundle/bundles/spark_jar_task/template/{{.project_name}}/PrintArgs.java rename to acceptance/bundle/deploy/spark-jar-task/myjar/PrintArgs.java diff --git a/acceptance/bundle/deploy/spark-jar-task/out.test.toml b/acceptance/bundle/deploy/spark-jar-task/out.test.toml new file mode 100644 index 00000000000..bbc7fcfd1bd --- /dev/null +++ b/acceptance/bundle/deploy/spark-jar-task/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deploy/spark-jar-task/output.txt b/acceptance/bundle/deploy/spark-jar-task/output.txt new file mode 100644 index 00000000000..3b556fbb60f --- /dev/null +++ b/acceptance/bundle/deploy/spark-jar-task/output.txt @@ -0,0 +1,24 @@ + +>>> [CLI] bundle deploy +Building my_java_code... +Uploading myjar/PrintArgs.jar... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/spark-jar-task-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle run jar_job +Run URL: [DATABRICKS_URL]/?o=[NUMID]#job/[NUMID]/run/[NUMID] + +[TIMESTAMP] "[default] Test Spark Jar Job [UNIQUE_NAME]" RUNNING +[TIMESTAMP] "[default] Test Spark Jar Job [UNIQUE_NAME]" TERMINATED SUCCESS +Hello from Jar! +[] +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.jar_job + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/spark-jar-task-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/deploy/spark-jar-task/script b/acceptance/bundle/deploy/spark-jar-task/script new file mode 100644 index 00000000000..d736ca6bfae --- /dev/null +++ b/acceptance/bundle/deploy/spark-jar-task/script @@ -0,0 +1,4 @@ +envsubst < databricks.yml.tmpl > databricks.yml +trap "errcode trace '$CLI' bundle destroy --auto-approve" EXIT +trace $CLI bundle deploy +trace $CLI bundle run jar_job diff --git a/acceptance/bundle/deploy/spark-jar-task/test.toml b/acceptance/bundle/deploy/spark-jar-task/test.toml new file mode 100644 index 00000000000..86ea49e050c --- /dev/null +++ b/acceptance/bundle/deploy/spark-jar-task/test.toml @@ -0,0 +1,17 @@ +Local = true +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] + +Ignore = [ + 'myjar/PrintArgs.jar', + 'myjar/PrintArgs.class', +] + +[[Server]] +Pattern = "GET /api/2.2/jobs/runs/get-output" +Response.Body = ''' +{ + "run_id": 1234567890, + "logs": "Hello from Jar!\n[]" +} +''' diff --git a/acceptance/bundle/deployment/bind/alert/out.test.toml b/acceptance/bundle/deployment/bind/alert/out.test.toml index ce10602d555..d47ff541e4b 100644 --- a/acceptance/bundle/deployment/bind/alert/out.test.toml +++ b/acceptance/bundle/deployment/bind/alert/out.test.toml @@ -1,8 +1,4 @@ Local = false Cloud = true - -[CloudEnvs] - aws = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.aws = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/catalog/out.test.toml b/acceptance/bundle/deployment/bind/catalog/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/deployment/bind/catalog/out.test.toml +++ b/acceptance/bundle/deployment/bind/catalog/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/cluster/out.test.toml b/acceptance/bundle/deployment/bind/cluster/out.test.toml index e28f5202340..f61486ff080 100644 --- a/acceptance/bundle/deployment/bind/cluster/out.test.toml +++ b/acceptance/bundle/deployment/bind/cluster/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresCluster = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/dashboard/out.test.toml b/acceptance/bundle/deployment/bind/dashboard/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/deployment/bind/dashboard/out.test.toml +++ b/acceptance/bundle/deployment/bind/dashboard/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/dashboard/recreation/out.state_after_bind.terraform.json b/acceptance/bundle/deployment/bind/dashboard/recreation/out.state_after_bind.terraform.json index fe6ee8d4b44..4eb65c32129 100644 --- a/acceptance/bundle/deployment/bind/dashboard/recreation/out.state_after_bind.terraform.json +++ b/acceptance/bundle/deployment/bind/dashboard/recreation/out.state_after_bind.terraform.json @@ -20,7 +20,7 @@ "dataset_catalog": null, "dataset_schema": null, "display_name": "test dashboard [UNIQUE_NAME]", - "embed_credentials": null, + "embed_credentials": false, "etag": [ETAG], "file_path": null, "id": "[DASHBOARD_ID]", diff --git a/acceptance/bundle/deployment/bind/dashboard/recreation/out.test.toml b/acceptance/bundle/deployment/bind/dashboard/recreation/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/deployment/bind/dashboard/recreation/out.test.toml +++ b/acceptance/bundle/deployment/bind/dashboard/recreation/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/database_instance/out.test.toml b/acceptance/bundle/deployment/bind/database_instance/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/database_instance/out.test.toml +++ b/acceptance/bundle/deployment/bind/database_instance/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/experiment/out.test.toml b/acceptance/bundle/deployment/bind/experiment/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/bind/experiment/out.test.toml +++ b/acceptance/bundle/deployment/bind/experiment/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/external_location/out.test.toml b/acceptance/bundle/deployment/bind/external_location/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/deployment/bind/external_location/out.test.toml +++ b/acceptance/bundle/deployment/bind/external_location/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/job/already-managed-different/out.test.toml b/acceptance/bundle/deployment/bind/job/already-managed-different/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/job/already-managed-different/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/already-managed-different/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/already-managed-same/out.test.toml b/acceptance/bundle/deployment/bind/job/already-managed-same/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/job/already-managed-same/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/already-managed-same/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/engine-from-config/out.test.toml b/acceptance/bundle/deployment/bind/job/engine-from-config/out.test.toml index d3e35285f1c..d6187dcb046 100644 --- a/acceptance/bundle/deployment/bind/job/engine-from-config/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/engine-from-config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = [] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/bundle/deployment/bind/job/generate-and-bind/out.test.toml b/acceptance/bundle/deployment/bind/job/generate-and-bind/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/deployment/bind/job/generate-and-bind/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/generate-and-bind/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/job-abort-bind/out.test.toml b/acceptance/bundle/deployment/bind/job/job-abort-bind/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/bind/job/job-abort-bind/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/job-abort-bind/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/job-spark-python-task/out.test.toml b/acceptance/bundle/deployment/bind/job/job-spark-python-task/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/bind/job/job-spark-python-task/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/job-spark-python-task/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/noop-job/out.job.direct.json b/acceptance/bundle/deployment/bind/job/noop-job/out.job.direct.json index f5661025453..80509b893bd 100644 --- a/acceptance/bundle/deployment/bind/job/noop-job/out.job.direct.json +++ b/acceptance/bundle/deployment/bind/job/noop-job/out.job.direct.json @@ -1,22 +1,22 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"Updated Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Updated Job", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/deployment/bind/job/noop-job/out.job.terraform.json b/acceptance/bundle/deployment/bind/job/noop-job/out.job.terraform.json index 53ac53e874d..99953cf995c 100644 --- a/acceptance/bundle/deployment/bind/job/noop-job/out.job.terraform.json +++ b/acceptance/bundle/deployment/bind/job/noop-job/out.job.terraform.json @@ -1,25 +1,25 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"Updated Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Updated Job", "queue": { - "enabled":true + "enabled": true }, "run_as": { - "user_name":"[USERNAME]" + "user_name": "[USERNAME]" }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/deployment/bind/job/noop-job/out.test.toml b/acceptance/bundle/deployment/bind/job/noop-job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/job/noop-job/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/noop-job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/python-job/out.job.direct.json b/acceptance/bundle/deployment/bind/job/python-job/out.job.direct.json index f5661025453..80509b893bd 100644 --- a/acceptance/bundle/deployment/bind/job/python-job/out.job.direct.json +++ b/acceptance/bundle/deployment/bind/job/python-job/out.job.direct.json @@ -1,22 +1,22 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"Updated Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Updated Job", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/deployment/bind/job/python-job/out.job.terraform.json b/acceptance/bundle/deployment/bind/job/python-job/out.job.terraform.json index 53ac53e874d..99953cf995c 100644 --- a/acceptance/bundle/deployment/bind/job/python-job/out.job.terraform.json +++ b/acceptance/bundle/deployment/bind/job/python-job/out.job.terraform.json @@ -1,25 +1,25 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"Updated Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Updated Job", "queue": { - "enabled":true + "enabled": true }, "run_as": { - "user_name":"[USERNAME]" + "user_name": "[USERNAME]" }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/deployment/bind/job/python-job/out.test.toml b/acceptance/bundle/deployment/bind/job/python-job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/job/python-job/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/python-job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/job/stale-state/out.test.toml b/acceptance/bundle/deployment/bind/job/stale-state/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/job/stale-state/out.test.toml +++ b/acceptance/bundle/deployment/bind/job/stale-state/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/model-serving-endpoint/out.test.toml b/acceptance/bundle/deployment/bind/model-serving-endpoint/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/bind/model-serving-endpoint/out.test.toml +++ b/acceptance/bundle/deployment/bind/model-serving-endpoint/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/pipelines/recreate/out.test.toml b/acceptance/bundle/deployment/bind/pipelines/recreate/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/bind/pipelines/recreate/out.test.toml +++ b/acceptance/bundle/deployment/bind/pipelines/recreate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.test.toml b/acceptance/bundle/deployment/bind/pipelines/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.test.toml +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/quality-monitor/out.test.toml b/acceptance/bundle/deployment/bind/quality-monitor/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/quality-monitor/out.test.toml +++ b/acceptance/bundle/deployment/bind/quality-monitor/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/quality-monitor/output.txt b/acceptance/bundle/deployment/bind/quality-monitor/output.txt index 1cd120d91d6..30e2790c9eb 100644 --- a/acceptance/bundle/deployment/bind/quality-monitor/output.txt +++ b/acceptance/bundle/deployment/bind/quality-monitor/output.txt @@ -1,15 +1,15 @@ >>> [CLI] quality-monitors create catalog.schema.table --json @input.json { - "assets_dir":"/Users/user/databricks_lakehouse_monitoring", - "dashboard_id":"(redacted)", - "drift_metrics_table_name":"catalog.schema.table_drift_metrics", - "monitor_version":0, - "output_schema_name":"catalog.schema", - "profile_metrics_table_name":"catalog.schema.table_profile_metrics", + "assets_dir": "/Users/user/databricks_lakehouse_monitoring", + "dashboard_id": "(redacted)", + "drift_metrics_table_name": "catalog.schema.table_drift_metrics", + "monitor_version": 0, + "output_schema_name": "catalog.schema", + "profile_metrics_table_name": "catalog.schema.table_profile_metrics", "snapshot": {}, - "status":"MONITOR_STATUS_ACTIVE", - "table_name":"catalog.schema.table" + "status": "MONITOR_STATUS_ACTIVE", + "table_name": "catalog.schema.table" } >>> [CLI] bundle deployment bind monitor1 catalog.schema.table diff --git a/acceptance/bundle/deployment/bind/quality-monitor/test.toml b/acceptance/bundle/deployment/bind/quality-monitor/test.toml index bc3149360b6..0fe8781c058 100644 --- a/acceptance/bundle/deployment/bind/quality-monitor/test.toml +++ b/acceptance/bundle/deployment/bind/quality-monitor/test.toml @@ -2,5 +2,5 @@ Local = true Cloud = false [[Repls]] -Old = '"dashboard_id":"[0-9a-f]+",' -New = '"dashboard_id":"(redacted)",' +Old = '"dashboard_id": "[0-9a-f]+",' +New = '"dashboard_id": "(redacted)",' diff --git a/acceptance/bundle/deployment/bind/registered-model/out.test.toml b/acceptance/bundle/deployment/bind/registered-model/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/deployment/bind/registered-model/out.test.toml +++ b/acceptance/bundle/deployment/bind/registered-model/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/schema/out.test.toml b/acceptance/bundle/deployment/bind/schema/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/deployment/bind/schema/out.test.toml +++ b/acceptance/bundle/deployment/bind/schema/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/secret-scope/out.test.toml b/acceptance/bundle/deployment/bind/secret-scope/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/deployment/bind/secret-scope/out.test.toml +++ b/acceptance/bundle/deployment/bind/secret-scope/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/sql_warehouse/out.test.toml b/acceptance/bundle/deployment/bind/sql_warehouse/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/bind/sql_warehouse/out.test.toml +++ b/acceptance/bundle/deployment/bind/sql_warehouse/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/bind/vector_search_endpoint/databricks.yml.tmpl b/acceptance/bundle/deployment/bind/vector_search_endpoint/databricks.yml.tmpl new file mode 100644 index 00000000000..b523fc5790a --- /dev/null +++ b/acceptance/bundle/deployment/bind/vector_search_endpoint/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + endpoint1: + name: $ENDPOINT_NAME + endpoint_type: STANDARD diff --git a/acceptance/bundle/deployment/bind/vector_search_endpoint/out.test.toml b/acceptance/bundle/deployment/bind/vector_search_endpoint/out.test.toml new file mode 100644 index 00000000000..fe4076cdf9b --- /dev/null +++ b/acceptance/bundle/deployment/bind/vector_search_endpoint/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/vector_search_endpoint/output.txt b/acceptance/bundle/deployment/bind/vector_search_endpoint/output.txt new file mode 100644 index 00000000000..2a731b4827e --- /dev/null +++ b/acceptance/bundle/deployment/bind/vector_search_endpoint/output.txt @@ -0,0 +1,43 @@ + +>>> [CLI] vector-search-endpoints create-endpoint test-vs-endpoint-[UNIQUE_NAME] STANDARD +{ + "id": "[UUID]", + "name": "test-vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] bundle deployment bind endpoint1 test-vs-endpoint-[UNIQUE_NAME] --auto-approve +Updating deployment state... +Successfully bound vector_search_endpoint with an id 'test-vs-endpoint-[UNIQUE_NAME]' +Run 'bundle deploy' to deploy changes to your workspace + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] vector-search-endpoints get-endpoint test-vs-endpoint-[UNIQUE_NAME] +{ + "id": "[UUID]", + "name": "test-vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] bundle deployment unbind endpoint1 +Updating deployment state... + +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! + +>>> [CLI] vector-search-endpoints get-endpoint test-vs-endpoint-[UNIQUE_NAME] +{ + "id": "[UUID]", + "name": "test-vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] vector-search-endpoints delete-endpoint test-vs-endpoint-[UNIQUE_NAME] diff --git a/acceptance/bundle/deployment/bind/vector_search_endpoint/script b/acceptance/bundle/deployment/bind/vector_search_endpoint/script new file mode 100644 index 00000000000..bf45cbea784 --- /dev/null +++ b/acceptance/bundle/deployment/bind/vector_search_endpoint/script @@ -0,0 +1,23 @@ +ENDPOINT_NAME="test-vs-endpoint-$UNIQUE_NAME" +export ENDPOINT_NAME +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI vector-search-endpoints delete-endpoint "${ENDPOINT_NAME}" +} +trap cleanup EXIT + +trace $CLI vector-search-endpoints create-endpoint "${ENDPOINT_NAME}" STANDARD | jq '{id, name, endpoint_type}' + +trace $CLI bundle deployment bind endpoint1 "${ENDPOINT_NAME}" --auto-approve + +trace $CLI bundle deploy --auto-approve + +trace $CLI vector-search-endpoints get-endpoint "${ENDPOINT_NAME}" | jq '{id, name, endpoint_type}' + +trace $CLI bundle deployment unbind endpoint1 + +trace $CLI bundle destroy --auto-approve + +# Read the pre-defined endpoint again (expecting it still exists and is not deleted): +trace $CLI vector-search-endpoints get-endpoint "${ENDPOINT_NAME}" | jq '{id, name, endpoint_type}' diff --git a/acceptance/bundle/deployment/bind/vector_search_endpoint/test.toml b/acceptance/bundle/deployment/bind/vector_search_endpoint/test.toml new file mode 100644 index 00000000000..5722b37ccca --- /dev/null +++ b/acceptance/bundle/deployment/bind/vector_search_endpoint/test.toml @@ -0,0 +1,11 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +Ignore = [ + ".databricks", + "databricks.yml", +] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deployment/bind/volume/out.test.toml b/acceptance/bundle/deployment/bind/volume/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/deployment/bind/volume/out.test.toml +++ b/acceptance/bundle/deployment/bind/volume/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/unbind/engine-from-config/out.test.toml b/acceptance/bundle/deployment/unbind/engine-from-config/out.test.toml index d3e35285f1c..d6187dcb046 100644 --- a/acceptance/bundle/deployment/unbind/engine-from-config/out.test.toml +++ b/acceptance/bundle/deployment/unbind/engine-from-config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = [] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/bundle/deployment/unbind/grants/out.test.toml b/acceptance/bundle/deployment/unbind/grants/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/deployment/unbind/grants/out.test.toml +++ b/acceptance/bundle/deployment/unbind/grants/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/unbind/job/out.test.toml b/acceptance/bundle/deployment/unbind/job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/unbind/job/out.test.toml +++ b/acceptance/bundle/deployment/unbind/job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/unbind/job/output.txt b/acceptance/bundle/deployment/unbind/job/output.txt index 0a890f923ab..e1c72de2d4f 100644 --- a/acceptance/bundle/deployment/unbind/job/output.txt +++ b/acceptance/bundle/deployment/unbind/job/output.txt @@ -18,24 +18,24 @@ Deployment complete! >>> [CLI] jobs get [NUMID] --output json { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"My Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "My Job", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/deployment/unbind/permissions/out.test.toml b/acceptance/bundle/deployment/unbind/permissions/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/deployment/unbind/permissions/out.test.toml +++ b/acceptance/bundle/deployment/unbind/permissions/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/unbind/python-job/out.test.toml b/acceptance/bundle/deployment/unbind/python-job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/deployment/unbind/python-job/out.test.toml +++ b/acceptance/bundle/deployment/unbind/python-job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/deployment/unbind/python-job/output.txt b/acceptance/bundle/deployment/unbind/python-job/output.txt index f041d38dc6b..75ff185b35c 100644 --- a/acceptance/bundle/deployment/unbind/python-job/output.txt +++ b/acceptance/bundle/deployment/unbind/python-job/output.txt @@ -18,24 +18,24 @@ Deployment complete! >>> [CLI] jobs get [NUMID] --output json { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[NUMID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [NUMID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"My Job", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "My Job", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/destroy/all-resources/out.test.toml b/acceptance/bundle/destroy/all-resources/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/destroy/all-resources/out.test.toml +++ b/acceptance/bundle/destroy/all-resources/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/destroy/jobs-and-pipeline/out.test.toml b/acceptance/bundle/destroy/jobs-and-pipeline/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/destroy/jobs-and-pipeline/out.test.toml +++ b/acceptance/bundle/destroy/jobs-and-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/environments/dependencies/out.test.toml b/acceptance/bundle/environments/dependencies/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/environments/dependencies/out.test.toml +++ b/acceptance/bundle/environments/dependencies/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/experimental/skip_name_prefix_for_schema/out.test.toml b/acceptance/bundle/experimental/skip_name_prefix_for_schema/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/experimental/skip_name_prefix_for_schema/out.test.toml +++ b/acceptance/bundle/experimental/skip_name_prefix_for_schema/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/alert/out.test.toml b/acceptance/bundle/generate/alert/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/generate/alert/out.test.toml +++ b/acceptance/bundle/generate/alert/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/alert_existing_id_not_found/out.test.toml b/acceptance/bundle/generate/alert_existing_id_not_found/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/alert_existing_id_not_found/out.test.toml +++ b/acceptance/bundle/generate/alert_existing_id_not_found/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/app_not_yet_deployed/out.test.toml b/acceptance/bundle/generate/app_not_yet_deployed/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/out.test.toml +++ b/acceptance/bundle/generate/app_not_yet_deployed/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/app_not_yet_deployed/output.txt b/acceptance/bundle/generate/app_not_yet_deployed/output.txt index feb98e91081..8c0e62433e5 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/output.txt +++ b/acceptance/bundle/generate/app_not_yet_deployed/output.txt @@ -1,20 +1,21 @@ ->>> [CLI] apps create my-app +>>> [CLI] apps create my-app --no-compute --no-wait { "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is stopped.", + "state": "STOPPED" }, - "id":"1000", - "name":"my-app", - "service_principal_client_id":"[UUID]", - "service_principal_id":[NUMID], - "service_principal_name":"app-my-app", - "url":"my-app-123.cloud.databricksapps.com" + "id": "1000", + "name": "my-app", + "service_principal_client_id": "[UUID]", + "service_principal_id": [NUMID], + "service_principal_name": "app-my-app", + "url": "my-app-123.cloud.databricksapps.com" } >>> [CLI] bundle generate app --existing-app-name my-app --config-dir . --key out diff --git a/acceptance/bundle/generate/app_not_yet_deployed/script b/acceptance/bundle/generate/app_not_yet_deployed/script index f9521c5717d..8883d05d15d 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/script +++ b/acceptance/bundle/generate/app_not_yet_deployed/script @@ -1,2 +1,2 @@ -trace $CLI apps create my-app +trace $CLI apps create my-app --no-compute --no-wait trace $CLI bundle generate app --existing-app-name my-app --config-dir . --key out diff --git a/acceptance/bundle/generate/app_subfolders/out.test.toml b/acceptance/bundle/generate/app_subfolders/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/app_subfolders/out.test.toml +++ b/acceptance/bundle/generate/app_subfolders/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/app_subfolders/test.toml b/acceptance/bundle/generate/app_subfolders/test.toml index 5721c4972f6..6fecc1cb749 100644 --- a/acceptance/bundle/generate/app_subfolders/test.toml +++ b/acceptance/bundle/generate/app_subfolders/test.toml @@ -5,7 +5,7 @@ Response.Body = ''' "app_id": "1234567890", "name": "my_app", "description": "This is a test app", - "default_source_code_path": "/Workspace/Users/foo@bar.com/my_app" + "default_source_code_path": "/Workspace/Users/foo@bar.test/my_app" } ''' @@ -15,7 +15,7 @@ Response.Body = ''' { "objects": [ { - "path": "/Workspace/Users/foo@bar.com/my_app/sub/folder/1.py", + "path": "/Workspace/Users/foo@bar.test/my_app/sub/folder/1.py", "object_type": "FILE" } ] @@ -25,7 +25,7 @@ Response.Body = ''' Pattern = "GET /api/2.0/workspace/get-status" Response.Body = ''' { - "path": "/Workspace/Users/foo@bar.com/my_app/sub/folder/1.py", + "path": "/Workspace/Users/foo@bar.test/my_app/sub/folder/1.py", "object_type": "FILE" } ''' diff --git a/acceptance/bundle/generate/auto-bind/out.test.toml b/acceptance/bundle/generate/auto-bind/out.test.toml index 3cdb920b677..14e5fee5e76 100644 --- a/acceptance/bundle/generate/auto-bind/out.test.toml +++ b/acceptance/bundle/generate/auto-bind/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/bundle/generate/dashboard-inplace/out.test.toml b/acceptance/bundle/generate/dashboard-inplace/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/dashboard-inplace/out.test.toml +++ b/acceptance/bundle/generate/dashboard-inplace/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/dashboard-inplace/output.txt b/acceptance/bundle/generate/dashboard-inplace/output.txt index cb2027af917..90d054197aa 100644 --- a/acceptance/bundle/generate/dashboard-inplace/output.txt +++ b/acceptance/bundle/generate/dashboard-inplace/output.txt @@ -12,16 +12,16 @@ Deployment complete! === update the dashboard >>> [CLI] lakeview update [DASHBOARD_ID] --serialized-dashboard {"a":"b"} { - "create_time":"[TIMESTAMP]", - "dashboard_id":"[DASHBOARD_ID]", - "display_name":"test dashboard", - "etag":"[NUMID]", - "lifecycle_state":"ACTIVE", - "parent_path":"/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources", - "path":"/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources/test dashboard.lvdash.json", - "serialized_dashboard":"{\"a\":\"b\"}\n", - "update_time":"[TIMESTAMP]", - "warehouse_id":"" + "create_time": "[TIMESTAMP]", + "dashboard_id": "[DASHBOARD_ID]", + "display_name": "test dashboard", + "etag": "[NUMID]", + "lifecycle_state": "ACTIVE", + "parent_path": "/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources", + "path": "/Users/[USERNAME]/.bundle/dashboard update inplace/default/resources/test dashboard.lvdash.json", + "serialized_dashboard": "{\"a\":\"b\"}\n", + "update_time": "[TIMESTAMP]", + "warehouse_id": "" } === update the dashboard file using bundle generate diff --git a/acceptance/bundle/generate/dashboard/out.test.toml b/acceptance/bundle/generate/dashboard/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/dashboard/out.test.toml +++ b/acceptance/bundle/generate/dashboard/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/dashboard_existing_id_not_found/out.test.toml b/acceptance/bundle/generate/dashboard_existing_id_not_found/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/dashboard_existing_id_not_found/out.test.toml +++ b/acceptance/bundle/generate/dashboard_existing_id_not_found/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/dashboard_existing_path_nominal/out.test.toml b/acceptance/bundle/generate/dashboard_existing_path_nominal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/dashboard_existing_path_nominal/out.test.toml +++ b/acceptance/bundle/generate/dashboard_existing_path_nominal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/dashboard_existing_path_not_found/out.test.toml b/acceptance/bundle/generate/dashboard_existing_path_not_found/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/dashboard_existing_path_not_found/out.test.toml +++ b/acceptance/bundle/generate/dashboard_existing_path_not_found/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/git_job/out.job.yml b/acceptance/bundle/generate/git_job/out.job.yml index 0eb2a3fb1fd..7142a074428 100644 --- a/acceptance/bundle/generate/git_job/out.job.yml +++ b/acceptance/bundle/generate/git_job/out.job.yml @@ -8,7 +8,7 @@ resources: notebook_path: some/test/notebook.py - task_key: test_task_2 notebook_task: - notebook_path: /Workspace/Users/foo@bar.com/some/test/notebook.py + notebook_path: /Workspace/Users/foo@bar.test/some/test/notebook.py source: WORKSPACE git_source: git_branch: main diff --git a/acceptance/bundle/generate/git_job/out.test.toml b/acceptance/bundle/generate/git_job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/git_job/out.test.toml +++ b/acceptance/bundle/generate/git_job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/git_job/test.toml b/acceptance/bundle/generate/git_job/test.toml index 07c62e678d4..c432342647d 100644 --- a/acceptance/bundle/generate/git_job/test.toml +++ b/acceptance/bundle/generate/git_job/test.toml @@ -22,7 +22,7 @@ Response.Body = ''' "task_key": "test_task_2", "notebook_task": { "source": "WORKSPACE", - "notebook_path": "/Workspace/Users/foo@bar.com/some/test/notebook.py" + "notebook_path": "/Workspace/Users/foo@bar.test/some/test/notebook.py" } } ] diff --git a/acceptance/bundle/generate/ipynb_job/out.test.toml b/acceptance/bundle/generate/ipynb_job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/ipynb_job/out.test.toml +++ b/acceptance/bundle/generate/ipynb_job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/job_nested_notebooks/databricks.yml b/acceptance/bundle/generate/job_nested_notebooks/databricks.yml new file mode 100644 index 00000000000..3331ecc8493 --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: nested_notebooks diff --git a/acceptance/bundle/generate/job_nested_notebooks/out.job.yml b/acceptance/bundle/generate/job_nested_notebooks/out.job.yml new file mode 100644 index 00000000000..83a90b6298d --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/out.job.yml @@ -0,0 +1,11 @@ +resources: + jobs: + out: + name: dev.my_repo.my_job + tasks: + - task_key: my_notebook_task + notebook_task: + notebook_path: src/my_folder/my_notebook.py + - task_key: other_notebook_task + notebook_task: + notebook_path: src/other_folder/other_notebook.py diff --git a/acceptance/bundle/generate/job_nested_notebooks/out.test.toml b/acceptance/bundle/generate/job_nested_notebooks/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/job_nested_notebooks/output.txt b/acceptance/bundle/generate/job_nested_notebooks/output.txt new file mode 100644 index 00000000000..f0b8326de09 --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/output.txt @@ -0,0 +1,9 @@ +File successfully saved to src/my_folder/my_notebook.py +File successfully saved to src/other_folder/other_notebook.py +Job configuration successfully saved to out.job.yml +=== old flattened files should be gone === +src/my_notebook.py removed +src/other_notebook.py removed +=== new nested files === +src/my_folder/my_notebook.py +src/other_folder/other_notebook.py diff --git a/acceptance/bundle/generate/job_nested_notebooks/script b/acceptance/bundle/generate/job_nested_notebooks/script new file mode 100644 index 00000000000..04805db6389 --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/script @@ -0,0 +1,12 @@ +mkdir -p src +echo "old" > src/my_notebook.py +echo "old" > src/other_notebook.py + +$CLI bundle generate job --existing-job-id 1234 --config-dir . --key out --force --source-dir src 2>&1 | sort + +echo "=== old flattened files should be gone ===" +test ! -f src/my_notebook.py && echo "src/my_notebook.py removed" || echo "src/my_notebook.py still exists" +test ! -f src/other_notebook.py && echo "src/other_notebook.py removed" || echo "src/other_notebook.py still exists" + +echo "=== new nested files ===" +find src -type f | sort diff --git a/acceptance/bundle/generate/job_nested_notebooks/test.toml b/acceptance/bundle/generate/job_nested_notebooks/test.toml new file mode 100644 index 00000000000..bdb350e53f0 --- /dev/null +++ b/acceptance/bundle/generate/job_nested_notebooks/test.toml @@ -0,0 +1,42 @@ +Ignore = ["src"] + +[[Server]] +Pattern = "GET /api/2.2/jobs/get" +Response.Body = ''' +{ + "job_id": 11223344, + "settings": { + "name": "dev.my_repo.my_job", + "tasks": [ + { + "task_key": "my_notebook_task", + "notebook_task": { + "notebook_path": "/my_data_product/dev/my_folder/my_notebook" + } + }, + { + "task_key": "other_notebook_task", + "notebook_task": { + "notebook_path": "/my_data_product/dev/other_folder/other_notebook" + } + } + ] + } +} +''' + +[[Server]] +Pattern = "GET /api/2.0/workspace/get-status" +Response.Body = ''' +{ + "object_type": "NOTEBOOK", + "language": "PYTHON", + "repos_export_format": "SOURCE" +} +''' + +[[Server]] +Pattern = "GET /api/2.0/workspace/export" +Response.Body = ''' +print("Hello, World!") +''' diff --git a/acceptance/bundle/generate/lakeflow_pipelines/out.test.toml b/acceptance/bundle/generate/lakeflow_pipelines/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/lakeflow_pipelines/out.test.toml +++ b/acceptance/bundle/generate/lakeflow_pipelines/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/pipeline/out.test.toml b/acceptance/bundle/generate/pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/pipeline/out.test.toml +++ b/acceptance/bundle/generate/pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/pipeline_with_sql/out.test.toml b/acceptance/bundle/generate/pipeline_with_sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/pipeline_with_sql/out.test.toml +++ b/acceptance/bundle/generate/pipeline_with_sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/python_job/out.test.toml b/acceptance/bundle/generate/python_job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/generate/python_job/out.test.toml +++ b/acceptance/bundle/generate/python_job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/python_job_and_deploy/databricks.yml b/acceptance/bundle/generate/python_job_and_deploy/databricks.yml new file mode 100644 index 00000000000..d7f9b4f9454 --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: python_job_and_deploy diff --git a/acceptance/bundle/generate/python_job_and_deploy/out.test.toml b/acceptance/bundle/generate/python_job_and_deploy/out.test.toml new file mode 100644 index 00000000000..bbc7fcfd1bd --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/generate/python_job_and_deploy/output.txt b/acceptance/bundle/generate/python_job_and_deploy/output.txt new file mode 100644 index 00000000000..418e008c535 --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/output.txt @@ -0,0 +1,29 @@ + +=== Upload notebook to a workspace path +>>> [CLI] workspace import /Workspace/Users/[USERNAME]/test_notebook.py --file test_notebook.py --format AUTO --overwrite + +=== Create a job that references the notebookCreated job + +=== Generate bundle config from the job +>>> [CLI] bundle generate job --existing-job-id [JOB_ID] --key out --config-dir resources --source-dir src --force +File successfully saved to src/test_notebook.py +Job configuration successfully saved to resources/out.job.yml + +=== Verify generated yaml has expected fields +=== Deploy the generated bundle +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/python_job_and_deploy/default/files... +Deploying resources... +Deployment complete! + +=== Destroy the deployed bundle +>>> [CLI] bundle destroy --auto-approve +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/python_job_and_deploy/default + +Deleting files... +Destroy complete! + +=== Cleanup: delete the original job and notebook +>>> errcode [CLI] jobs delete [JOB_ID] + +>>> errcode [CLI] workspace delete /Workspace/Users/[USERNAME]/test_notebook diff --git a/acceptance/bundle/generate/python_job_and_deploy/script b/acceptance/bundle/generate/python_job_and_deploy/script new file mode 100644 index 00000000000..bf40ed318dc --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/script @@ -0,0 +1,40 @@ +title "Upload notebook to a workspace path" +trace $CLI workspace import "/Workspace/Users/${CURRENT_USER_NAME}/test_notebook.py" --file test_notebook.py --format AUTO --overwrite + +title "Create a job that references the notebook" +JOB_ID=$($CLI jobs create --json '{ + "name": "test-job", + "max_concurrent_runs": 1, + "queue": {"enabled": true}, + "tasks": [ + { + "task_key": "test_task", + "notebook_task": { + "notebook_path": "/Workspace/Users/'${CURRENT_USER_NAME}'/test_notebook" + } + } + ] +}' | jq -r '.job_id') +echo "Created job" +# Disable MSYS_NO_PATHCONV when invoking python scripts: with it set, Git Bash on Windows +# fails to translate the script path so the python interpreter can't find the file. +env -u MSYS_NO_PATHCONV add_repl.py "$JOB_ID" JOB_ID + +cleanup() { + title "Cleanup: delete the original job and notebook" + trace errcode $CLI jobs delete "$JOB_ID" + trace errcode $CLI workspace delete "/Workspace/Users/${CURRENT_USER_NAME}/test_notebook" +} +trap cleanup EXIT + +title "Generate bundle config from the job" +trace $CLI bundle generate job --existing-job-id "$JOB_ID" --key out --config-dir resources --source-dir src --force + +title "Verify generated yaml has expected fields" +cat resources/out.job.yml | env -u MSYS_NO_PATHCONV contains.py "task_key: test_task" "notebook_task:" "notebook_path: ../src/test_notebook.py" > /dev/null + +title "Deploy the generated bundle" +trace $CLI bundle deploy + +title "Destroy the deployed bundle" +trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/generate/python_job_and_deploy/test.toml b/acceptance/bundle/generate/python_job_and_deploy/test.toml new file mode 100644 index 00000000000..3eba85b404a --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/test.toml @@ -0,0 +1,13 @@ +Local = true +Cloud = true + +Ignore = [ + "databricks.yml", + "resources/*", + "src/*", + ".databricks", +] + +[Env] +# MSYS2 automatically converts absolute paths on Windows; disable for the workspace path. +MSYS_NO_PATHCONV = "1" diff --git a/acceptance/bundle/generate/python_job_and_deploy/test_notebook.py b/acceptance/bundle/generate/python_job_and_deploy/test_notebook.py new file mode 100644 index 00000000000..38d86b79c70 --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/test_notebook.py @@ -0,0 +1,2 @@ +# Databricks notebook source +print("Hello, World!") diff --git a/acceptance/bundle/git-permerror/out.test.toml b/acceptance/bundle/git-permerror/out.test.toml index 40bb0d10471..1baaa898c5b 100644 --- a/acceptance/bundle/git-permerror/out.test.toml +++ b/acceptance/bundle/git-permerror/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = false - -[GOOS] - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-deploy/out.test.toml b/acceptance/bundle/help/bundle-deploy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-deploy/out.test.toml +++ b/acceptance/bundle/help/bundle-deploy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-deployment-migrate/out.test.toml b/acceptance/bundle/help/bundle-deployment-migrate/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-deployment-migrate/out.test.toml +++ b/acceptance/bundle/help/bundle-deployment-migrate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-deployment/out.test.toml b/acceptance/bundle/help/bundle-deployment/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-deployment/out.test.toml +++ b/acceptance/bundle/help/bundle-deployment/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-destroy/out.test.toml b/acceptance/bundle/help/bundle-destroy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-destroy/out.test.toml +++ b/acceptance/bundle/help/bundle-destroy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-generate-dashboard/out.test.toml b/acceptance/bundle/help/bundle-generate-dashboard/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-generate-dashboard/out.test.toml +++ b/acceptance/bundle/help/bundle-generate-dashboard/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-generate-job/out.test.toml b/acceptance/bundle/help/bundle-generate-job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-generate-job/out.test.toml +++ b/acceptance/bundle/help/bundle-generate-job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-generate-pipeline/out.test.toml b/acceptance/bundle/help/bundle-generate-pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-generate-pipeline/out.test.toml +++ b/acceptance/bundle/help/bundle-generate-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-generate-pipeline/output.txt b/acceptance/bundle/help/bundle-generate-pipeline/output.txt index 7d0db9a0983..884cd8614e8 100644 --- a/acceptance/bundle/help/bundle-generate-pipeline/output.txt +++ b/acceptance/bundle/help/bundle-generate-pipeline/output.txt @@ -1,13 +1,13 @@ >>> [CLI] bundle generate pipeline --help -Generate bundle configuration for an existing Delta Live Tables pipeline. +Generate bundle configuration for an existing pipeline. -This command downloads an existing Lakeflow Spark Declarative Pipeline's configuration and any associated +This command downloads an existing pipeline's configuration and any associated notebooks, creating bundle files that you can use to deploy the pipeline to other environments or manage it as code. Examples: - # Import a production Lakeflow Spark Declarative Pipeline + # Import a production pipeline databricks bundle generate pipeline --existing-pipeline-id abc123 --key etl_pipeline # Organize files in custom directories diff --git a/acceptance/bundle/help/bundle-generate/out.test.toml b/acceptance/bundle/help/bundle-generate/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-generate/out.test.toml +++ b/acceptance/bundle/help/bundle-generate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-init/out.test.toml b/acceptance/bundle/help/bundle-init/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-init/out.test.toml +++ b/acceptance/bundle/help/bundle-init/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-open/out.test.toml b/acceptance/bundle/help/bundle-open/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-open/out.test.toml +++ b/acceptance/bundle/help/bundle-open/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-open/output.txt b/acceptance/bundle/help/bundle-open/output.txt index 568908f9372..8c5f25db3cb 100644 --- a/acceptance/bundle/help/bundle-open/output.txt +++ b/acceptance/bundle/help/bundle-open/output.txt @@ -4,7 +4,7 @@ Open a deployed bundle resource in the Databricks workspace. Examples: databricks bundle open # Prompts to select a resource to open - databricks bundle open my_job # Open specific job in Workflows UI + databricks bundle open my_job # Open specific job in Jobs UI databricks bundle open my_dashboard # Open dashboard in browser Use after deployment to quickly navigate to your resources in the workspace. diff --git a/acceptance/bundle/help/bundle-run/out.test.toml b/acceptance/bundle/help/bundle-run/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-run/out.test.toml +++ b/acceptance/bundle/help/bundle-run/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-schema/out.test.toml b/acceptance/bundle/help/bundle-schema/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-schema/out.test.toml +++ b/acceptance/bundle/help/bundle-schema/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-summary/out.test.toml b/acceptance/bundle/help/bundle-summary/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-summary/out.test.toml +++ b/acceptance/bundle/help/bundle-summary/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-sync/out.test.toml b/acceptance/bundle/help/bundle-sync/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-sync/out.test.toml +++ b/acceptance/bundle/help/bundle-sync/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle-validate/out.test.toml b/acceptance/bundle/help/bundle-validate/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle-validate/out.test.toml +++ b/acceptance/bundle/help/bundle-validate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/help/bundle/out.test.toml b/acceptance/bundle/help/bundle/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/help/bundle/out.test.toml +++ b/acceptance/bundle/help/bundle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/includes/glob_in_root_path/out.test.toml b/acceptance/bundle/includes/glob_in_root_path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/includes/glob_in_root_path/out.test.toml +++ b/acceptance/bundle/includes/glob_in_root_path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/includes/include_outside_root/out.test.toml b/acceptance/bundle/includes/include_outside_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/includes/include_outside_root/out.test.toml +++ b/acceptance/bundle/includes/include_outside_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/includes/non_yaml_in_include/out.test.toml b/acceptance/bundle/includes/non_yaml_in_include/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/includes/non_yaml_in_include/out.test.toml +++ b/acceptance/bundle/includes/non_yaml_in_include/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/includes/yml_outside_root/out.test.toml b/acceptance/bundle/includes/yml_outside_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/includes/yml_outside_root/out.test.toml +++ b/acceptance/bundle/includes/yml_outside_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/base/out.test.toml b/acceptance/bundle/integration_whl/base/out.test.toml index 5366fbb1a4d..880431d5a9f 100644 --- a/acceptance/bundle/integration_whl/base/out.test.toml +++ b/acceptance/bundle/integration_whl/base/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/custom_params/out.test.toml b/acceptance/bundle/integration_whl/custom_params/out.test.toml index 5366fbb1a4d..880431d5a9f 100644 --- a/acceptance/bundle/integration_whl/custom_params/out.test.toml +++ b/acceptance/bundle/integration_whl/custom_params/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/interactive_cluster/out.test.toml b/acceptance/bundle/integration_whl/interactive_cluster/out.test.toml index 5366fbb1a4d..880431d5a9f 100644 --- a/acceptance/bundle/integration_whl/interactive_cluster/out.test.toml +++ b/acceptance/bundle/integration_whl/interactive_cluster/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/out.test.toml b/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/out.test.toml index 133aeebaa54..19068d43e0a 100644 --- a/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/out.test.toml +++ b/acceptance/bundle/integration_whl/interactive_cluster_dynamic_version/out.test.toml @@ -1,7 +1,5 @@ Local = true Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - DATA_SECURITY_MODE = ["USER_ISOLATION", "SINGLE_USER"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATA_SECURITY_MODE = ["USER_ISOLATION", "SINGLE_USER"] diff --git a/acceptance/bundle/integration_whl/interactive_single_user/out.test.toml b/acceptance/bundle/integration_whl/interactive_single_user/out.test.toml index 5366fbb1a4d..880431d5a9f 100644 --- a/acceptance/bundle/integration_whl/interactive_single_user/out.test.toml +++ b/acceptance/bundle/integration_whl/interactive_single_user/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/serverless/out.test.toml b/acceptance/bundle/integration_whl/serverless/out.test.toml index 1573e025f6e..68b04957a4c 100644 --- a/acceptance/bundle/integration_whl/serverless/out.test.toml +++ b/acceptance/bundle/integration_whl/serverless/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true CloudSlow = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/serverless_custom_params/out.test.toml b/acceptance/bundle/integration_whl/serverless_custom_params/out.test.toml index 1573e025f6e..68b04957a4c 100644 --- a/acceptance/bundle/integration_whl/serverless_custom_params/out.test.toml +++ b/acceptance/bundle/integration_whl/serverless_custom_params/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true CloudSlow = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/serverless_dynamic_version/out.test.toml b/acceptance/bundle/integration_whl/serverless_dynamic_version/out.test.toml index 1573e025f6e..68b04957a4c 100644 --- a/acceptance/bundle/integration_whl/serverless_dynamic_version/out.test.toml +++ b/acceptance/bundle/integration_whl/serverless_dynamic_version/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true CloudSlow = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/wrapper/out.test.toml b/acceptance/bundle/integration_whl/wrapper/out.test.toml index 69e2e2028f3..44a1a2186a1 100644 --- a/acceptance/bundle/integration_whl/wrapper/out.test.toml +++ b/acceptance/bundle/integration_whl/wrapper/out.test.toml @@ -1,9 +1,6 @@ Local = true Cloud = true CloudSlow = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.aws = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/wrapper/test.toml b/acceptance/bundle/integration_whl/wrapper/test.toml index 6fd2fa8b3bd..36ac5011b69 100644 --- a/acceptance/bundle/integration_whl/wrapper/test.toml +++ b/acceptance/bundle/integration_whl/wrapper/test.toml @@ -1,2 +1,7 @@ -# Temporarily disabling due to DBR release breakage. +# This test exercises the trampoline workaround for DBR <13.1 (PR #635), which +# requires booking a cluster on Spark 12.2.x-scala2.12. The AWS test workspaces +# have legacy access disabled, so 12.2.x is rejected with INVALID_PARAMETER_VALUE +# ("legacy access is disabled in your workspace. Please use Databricks Runtime +# 13.3 LTS or above"). GCP was previously disabled due to a DBR release breakage. +CloudEnvs.aws = false CloudEnvs.gcp = false diff --git a/acceptance/bundle/integration_whl/wrapper_custom_params/out.test.toml b/acceptance/bundle/integration_whl/wrapper_custom_params/out.test.toml index 69e2e2028f3..44a1a2186a1 100644 --- a/acceptance/bundle/integration_whl/wrapper_custom_params/out.test.toml +++ b/acceptance/bundle/integration_whl/wrapper_custom_params/out.test.toml @@ -1,9 +1,6 @@ Local = true Cloud = true CloudSlow = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.aws = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/integration_whl/wrapper_custom_params/test.toml b/acceptance/bundle/integration_whl/wrapper_custom_params/test.toml index 6fd2fa8b3bd..36ac5011b69 100644 --- a/acceptance/bundle/integration_whl/wrapper_custom_params/test.toml +++ b/acceptance/bundle/integration_whl/wrapper_custom_params/test.toml @@ -1,2 +1,7 @@ -# Temporarily disabling due to DBR release breakage. +# This test exercises the trampoline workaround for DBR <13.1 (PR #635), which +# requires booking a cluster on Spark 12.2.x-scala2.12. The AWS test workspaces +# have legacy access disabled, so 12.2.x is rejected with INVALID_PARAMETER_VALUE +# ("legacy access is disabled in your workspace. Please use Databricks Runtime +# 13.3 LTS or above"). GCP was previously disabled due to a DBR release breakage. +CloudEnvs.aws = false CloudEnvs.gcp = false diff --git a/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl b/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl new file mode 100644 index 00000000000..8a460d1d906 --- /dev/null +++ b/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl @@ -0,0 +1,22 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + jobs: + foo: + name: test-job-$UNIQUE_NAME + tasks: + - task_key: main + notebook_task: + notebook_path: /Shared/notebook + - task_key: process + depends_on: + - task_key: main + notebook_task: + notebook_path: /Shared/notebook + - task_key: finalize + depends_on: + - task_key: process + - task_key: main + notebook_task: + notebook_path: /Shared/notebook diff --git a/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl b/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl new file mode 100644 index 00000000000..44e6621ac1f --- /dev/null +++ b/acceptance/bundle/invariant/configs/model_with_permissions.yml.tmpl @@ -0,0 +1,10 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + models: + foo: + name: test-model-$UNIQUE_NAME + permissions: + - level: CAN_READ + group_name: users diff --git a/acceptance/bundle/invariant/configs/pipeline_config_dots.yml.tmpl b/acceptance/bundle/invariant/configs/pipeline_config_dots.yml.tmpl new file mode 100644 index 00000000000..beb664f1228 --- /dev/null +++ b/acceptance/bundle/invariant/configs/pipeline_config_dots.yml.tmpl @@ -0,0 +1,24 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +variables: + AZURE: + type: complex + default: + subscription: test-sub-123 + +resources: + schemas: + my_schema: + catalog_name: main + name: test-schema-$UNIQUE_NAME + + pipelines: + my_pipeline: + name: test-pipeline-$UNIQUE_NAME + libraries: + - file: + path: pipeline.py + configuration: + europris.swipe.egress_streaming_schema: "${resources.schemas.my_schema.catalog_name}.${resources.schemas.my_schema.name}" + europris.azure.subscription: ${var.AZURE.subscription} diff --git a/acceptance/bundle/invariant/configs/sql_warehouse.yml.tmpl b/acceptance/bundle/invariant/configs/sql_warehouse.yml.tmpl index 3fefbdaa355..4c1bef63037 100644 --- a/acceptance/bundle/invariant/configs/sql_warehouse.yml.tmpl +++ b/acceptance/bundle/invariant/configs/sql_warehouse.yml.tmpl @@ -7,3 +7,5 @@ resources: name: test-warehouse-$UNIQUE_NAME cluster_size: X-Small max_num_clusters: 1 + min_num_clusters: 1 + warehouse_type: CLASSIC diff --git a/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl new file mode 100644 index 00000000000..cea1a4d026c --- /dev/null +++ b/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl @@ -0,0 +1,15 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + vector_search_endpoints: + foo: + name: test-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD + bar: + # Endpoint names must be < 50 chars, so keep this prefix short. + name: test-vse-perm-$UNIQUE_NAME + endpoint_type: STANDARD + permissions: + - level: CAN_USE + group_name: users diff --git a/acceptance/bundle/invariant/configs/volume_external.yml.tmpl b/acceptance/bundle/invariant/configs/volume_external.yml.tmpl new file mode 100644 index 00000000000..9bb123550f5 --- /dev/null +++ b/acceptance/bundle/invariant/configs/volume_external.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + volumes: + foo: + name: test-volume-$UNIQUE_NAME + catalog_name: main + schema_name: default + volume_type: EXTERNAL + storage_location: s3://test-bucket/path/ diff --git a/acceptance/bundle/invariant/continue_293/out.test.toml b/acceptance/bundle/invariant/continue_293/out.test.toml index 199859bd7c0..11aaf584918 100644 --- a/acceptance/bundle/invariant/continue_293/out.test.toml +++ b/acceptance/bundle/invariant/continue_293/out.test.toml @@ -1,7 +1,43 @@ Local = true -Cloud = false +Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "synced_database_table.yml.tmpl", "volume.yml.tmpl"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.INPUT_CONFIG = [ + "alert.yml.tmpl", + "app.yml.tmpl", + "catalog.yml.tmpl", + "cluster.yml.tmpl", + "dashboard.yml.tmpl", + "database_catalog.yml.tmpl", + "database_instance.yml.tmpl", + "experiment.yml.tmpl", + "external_location.yml.tmpl", + "job.yml.tmpl", + "job_pydabs_10_tasks.yml.tmpl", + "job_pydabs_1000_tasks.yml.tmpl", + "job_cross_resource_ref.yml.tmpl", + "job_permission_ref.yml.tmpl", + "job_with_depends_on.yml.tmpl", + "job_with_permissions.yml.tmpl", + "job_with_task.yml.tmpl", + "model.yml.tmpl", + "model_with_permissions.yml.tmpl", + "model_serving_endpoint.yml.tmpl", + "pipeline.yml.tmpl", + "pipeline_config_dots.yml.tmpl", + "postgres_branch.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "postgres_project.yml.tmpl", + "registered_model.yml.tmpl", + "schema.yml.tmpl", + "schema_grant_ref.yml.tmpl", + "schema_with_grants.yml.tmpl", + "secret_scope.yml.tmpl", + "secret_scope_default_backend_type.yml.tmpl", + "secret_scope_with_permissions.yml.tmpl", + "sql_warehouse.yml.tmpl", + "synced_database_table.yml.tmpl", + "vector_search_endpoint.yml.tmpl", + "volume.yml.tmpl", + "volume_external.yml.tmpl" +] diff --git a/acceptance/bundle/invariant/continue_293/test.toml b/acceptance/bundle/invariant/continue_293/test.toml index 7bee328d234..2afbcbf5e31 100644 --- a/acceptance/bundle/invariant/continue_293/test.toml +++ b/acceptance/bundle/invariant/continue_293/test.toml @@ -1,7 +1,17 @@ -Cloud = false -Slow = true - # $resources references to permissions and grants are not supported on v0.293.0 EnvMatrixExclude.no_permission_ref = ["INPUT_CONFIG=job_permission_ref.yml.tmpl"] EnvMatrixExclude.no_cross_resource_ref = ["INPUT_CONFIG=job_cross_resource_ref.yml.tmpl"] EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] + +# Model permissions did not work until 0.297.0 https://github.com/databricks/cli/pull/4941 +EnvMatrixExclude.no_model_with_permissions = ["INPUT_CONFIG=model_with_permissions.yml.tmpl"] + +# vector_search_endpoints resource is not supported on v0.293.0 +EnvMatrixExclude.no_vector_search_endpoint = ["INPUT_CONFIG=vector_search_endpoint.yml.tmpl"] + +# Dotted pipeline configuration keys are not supported on v0.293.0 +EnvMatrixExclude.no_pipeline_config_dots = ["INPUT_CONFIG=pipeline_config_dots.yml.tmpl"] + +# The 1000-task scale case is covered by no_drift. Running it here adds ~1.5 min +# per variant (two full deploys at 1000 tasks) without incremental coverage. +EnvMatrixExclude.no_pydabs_1000_tasks = ["INPUT_CONFIG=job_pydabs_1000_tasks.yml.tmpl"] diff --git a/acceptance/bundle/invariant/migrate/out.test.toml b/acceptance/bundle/invariant/migrate/out.test.toml index 7da28d9c55e..11aaf584918 100644 --- a/acceptance/bundle/invariant/migrate/out.test.toml +++ b/acceptance/bundle/invariant/migrate/out.test.toml @@ -1,7 +1,43 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "synced_database_table.yml.tmpl", "volume.yml.tmpl"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.INPUT_CONFIG = [ + "alert.yml.tmpl", + "app.yml.tmpl", + "catalog.yml.tmpl", + "cluster.yml.tmpl", + "dashboard.yml.tmpl", + "database_catalog.yml.tmpl", + "database_instance.yml.tmpl", + "experiment.yml.tmpl", + "external_location.yml.tmpl", + "job.yml.tmpl", + "job_pydabs_10_tasks.yml.tmpl", + "job_pydabs_1000_tasks.yml.tmpl", + "job_cross_resource_ref.yml.tmpl", + "job_permission_ref.yml.tmpl", + "job_with_depends_on.yml.tmpl", + "job_with_permissions.yml.tmpl", + "job_with_task.yml.tmpl", + "model.yml.tmpl", + "model_with_permissions.yml.tmpl", + "model_serving_endpoint.yml.tmpl", + "pipeline.yml.tmpl", + "pipeline_config_dots.yml.tmpl", + "postgres_branch.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "postgres_project.yml.tmpl", + "registered_model.yml.tmpl", + "schema.yml.tmpl", + "schema_grant_ref.yml.tmpl", + "schema_with_grants.yml.tmpl", + "secret_scope.yml.tmpl", + "secret_scope_default_backend_type.yml.tmpl", + "secret_scope_with_permissions.yml.tmpl", + "sql_warehouse.yml.tmpl", + "synced_database_table.yml.tmpl", + "vector_search_endpoint.yml.tmpl", + "volume.yml.tmpl", + "volume_external.yml.tmpl" +] diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index d02200cb532..78f45faa7da 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -34,7 +34,17 @@ cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null echo INPUT_CONFIG_OK -trace $CLI bundle deployment migrate &> LOG.migrate +MIGRATE_ARGS="" +# The terraform provider sorts depends_on entries alphabetically by task_key on Read +# (see terraform-provider-databricks PR #3000). Since depends_on uses TypeList +# (order-sensitive), terraform plan reports positional drift when the bundle config +# specifies depends_on in a different order than the provider's sorted state. +# This is a false positive -- the logical dependencies are identical. +if [[ "$INPUT_CONFIG" == "job_with_depends_on.yml.tmpl" ]]; then + MIGRATE_ARGS="--noplancheck" +fi + +trace $CLI bundle deployment migrate $MIGRATE_ARGS &> LOG.migrate cat LOG.migrate | contains.py '!panic:' '!internal error' > /dev/null diff --git a/acceptance/bundle/invariant/migrate/test.toml b/acceptance/bundle/invariant/migrate/test.toml index 5ffa32ae136..240a32d5d4c 100644 --- a/acceptance/bundle/invariant/migrate/test.toml +++ b/acceptance/bundle/invariant/migrate/test.toml @@ -1,3 +1,6 @@ +# vector_search_endpoints has no terraform converter +EnvMatrixExclude.no_vector_search_endpoint = ["INPUT_CONFIG=vector_search_endpoint.yml.tmpl"] + # Error: Catalog resources are only supported with direct deployment mode EnvMatrixExclude.no_catalog = ["INPUT_CONFIG=catalog.yml.tmpl"] EnvMatrixExclude.no_external_location = ["INPUT_CONFIG=external_location.yml.tmpl"] @@ -13,3 +16,10 @@ EnvMatrixExclude.no_cross_resource_ref = ["INPUT_CONFIG=job_cross_resource_ref.y # Grant cross-references require the EmbeddedSlice pattern not present in terraform mode. EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] + +# SQL warehouses currently failing with migration with permanent drift. TODO: fix this. +EnvMatrixExclude.no_sql_warehouse = ["INPUT_CONFIG=sql_warehouse.yml.tmpl"] + +# The 1000-task scale case is covered by no_drift. Running it here adds ~1.5 min +# per variant (deploy + migrate + plan at 1000 tasks) without incremental coverage. +EnvMatrixExclude.no_pydabs_1000_tasks = ["INPUT_CONFIG=job_pydabs_1000_tasks.yml.tmpl"] diff --git a/acceptance/bundle/invariant/no_drift/out.test.toml b/acceptance/bundle/invariant/no_drift/out.test.toml index 7da28d9c55e..11aaf584918 100644 --- a/acceptance/bundle/invariant/no_drift/out.test.toml +++ b/acceptance/bundle/invariant/no_drift/out.test.toml @@ -1,7 +1,43 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "synced_database_table.yml.tmpl", "volume.yml.tmpl"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.INPUT_CONFIG = [ + "alert.yml.tmpl", + "app.yml.tmpl", + "catalog.yml.tmpl", + "cluster.yml.tmpl", + "dashboard.yml.tmpl", + "database_catalog.yml.tmpl", + "database_instance.yml.tmpl", + "experiment.yml.tmpl", + "external_location.yml.tmpl", + "job.yml.tmpl", + "job_pydabs_10_tasks.yml.tmpl", + "job_pydabs_1000_tasks.yml.tmpl", + "job_cross_resource_ref.yml.tmpl", + "job_permission_ref.yml.tmpl", + "job_with_depends_on.yml.tmpl", + "job_with_permissions.yml.tmpl", + "job_with_task.yml.tmpl", + "model.yml.tmpl", + "model_with_permissions.yml.tmpl", + "model_serving_endpoint.yml.tmpl", + "pipeline.yml.tmpl", + "pipeline_config_dots.yml.tmpl", + "postgres_branch.yml.tmpl", + "postgres_endpoint.yml.tmpl", + "postgres_project.yml.tmpl", + "registered_model.yml.tmpl", + "schema.yml.tmpl", + "schema_grant_ref.yml.tmpl", + "schema_with_grants.yml.tmpl", + "secret_scope.yml.tmpl", + "secret_scope_default_backend_type.yml.tmpl", + "secret_scope_with_permissions.yml.tmpl", + "sql_warehouse.yml.tmpl", + "synced_database_table.yml.tmpl", + "vector_search_endpoint.yml.tmpl", + "volume.yml.tmpl", + "volume_external.yml.tmpl" +] diff --git a/acceptance/bundle/invariant/test.toml b/acceptance/bundle/invariant/test.toml index cf7cd12c303..257e33005a3 100644 --- a/acceptance/bundle/invariant/test.toml +++ b/acceptance/bundle/invariant/test.toml @@ -35,11 +35,14 @@ EnvMatrix.INPUT_CONFIG = [ "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", + "job_with_depends_on.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", + "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", + "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", @@ -50,8 +53,11 @@ EnvMatrix.INPUT_CONFIG = [ "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", + "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", + "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl", + "volume_external.yml.tmpl", ] [EnvMatrixExclude] @@ -66,11 +72,14 @@ no_postgres_endpoint_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_end # which are environment-specific, so we only test locally with the mock server no_external_location_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=external_location.yml.tmpl"] +# External volumes reference external locations; excluded from cloud for the same reason +no_external_volume_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=volume_external.yml.tmpl"] + # Fake SQL endpoint for local tests [[Server]] Pattern = "POST /api/2.0/sql/statements/" Response.Body = '{"status": {"state": "SUCCEEDED"}, "manifest": {"schema": {"columns": []}}}' [[Server]] -Pattern = "DELETE /api/2.1/unity-catalog/tables/{name}" +Pattern = "DELETE /api/2.1/unity-catalog/tables/{full_name}" Response.Body = '{"status": "OK"}' diff --git a/acceptance/bundle/libraries/maven/out.test.toml b/acceptance/bundle/libraries/maven/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/libraries/maven/out.test.toml +++ b/acceptance/bundle/libraries/maven/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/libraries/outside_of_bundle_root/out.test.toml b/acceptance/bundle/libraries/outside_of_bundle_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/libraries/outside_of_bundle_root/out.test.toml +++ b/acceptance/bundle/libraries/outside_of_bundle_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/libraries/pypi/out.test.toml b/acceptance/bundle/libraries/pypi/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/libraries/pypi/out.test.toml +++ b/acceptance/bundle/libraries/pypi/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/lifecycle/prevent-destroy/out.test.toml b/acceptance/bundle/lifecycle/prevent-destroy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/lifecycle/prevent-destroy/out.test.toml +++ b/acceptance/bundle/lifecycle/prevent-destroy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/lifecycle/started-validation/out.test.toml b/acceptance/bundle/lifecycle/started-validation/out.test.toml index 58724616cf8..b37ee45aed6 100644 --- a/acceptance/bundle/lifecycle/started-validation/out.test.toml +++ b/acceptance/bundle/lifecycle/started-validation/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/lifecycle/started/out.test.toml b/acceptance/bundle/lifecycle/started/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/lifecycle/started/out.test.toml +++ b/acceptance/bundle/lifecycle/started/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/local_state_staleness/out.test.toml b/acceptance/bundle/local_state_staleness/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/local_state_staleness/out.test.toml +++ b/acceptance/bundle/local_state_staleness/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/migrate/basic/out.original_state.json b/acceptance/bundle/migrate/basic/out.original_state.json index d790032e2db..65e976ff461 100644 --- a/acceptance/bundle/migrate/basic/out.original_state.json +++ b/acceptance/bundle/migrate/basic/out.original_state.json @@ -78,6 +78,7 @@ "tags": null, "task": [ { + "alert_task": [], "clean_rooms_notebook_task": [], "compute": [], "condition_task": [], diff --git a/acceptance/bundle/migrate/basic/out.test.toml b/acceptance/bundle/migrate/basic/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/basic/out.test.toml +++ b/acceptance/bundle/migrate/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/dashboards/out.test.toml b/acceptance/bundle/migrate/dashboards/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/dashboards/out.test.toml +++ b/acceptance/bundle/migrate/dashboards/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/default-python/out.state_original.json b/acceptance/bundle/migrate/default-python/out.state_original.json index e561de6b59a..a91be18e2d2 100644 --- a/acceptance/bundle/migrate/default-python/out.state_original.json +++ b/acceptance/bundle/migrate/default-python/out.state_original.json @@ -141,6 +141,7 @@ }, "task": [ { + "alert_task": [], "clean_rooms_notebook_task": [], "compute": [], "condition_task": [], @@ -207,6 +208,7 @@ "webhook_notifications": [] }, { + "alert_task": [], "clean_rooms_notebook_task": [], "compute": [], "condition_task": [], @@ -283,6 +285,7 @@ "webhook_notifications": [] }, { + "alert_task": [], "clean_rooms_notebook_task": [], "compute": [], "condition_task": [], diff --git a/acceptance/bundle/migrate/default-python/out.test.toml b/acceptance/bundle/migrate/default-python/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/default-python/out.test.toml +++ b/acceptance/bundle/migrate/default-python/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/engine-config-direct/out.test.toml b/acceptance/bundle/migrate/engine-config-direct/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/engine-config-direct/out.test.toml +++ b/acceptance/bundle/migrate/engine-config-direct/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/engine-config-terraform/out.test.toml b/acceptance/bundle/migrate/engine-config-terraform/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/engine-config-terraform/out.test.toml +++ b/acceptance/bundle/migrate/engine-config-terraform/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/grants/out.test.toml b/acceptance/bundle/migrate/grants/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/grants/out.test.toml +++ b/acceptance/bundle/migrate/grants/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/permissions/out.original_state.json b/acceptance/bundle/migrate/permissions/out.original_state.json index fdacc7dbb7d..866dadca8ac 100644 --- a/acceptance/bundle/migrate/permissions/out.original_state.json +++ b/acceptance/bundle/migrate/permissions/out.original_state.json @@ -78,6 +78,7 @@ "tags": null, "task": [ { + "alert_task": [], "clean_rooms_notebook_task": [], "compute": [], "condition_task": [], diff --git a/acceptance/bundle/migrate/permissions/out.test.toml b/acceptance/bundle/migrate/permissions/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/permissions/out.test.toml +++ b/acceptance/bundle/migrate/permissions/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/profile_arg/out.test.toml b/acceptance/bundle/migrate/profile_arg/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/profile_arg/out.test.toml +++ b/acceptance/bundle/migrate/profile_arg/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/runas/out.create_requests.json b/acceptance/bundle/migrate/runas/out.create_requests.json index 126041b2e6d..65cdc895abc 100644 --- a/acceptance/bundle/migrate/runas/out.create_requests.json +++ b/acceptance/bundle/migrate/runas/out.create_requests.json @@ -1,7 +1,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/pipeline auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/pipeline auth/pat" ] }, "method": "POST", @@ -32,7 +32,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/permissions auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/permissions auth/pat" ] }, "method": "PUT", diff --git a/acceptance/bundle/migrate/runas/out.pipelines_get.json b/acceptance/bundle/migrate/runas/out.pipelines_get.json index 959dae89ef2..715c10e9e13 100644 --- a/acceptance/bundle/migrate/runas/out.pipelines_get.json +++ b/acceptance/bundle/migrate/runas/out.pipelines_get.json @@ -1,29 +1,29 @@ { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS], - "name":"DABs Revenue Pipeline", - "pipeline_id":"[UUID]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS], + "name": "DABs Revenue Pipeline", + "pipeline_id": "[UUID]", + "run_as_user_name": "[USERNAME]", "spec": { - "catalog":"main", - "channel":"CURRENT", + "catalog": "main", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/dabs_revenue-[UNIQUE_NAME]/production/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/dabs_revenue-[UNIQUE_NAME]/production/state/metadata.json" }, - "edition":"ADVANCED", - "id":"[UUID]", + "edition": "ADVANCED", + "id": "[UUID]", "libraries": [ { "notebook": { - "path":"/Workspace/Users/[USERNAME]/.bundle/dabs_revenue-[UNIQUE_NAME]/production/files/sql" + "path": "/Workspace/Users/[USERNAME]/.bundle/dabs_revenue-[UNIQUE_NAME]/production/files/sql" } } ], - "name":"DABs Revenue Pipeline", - "serverless":true, - "target":"team_eng_deco" + "name": "DABs Revenue Pipeline", + "serverless": true, + "target": "team_eng_deco" }, - "state":"IDLE" + "state": "IDLE" } diff --git a/acceptance/bundle/migrate/runas/out.test.toml b/acceptance/bundle/migrate/runas/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/runas/out.test.toml +++ b/acceptance/bundle/migrate/runas/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/migrate/var_arg/out.test.toml b/acceptance/bundle/migrate/var_arg/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/migrate/var_arg/out.test.toml +++ b/acceptance/bundle/migrate/var_arg/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/multi_profile/auto_select/out.test.toml b/acceptance/bundle/multi_profile/auto_select/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/multi_profile/auto_select/out.test.toml +++ b/acceptance/bundle/multi_profile/auto_select/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/multi_profile/env_auth_skip/out.test.toml b/acceptance/bundle/multi_profile/env_auth_skip/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/multi_profile/env_auth_skip/out.test.toml +++ b/acceptance/bundle/multi_profile/env_auth_skip/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/multi_profile/no_workspace_profiles/out.test.toml b/acceptance/bundle/multi_profile/no_workspace_profiles/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/multi_profile/no_workspace_profiles/out.test.toml +++ b/acceptance/bundle/multi_profile/no_workspace_profiles/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/multi_profile/non_interactive_error/out.test.toml b/acceptance/bundle/multi_profile/non_interactive_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/multi_profile/non_interactive_error/out.test.toml +++ b/acceptance/bundle/multi_profile/non_interactive_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/open/open b/acceptance/bundle/open/open deleted file mode 100755 index 5c6c78d6a78..00000000000 --- a/acceptance/bundle/open/open +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -echo "I AM BROWSER" diff --git a/acceptance/bundle/open/out.test.toml b/acceptance/bundle/open/out.test.toml index 216969a7619..f784a183258 100644 --- a/acceptance/bundle/open/out.test.toml +++ b/acceptance/bundle/open/out.test.toml @@ -1,9 +1,3 @@ Local = true Cloud = false - -[GOOS] - linux = false - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/open/output.txt b/acceptance/bundle/open/output.txt index e310567b110..2da91364c7b 100644 --- a/acceptance/bundle/open/output.txt +++ b/acceptance/bundle/open/output.txt @@ -17,11 +17,11 @@ Deploying resources... Updating deployment state... Deployment complete! -=== Modify PATH so that real open is not run -=== open after deployment. This will fail to open browser and complain, that's ok, we only want the message +=== Use a fake browser that just prints the URL it would have opened +=== open after deployment >>> [CLI] bundle open foo Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] -Error: exec: "open": cannot run executable found relative to current directory +[DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] === test auto-completion handler >>> [CLI] __complete bundle open , diff --git a/acceptance/bundle/open/script b/acceptance/bundle/open/script index 724bb42871e..1fcc79ddbff 100644 --- a/acceptance/bundle/open/script +++ b/acceptance/bundle/open/script @@ -6,11 +6,11 @@ errcode trace $CLI bundle open foo errcode trace $CLI bundle deploy -title "Modify PATH so that real open is not run" -export PATH=.:$PATH +title "Use a fake browser that just prints the URL it would have opened" +export BROWSER="echo_browser.py" -title "open after deployment. This will fail to open browser and complain, that's ok, we only want the message" -musterr trace $CLI bundle open foo +title "open after deployment" +trace $CLI bundle open foo title "test auto-completion handler" trace $CLI __complete bundle open , diff --git a/acceptance/bundle/open/test.toml b/acceptance/bundle/open/test.toml deleted file mode 100644 index 078e52c97ea..00000000000 --- a/acceptance/bundle/open/test.toml +++ /dev/null @@ -1,2 +0,0 @@ -GOOS.windows = false -GOOS.linux = false diff --git a/acceptance/bundle/override/clusters/out.test.toml b/acceptance/bundle/override/clusters/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/clusters/out.test.toml +++ b/acceptance/bundle/override/clusters/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/override/job_cluster/out.test.toml b/acceptance/bundle/override/job_cluster/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/job_cluster/out.test.toml +++ b/acceptance/bundle/override/job_cluster/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/override/job_cluster_var/out.test.toml b/acceptance/bundle/override/job_cluster_var/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/job_cluster_var/out.test.toml +++ b/acceptance/bundle/override/job_cluster_var/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/override/job_tasks/out.test.toml b/acceptance/bundle/override/job_tasks/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/job_tasks/out.test.toml +++ b/acceptance/bundle/override/job_tasks/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/override/merge-string-map/out.test.toml b/acceptance/bundle/override/merge-string-map/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/merge-string-map/out.test.toml +++ b/acceptance/bundle/override/merge-string-map/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/override/pipeline_cluster/out.test.toml b/acceptance/bundle/override/pipeline_cluster/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/override/pipeline_cluster/out.test.toml +++ b/acceptance/bundle/override/pipeline_cluster/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/fallback/out.test.toml b/acceptance/bundle/paths/fallback/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/fallback/out.test.toml +++ b/acceptance/bundle/paths/fallback/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/git_source_jobs/out.test.toml b/acceptance/bundle/paths/git_source_jobs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/git_source_jobs/out.test.toml +++ b/acceptance/bundle/paths/git_source_jobs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/invalid_pipeline_globs/databricks.yml b/acceptance/bundle/paths/invalid_pipeline_globs/databricks.yml index 5ed46e048ab..d80b8aebb52 100644 --- a/acceptance/bundle/paths/invalid_pipeline_globs/databricks.yml +++ b/acceptance/bundle/paths/invalid_pipeline_globs/databricks.yml @@ -9,5 +9,5 @@ resources: variables: notebook_dir: - description: Directory with DLT notebooks + description: Directory with SDP notebooks default: non-existent diff --git a/acceptance/bundle/paths/invalid_pipeline_globs/out.test.toml b/acceptance/bundle/paths/invalid_pipeline_globs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/invalid_pipeline_globs/out.test.toml +++ b/acceptance/bundle/paths/invalid_pipeline_globs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/nominal/out.test.toml b/acceptance/bundle/paths/nominal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/nominal/out.test.toml +++ b/acceptance/bundle/paths/nominal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/outside_root_no_sync/out.test.toml b/acceptance/bundle/paths/outside_root_no_sync/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/outside_root_no_sync/out.test.toml +++ b/acceptance/bundle/paths/outside_root_no_sync/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/pipeline_expected_file_got_notebook/databricks.yml b/acceptance/bundle/paths/pipeline_expected_file_got_notebook/databricks.yml index 7d176f0cd50..4fcdf53e032 100644 --- a/acceptance/bundle/paths/pipeline_expected_file_got_notebook/databricks.yml +++ b/acceptance/bundle/paths/pipeline_expected_file_got_notebook/databricks.yml @@ -6,5 +6,5 @@ include: variables: notebook_dir: - description: Directory with DLT notebooks + description: Directory with SDP notebooks default: notebooks diff --git a/acceptance/bundle/paths/pipeline_expected_file_got_notebook/out.test.toml b/acceptance/bundle/paths/pipeline_expected_file_got_notebook/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/pipeline_expected_file_got_notebook/out.test.toml +++ b/acceptance/bundle/paths/pipeline_expected_file_got_notebook/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/pipeline_globs/out.test.toml b/acceptance/bundle/paths/pipeline_globs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/pipeline_globs/out.test.toml +++ b/acceptance/bundle/paths/pipeline_globs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/pipeline_globs/root/databricks.yml b/acceptance/bundle/paths/pipeline_globs/root/databricks.yml index a2b3f776989..843bd923484 100644 --- a/acceptance/bundle/paths/pipeline_globs/root/databricks.yml +++ b/acceptance/bundle/paths/pipeline_globs/root/databricks.yml @@ -6,8 +6,8 @@ include: variables: notebook_dir: - description: Directory with DLT notebooks + description: Directory with SDP notebooks default: notebooks file_dir: - description: Directory with DLT files + description: Directory with SDP files default: files diff --git a/acceptance/bundle/paths/pipeline_root_path_doesnotexist/out.test.toml b/acceptance/bundle/paths/pipeline_root_path_doesnotexist/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/pipeline_root_path_doesnotexist/out.test.toml +++ b/acceptance/bundle/paths/pipeline_root_path_doesnotexist/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/pipelines_glob_include_and_root_path/out.test.toml b/acceptance/bundle/paths/pipelines_glob_include_and_root_path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/pipelines_glob_include_and_root_path/out.test.toml +++ b/acceptance/bundle/paths/pipelines_glob_include_and_root_path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/pipelines_root_path_outside_sync_root/out.test.toml b/acceptance/bundle/paths/pipelines_root_path_outside_sync_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/pipelines_root_path_outside_sync_root/out.test.toml +++ b/acceptance/bundle/paths/pipelines_root_path_outside_sync_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/relative_path_outside_root/out.test.toml b/acceptance/bundle/paths/relative_path_outside_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/relative_path_outside_root/out.test.toml +++ b/acceptance/bundle/paths/relative_path_outside_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/paths/relative_path_translation/out.test.toml b/acceptance/bundle/paths/relative_path_translation/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/paths/relative_path_translation/out.test.toml +++ b/acceptance/bundle/paths/relative_path_translation/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/plan/no_upload/out.test.toml b/acceptance/bundle/plan/no_upload/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/plan/no_upload/out.test.toml +++ b/acceptance/bundle/plan/no_upload/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/presets/preset_vs_dev_mode/out.test.toml b/acceptance/bundle/presets/preset_vs_dev_mode/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/presets/preset_vs_dev_mode/out.test.toml +++ b/acceptance/bundle/presets/preset_vs_dev_mode/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/experimental-compatibility/out.test.toml b/acceptance/bundle/python/experimental-compatibility/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/experimental-compatibility/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/grants-aliases/out.test.toml b/acceptance/bundle/python/grants-aliases/out.test.toml index dab0c6a7168..98d084e3bb9 100644 --- a/acceptance/bundle/python/grants-aliases/out.test.toml +++ b/acceptance/bundle/python/grants-aliases/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/acceptance/bundle/python/mutator-ordering/out.test.toml b/acceptance/bundle/python/mutator-ordering/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/mutator-ordering/out.test.toml +++ b/acceptance/bundle/python/mutator-ordering/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/pipelines-support/out.test.toml b/acceptance/bundle/python/pipelines-support/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/pipelines-support/out.test.toml +++ b/acceptance/bundle/python/pipelines-support/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/propagates-auth-env/.databrickscfg b/acceptance/bundle/python/propagates-auth-env/.databrickscfg new file mode 100644 index 00000000000..dec8f683589 --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/.databrickscfg @@ -0,0 +1,7 @@ +[my-profile] +host = $DATABRICKS_HOST +token = $DATABRICKS_TOKEN + +[other-profile] +host = $DATABRICKS_HOST +token = other-token diff --git a/acceptance/bundle/python/propagates-auth-env/databricks.yml b/acceptance/bundle/python/propagates-auth-env/databricks.yml new file mode 100644 index 00000000000..ad214e007bf --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +python: + mutators: + - "mutators:capture_profile_env" + +workspace: + profile: my-profile + +resources: + jobs: + my_job: + name: "Job" diff --git a/acceptance/bundle/python/propagates-auth-env/mutators.py b/acceptance/bundle/python/propagates-auth-env/mutators.py new file mode 100644 index 00000000000..959d3929379 --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/mutators.py @@ -0,0 +1,13 @@ +from databricks.bundles.jobs import Job +from databricks.bundles.core import job_mutator, Bundle +import os + + +@job_mutator +def capture_profile_env(bundle: Bundle, job: Job) -> Job: + # The CLI must propagate DATABRICKS_CONFIG_PROFILE to the python subprocess + # so the Databricks SDK can disambiguate when multiple profiles share a host. + value = os.getenv("DATABRICKS_CONFIG_PROFILE", "") + with open("captured_env.txt", "w") as f: + f.write(value) + return job diff --git a/acceptance/bundle/python/propagates-auth-env/out.test.toml b/acceptance/bundle/python/propagates-auth-env/out.test.toml new file mode 100644 index 00000000000..0969b3f3733 --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/propagates-auth-env/output.txt b/acceptance/bundle/python/propagates-auth-env/output.txt new file mode 100644 index 00000000000..7279b3df1a6 --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/output.txt @@ -0,0 +1,8 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle summary -o json +{ + "profile": "my-profile" +} + +>>> cat captured_env.txt +my-profile diff --git a/acceptance/bundle/python/propagates-auth-env/script b/acceptance/bundle/python/propagates-auth-env/script new file mode 100644 index 00000000000..73f9542cce3 --- /dev/null +++ b/acceptance/bundle/python/propagates-auth-env/script @@ -0,0 +1,18 @@ + +# Two workspace profiles share the same host so picking one is meaningful. +envsubst < .databrickscfg > out && mv out .databrickscfg +export DATABRICKS_CONFIG_FILE=.databrickscfg +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE + +trace uv run $UV_ARGS -q $CLI bundle summary -o json | jq '{profile: .workspace.profile}' + +# The python mutator captures DATABRICKS_CONFIG_PROFILE from its subprocess env. +# Without the fix, the CLI does not propagate the bundle's resolved profile, +# so the SDK inside python re-invokes the CLI without a profile and fails on +# multi-profile ambiguity. +trace cat captured_env.txt +echo "" + +rm -fr .databricks __pycache__ captured_env.txt diff --git a/acceptance/bundle/python/resolve-variable/out.test.toml b/acceptance/bundle/python/resolve-variable/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/resolve-variable/out.test.toml +++ b/acceptance/bundle/python/resolve-variable/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/resource-loading/out.test.toml b/acceptance/bundle/python/resource-loading/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/resource-loading/out.test.toml +++ b/acceptance/bundle/python/resource-loading/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/restricted-execution/out.test.toml b/acceptance/bundle/python/restricted-execution/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/restricted-execution/out.test.toml +++ b/acceptance/bundle/python/restricted-execution/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/schemas-support/out.test.toml b/acceptance/bundle/python/schemas-support/out.test.toml index dab0c6a7168..98d084e3bb9 100644 --- a/acceptance/bundle/python/schemas-support/out.test.toml +++ b/acceptance/bundle/python/schemas-support/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/acceptance/bundle/python/unicode-support/out.test.toml b/acceptance/bundle/python/unicode-support/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/unicode-support/out.test.toml +++ b/acceptance/bundle/python/unicode-support/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/python/volumes-support/out.test.toml b/acceptance/bundle/python/volumes-support/out.test.toml index f8385523d95..0969b3f3733 100644 --- a/acceptance/bundle/python/volumes-support/out.test.toml +++ b/acceptance/bundle/python/volumes-support/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - PYDAB_VERSION = ["0.266.0", "current"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.PYDAB_VERSION = ["0.266.0", "current"] diff --git a/acceptance/bundle/quality_monitor/out.test.toml b/acceptance/bundle/quality_monitor/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/quality_monitor/out.test.toml +++ b/acceptance/bundle/quality_monitor/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index ecfab1562e8..c79b0d3533c 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -162,6 +162,8 @@ resources.apps.*.pending_deployment.update_time string ALL resources.apps.*.resources []apps.AppResource ALL resources.apps.*.resources[*] apps.AppResource ALL resources.apps.*.resources[*].app *apps.AppResourceApp ALL +resources.apps.*.resources[*].app.name string ALL +resources.apps.*.resources[*].app.permission apps.AppResourceAppAppPermission ALL resources.apps.*.resources[*].database *apps.AppResourceDatabase ALL resources.apps.*.resources[*].database.database_name string ALL resources.apps.*.resources[*].database.instance_name string ALL @@ -236,6 +238,13 @@ resources.catalogs.*.id string INPUT resources.catalogs.*.isolation_mode catalog.CatalogIsolationMode REMOTE resources.catalogs.*.lifecycle resources.Lifecycle INPUT resources.catalogs.*.lifecycle.prevent_destroy bool INPUT +resources.catalogs.*.managed_encryption_settings *catalog.EncryptionSettings ALL +resources.catalogs.*.managed_encryption_settings.azure_encryption_settings *catalog.AzureEncryptionSettings ALL +resources.catalogs.*.managed_encryption_settings.azure_encryption_settings.azure_cmk_access_connector_id string ALL +resources.catalogs.*.managed_encryption_settings.azure_encryption_settings.azure_cmk_managed_identity_id string ALL +resources.catalogs.*.managed_encryption_settings.azure_encryption_settings.azure_tenant_id string ALL +resources.catalogs.*.managed_encryption_settings.azure_key_vault_key_id string ALL +resources.catalogs.*.managed_encryption_settings.customer_managed_key_id string ALL resources.catalogs.*.metastore_id string REMOTE resources.catalogs.*.modified_status string INPUT resources.catalogs.*.name string ALL @@ -646,6 +655,29 @@ resources.external_locations.*.created_by string REMOTE resources.external_locations.*.credential_id string REMOTE resources.external_locations.*.credential_name string ALL resources.external_locations.*.effective_enable_file_events bool ALL +resources.external_locations.*.effective_file_event_queue *catalog.FileEventQueue ALL +resources.external_locations.*.effective_file_event_queue.managed_aqs *catalog.AzureQueueStorage ALL +resources.external_locations.*.effective_file_event_queue.managed_aqs.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.managed_aqs.queue_url string ALL +resources.external_locations.*.effective_file_event_queue.managed_aqs.resource_group string ALL +resources.external_locations.*.effective_file_event_queue.managed_aqs.subscription_id string ALL +resources.external_locations.*.effective_file_event_queue.managed_pubsub *catalog.GcpPubsub ALL +resources.external_locations.*.effective_file_event_queue.managed_pubsub.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.managed_pubsub.subscription_name string ALL +resources.external_locations.*.effective_file_event_queue.managed_sqs *catalog.AwsSqsQueue ALL +resources.external_locations.*.effective_file_event_queue.managed_sqs.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.managed_sqs.queue_url string ALL +resources.external_locations.*.effective_file_event_queue.provided_aqs *catalog.AzureQueueStorage ALL +resources.external_locations.*.effective_file_event_queue.provided_aqs.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.provided_aqs.queue_url string ALL +resources.external_locations.*.effective_file_event_queue.provided_aqs.resource_group string ALL +resources.external_locations.*.effective_file_event_queue.provided_aqs.subscription_id string ALL +resources.external_locations.*.effective_file_event_queue.provided_pubsub *catalog.GcpPubsub ALL +resources.external_locations.*.effective_file_event_queue.provided_pubsub.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.provided_pubsub.subscription_name string ALL +resources.external_locations.*.effective_file_event_queue.provided_sqs *catalog.AwsSqsQueue ALL +resources.external_locations.*.effective_file_event_queue.provided_sqs.managed_resource_id string ALL +resources.external_locations.*.effective_file_event_queue.provided_sqs.queue_url string ALL resources.external_locations.*.enable_file_events bool ALL resources.external_locations.*.encryption_details *catalog.EncryptionDetails ALL resources.external_locations.*.encryption_details.sse_encryption_details *catalog.SseEncryptionDetails ALL @@ -2105,6 +2137,7 @@ resources.models.*.latest_versions[*].user_id string REMOTE resources.models.*.latest_versions[*].version string REMOTE resources.models.*.lifecycle resources.Lifecycle INPUT resources.models.*.lifecycle.prevent_destroy bool INPUT +resources.models.*.model_id string REMOTE resources.models.*.modified_status string INPUT resources.models.*.name string ALL resources.models.*.permission_level ml.PermissionLevel REMOTE @@ -2296,6 +2329,61 @@ resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].key string ALL resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration.workday_report_parameters.report_parameters[*].value string ALL resources.pipelines.*.ingestion_definition.objects[*].schema *pipelines.SchemaSpec ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options *pipelines.ConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options *pipelines.GoogleDriveOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type pipelines.GoogleDriveOptionsGoogleDriveEntityType ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options *pipelines.FileIngestionOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters []pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*] pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format pipelines.FileIngestionOptionsFileFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format_options.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.infer_column_types bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.rescued_data_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.single_variant_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.url string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.google_ads_options *pipelines.GoogleAdsOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.google_ads_options.lookback_window_days int ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.google_ads_options.manager_account_id string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.google_ads_options.sync_start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options *pipelines.SharepointOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type pipelines.SharepointOptionsSharepointEntityType ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options *pipelines.FileIngestionOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters []pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*] pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format pipelines.FileIngestionOptionsFileFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format_options.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.infer_column_types bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.single_variant_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.url string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options *pipelines.TikTokAdsOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level pipelines.TikTokAdsOptionsTikTokDataLevel ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.dimensions[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.lookback_window_days int ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.metrics[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.query_lifetime bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type pipelines.TikTokAdsOptionsTikTokReportType ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.sync_start_date string ALL resources.pipelines.*.ingestion_definition.objects[*].schema.destination_catalog string ALL resources.pipelines.*.ingestion_definition.objects[*].schema.destination_schema string ALL resources.pipelines.*.ingestion_definition.objects[*].schema.source_catalog string ALL @@ -2329,6 +2417,61 @@ resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].key string ALL resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration.workday_report_parameters.report_parameters[*].value string ALL resources.pipelines.*.ingestion_definition.objects[*].table *pipelines.TableSpec ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options *pipelines.ConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options *pipelines.GoogleDriveOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type pipelines.GoogleDriveOptionsGoogleDriveEntityType ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options *pipelines.FileIngestionOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.corrupt_record_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters []pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*] pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_after string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].modified_before string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.file_filters[*].path_filter string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format pipelines.FileIngestionOptionsFileFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format_options.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.ignore_corrupt_files bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.infer_column_types bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.reader_case_sensitive bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.rescued_data_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.single_variant_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.url string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.google_ads_options *pipelines.GoogleAdsOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.google_ads_options.lookback_window_days int ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.google_ads_options.manager_account_id string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.google_ads_options.sync_start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options *pipelines.SharepointOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type pipelines.SharepointOptionsSharepointEntityType ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options *pipelines.FileIngestionOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.corrupt_record_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters []pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*] pipelines.FileFilter ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_after string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].modified_before string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.file_filters[*].path_filter string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format pipelines.FileIngestionOptionsFileFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format_options.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.ignore_corrupt_files bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.infer_column_types bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.reader_case_sensitive bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.rescued_data_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.single_variant_column string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.url string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options *pipelines.TikTokAdsOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level pipelines.TikTokAdsOptionsTikTokDataLevel ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.dimensions[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.lookback_window_days int ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.metrics[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.query_lifetime bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type pipelines.TikTokAdsOptionsTikTokReportType ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.sync_start_date string ALL resources.pipelines.*.ingestion_definition.objects[*].table.destination_catalog string ALL resources.pipelines.*.ingestion_definition.objects[*].table.destination_schema string ALL resources.pipelines.*.ingestion_definition.objects[*].table.destination_table string ALL @@ -2562,6 +2705,7 @@ resources.postgres_projects.*.custom_tags []postgres.ProjectCustomTag INPUT STAT resources.postgres_projects.*.custom_tags[*] postgres.ProjectCustomTag INPUT STATE resources.postgres_projects.*.custom_tags[*].key string INPUT STATE resources.postgres_projects.*.custom_tags[*].value string INPUT STATE +resources.postgres_projects.*.default_branch string INPUT STATE resources.postgres_projects.*.default_endpoint_settings *postgres.ProjectDefaultEndpointSettings INPUT STATE resources.postgres_projects.*.default_endpoint_settings.autoscaling_limit_max_cu float64 INPUT STATE resources.postgres_projects.*.default_endpoint_settings.autoscaling_limit_min_cu float64 INPUT STATE @@ -2590,6 +2734,7 @@ resources.postgres_projects.*.spec.custom_tags []postgres.ProjectCustomTag REMOT resources.postgres_projects.*.spec.custom_tags[*] postgres.ProjectCustomTag REMOTE resources.postgres_projects.*.spec.custom_tags[*].key string REMOTE resources.postgres_projects.*.spec.custom_tags[*].value string REMOTE +resources.postgres_projects.*.spec.default_branch string REMOTE resources.postgres_projects.*.spec.default_endpoint_settings *postgres.ProjectDefaultEndpointSettings REMOTE resources.postgres_projects.*.spec.default_endpoint_settings.autoscaling_limit_max_cu float64 REMOTE resources.postgres_projects.*.spec.default_endpoint_settings.autoscaling_limit_min_cu float64 REMOTE @@ -2608,6 +2753,7 @@ resources.postgres_projects.*.status.custom_tags []postgres.ProjectCustomTag REM resources.postgres_projects.*.status.custom_tags[*] postgres.ProjectCustomTag REMOTE resources.postgres_projects.*.status.custom_tags[*].key string REMOTE resources.postgres_projects.*.status.custom_tags[*].value string REMOTE +resources.postgres_projects.*.status.default_branch string REMOTE resources.postgres_projects.*.status.default_endpoint_settings *postgres.ProjectDefaultEndpointSettings REMOTE resources.postgres_projects.*.status.default_endpoint_settings.autoscaling_limit_max_cu float64 REMOTE resources.postgres_projects.*.status.default_endpoint_settings.autoscaling_limit_min_cu float64 REMOTE @@ -2884,6 +3030,39 @@ resources.synced_database_tables.*.spec.source_table_full_name string ALL resources.synced_database_tables.*.spec.timeseries_key string ALL resources.synced_database_tables.*.unity_catalog_provisioning_state database.ProvisioningInfoState ALL resources.synced_database_tables.*.url string INPUT +resources.vector_search_endpoints.*.budget_policy_id string ALL +resources.vector_search_endpoints.*.creation_timestamp int64 REMOTE +resources.vector_search_endpoints.*.creator string REMOTE +resources.vector_search_endpoints.*.custom_tags []vectorsearch.CustomTag REMOTE +resources.vector_search_endpoints.*.custom_tags[*] vectorsearch.CustomTag REMOTE +resources.vector_search_endpoints.*.custom_tags[*].key string REMOTE +resources.vector_search_endpoints.*.custom_tags[*].value string REMOTE +resources.vector_search_endpoints.*.effective_budget_policy_id string REMOTE +resources.vector_search_endpoints.*.endpoint_status *vectorsearch.EndpointStatus REMOTE +resources.vector_search_endpoints.*.endpoint_status.message string REMOTE +resources.vector_search_endpoints.*.endpoint_status.state vectorsearch.EndpointStatusState REMOTE +resources.vector_search_endpoints.*.endpoint_type vectorsearch.EndpointType ALL +resources.vector_search_endpoints.*.endpoint_uuid string REMOTE +resources.vector_search_endpoints.*.id string INPUT REMOTE +resources.vector_search_endpoints.*.last_updated_timestamp int64 REMOTE +resources.vector_search_endpoints.*.last_updated_user string REMOTE +resources.vector_search_endpoints.*.lifecycle resources.Lifecycle INPUT +resources.vector_search_endpoints.*.lifecycle.prevent_destroy bool INPUT +resources.vector_search_endpoints.*.min_qps int64 INPUT STATE +resources.vector_search_endpoints.*.modified_status string INPUT +resources.vector_search_endpoints.*.name string ALL +resources.vector_search_endpoints.*.num_indexes int REMOTE +resources.vector_search_endpoints.*.scaling_info *vectorsearch.EndpointScalingInfo REMOTE +resources.vector_search_endpoints.*.scaling_info.requested_min_qps int64 REMOTE +resources.vector_search_endpoints.*.scaling_info.state vectorsearch.ScalingChangeState REMOTE +resources.vector_search_endpoints.*.url string INPUT +resources.vector_search_endpoints.*.usage_policy_id string INPUT STATE +resources.vector_search_endpoints.*.permissions.object_id string ALL +resources.vector_search_endpoints.*.permissions[*] dresources.StatePermission ALL +resources.vector_search_endpoints.*.permissions[*].group_name string ALL +resources.vector_search_endpoints.*.permissions[*].level iam.PermissionLevel ALL +resources.vector_search_endpoints.*.permissions[*].service_principal_name string ALL +resources.vector_search_endpoints.*.permissions[*].user_name string ALL resources.volumes.*.access_point string REMOTE resources.volumes.*.browse_only bool REMOTE resources.volumes.*.catalog_name string ALL diff --git a/acceptance/bundle/refschema/out.test.toml b/acceptance/bundle/refschema/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/refschema/out.test.toml +++ b/acceptance/bundle/refschema/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.requests.txt b/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.requests.txt deleted file mode 100644 index c7b60b659d8..00000000000 --- a/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.requests.txt +++ /dev/null @@ -1,32 +0,0 @@ -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} -{ - "method": "GET", - "path": "/api/2.0/preview/scim/v2/Me" -} -{ - "method": "GET", - "path": "/api/2.0/workspace/get-status", - "q": { - "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json", - "return_export_info": "true" - } -} -{ - "method": "GET", - "path": "/api/2.0/workspace/get-status", - "q": { - "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json", - "return_export_info": "true" - } -} -{ - "method": "GET", - "path": "/api/2.0/workspace/get-status", - "q": { - "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json", - "return_export_info": "true" - } -} diff --git a/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.test.toml b/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.test.toml +++ b/acceptance/bundle/resource_deps/bad_ref_string_to_int/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/bad_ref_string_to_int/test.toml b/acceptance/bundle/resource_deps/bad_ref_string_to_int/test.toml index 364eb9f840f..022acf3a195 100644 --- a/acceptance/bundle/resource_deps/bad_ref_string_to_int/test.toml +++ b/acceptance/bundle/resource_deps/bad_ref_string_to_int/test.toml @@ -1,3 +1,5 @@ +RecordRequests = false + [Env] DATABRICKS_CACHE_ENABLED = 'false' diff --git a/acceptance/bundle/resource_deps/bad_syntax/out.test.toml b/acceptance/bundle/resource_deps/bad_syntax/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/bad_syntax/out.test.toml +++ b/acceptance/bundle/resource_deps/bad_syntax/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/create_error/out.test.toml b/acceptance/bundle/resource_deps/create_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/create_error/out.test.toml +++ b/acceptance/bundle/resource_deps/create_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/grant_ref/out.test.toml b/acceptance/bundle/resource_deps/grant_ref/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resource_deps/grant_ref/out.test.toml +++ b/acceptance/bundle/resource_deps/grant_ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resource_deps/id_chain/out.test.toml b/acceptance/bundle/resource_deps/id_chain/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/id_chain/out.test.toml +++ b/acceptance/bundle/resource_deps/id_chain/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/id_star/out.test.toml b/acceptance/bundle/resource_deps/id_star/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/id_star/out.test.toml +++ b/acceptance/bundle/resource_deps/id_star/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/immutable_field_ref/out.test.toml b/acceptance/bundle/resource_deps/immutable_field_ref/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resource_deps/immutable_field_ref/out.test.toml +++ b/acceptance/bundle/resource_deps/immutable_field_ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resource_deps/implicit_deps_model_serving_endpoint/out.test.toml b/acceptance/bundle/resource_deps/implicit_deps_model_serving_endpoint/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/implicit_deps_model_serving_endpoint/out.test.toml +++ b/acceptance/bundle/resource_deps/implicit_deps_model_serving_endpoint/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/implicit_deps_quality_monitor/out.test.toml b/acceptance/bundle/resource_deps/implicit_deps_quality_monitor/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/implicit_deps_quality_monitor/out.test.toml +++ b/acceptance/bundle/resource_deps/implicit_deps_quality_monitor/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/implicit_deps_registered_model/out.test.toml b/acceptance/bundle/resource_deps/implicit_deps_registered_model/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/implicit_deps_registered_model/out.test.toml +++ b/acceptance/bundle/resource_deps/implicit_deps_registered_model/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/implicit_deps_volume/out.test.toml b/acceptance/bundle/resource_deps/implicit_deps_volume/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/implicit_deps_volume/out.test.toml +++ b/acceptance/bundle/resource_deps/implicit_deps_volume/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_id/out.test.toml b/acceptance/bundle/resource_deps/job_id/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_id/out.test.toml +++ b/acceptance/bundle/resource_deps/job_id/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_id_big_graph/delete_all/out.test.toml b/acceptance/bundle/resource_deps/job_id_big_graph/delete_all/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_id_big_graph/delete_all/out.test.toml +++ b/acceptance/bundle/resource_deps/job_id_big_graph/delete_all/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_id_big_graph/destroy/out.test.toml b/acceptance/bundle/resource_deps/job_id_big_graph/destroy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_id_big_graph/destroy/out.test.toml +++ b/acceptance/bundle/resource_deps/job_id_big_graph/destroy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_id_delete_bar/out.test.toml b/acceptance/bundle/resource_deps/job_id_delete_bar/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_id_delete_bar/out.test.toml +++ b/acceptance/bundle/resource_deps/job_id_delete_bar/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_id_delete_foo/out.test.toml b/acceptance/bundle/resource_deps/job_id_delete_foo/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_id_delete_foo/out.test.toml +++ b/acceptance/bundle/resource_deps/job_id_delete_foo/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/job_tasks/out.test.toml b/acceptance/bundle/resource_deps/job_tasks/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/job_tasks/out.test.toml +++ b/acceptance/bundle/resource_deps/job_tasks/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/jobs_update/out.get_foo.direct.json b/acceptance/bundle/resource_deps/jobs_update/out.get_foo.direct.json index 17be9144b83..fcdded0d5a8 100644 --- a/acceptance/bundle/resource_deps/jobs_update/out.get_foo.direct.json +++ b/acceptance/bundle/resource_deps/jobs_update/out.get_foo.direct.json @@ -1,36 +1,36 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[FOO_ID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [FOO_ID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", + "format": "MULTI_TASK", "job_clusters": [ { - "job_cluster_key":"key", + "job_cluster_key": "key", "new_cluster": { - "num_workers":0, - "spark_version":"13.3.x-scala2.12" + "num_workers": 0, + "spark_version": "13.3.x-scala2.12" } } ], - "max_concurrent_runs":1, - "name":"foo", + "max_concurrent_runs": 1, + "name": "foo", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "trigger": { - "pause_status":"UNPAUSED", + "pause_status": "UNPAUSED", "periodic": { - "interval":1, - "unit":"HOURS" + "interval": 1, + "unit": "HOURS" } }, "webhook_notifications": {} diff --git a/acceptance/bundle/resource_deps/jobs_update/out.get_foo.terraform.json b/acceptance/bundle/resource_deps/jobs_update/out.get_foo.terraform.json index 0bd520f607c..e20aea135c4 100644 --- a/acceptance/bundle/resource_deps/jobs_update/out.get_foo.terraform.json +++ b/acceptance/bundle/resource_deps/jobs_update/out.get_foo.terraform.json @@ -1,39 +1,39 @@ { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[FOO_ID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [FOO_ID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" }, - "edit_mode":"UI_LOCKED", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", + "format": "MULTI_TASK", "job_clusters": [ { - "job_cluster_key":"key", + "job_cluster_key": "key", "new_cluster": { - "num_workers":0, - "spark_version":"13.3.x-scala2.12" + "num_workers": 0, + "spark_version": "13.3.x-scala2.12" } } ], - "max_concurrent_runs":1, - "name":"foo", + "max_concurrent_runs": 1, + "name": "foo", "queue": { - "enabled":true + "enabled": true }, "run_as": { - "user_name":"[USERNAME]" + "user_name": "[USERNAME]" }, - "timeout_seconds":0, + "timeout_seconds": 0, "trigger": { - "pause_status":"UNPAUSED", + "pause_status": "UNPAUSED", "periodic": { - "interval":1, - "unit":"HOURS" + "interval": 1, + "unit": "HOURS" } }, "webhook_notifications": {} diff --git a/acceptance/bundle/resource_deps/jobs_update/out.test.toml b/acceptance/bundle/resource_deps/jobs_update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/jobs_update/out.test.toml +++ b/acceptance/bundle/resource_deps/jobs_update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/jobs_update/output.txt b/acceptance/bundle/resource_deps/jobs_update/output.txt index 2096504d5e1..edbfc2df2b3 100644 --- a/acceptance/bundle/resource_deps/jobs_update/output.txt +++ b/acceptance/bundle/resource_deps/jobs_update/output.txt @@ -40,25 +40,25 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged >>> [CLI] jobs get [BAR_ID] { - "created_time":[UNIX_TIME_MILLIS], - "creator_user_name":"[USERNAME]", - "job_id":[BAR_ID], - "run_as_user_name":"[USERNAME]", + "created_time": [UNIX_TIME_MILLIS], + "creator_user_name": "[USERNAME]", + "job_id": [BAR_ID], + "run_as_user_name": "[USERNAME]", "settings": { "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" }, - "description":"depends on foo id [FOO_ID]", - "edit_mode":"UI_LOCKED", + "description": "depends on foo id [FOO_ID]", + "edit_mode": "UI_LOCKED", "email_notifications": {}, - "format":"MULTI_TASK", - "max_concurrent_runs":1, - "name":"bar", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "bar", "queue": { - "enabled":true + "enabled": true }, - "timeout_seconds":0, + "timeout_seconds": 0, "webhook_notifications": {} } } diff --git a/acceptance/bundle/resource_deps/jobs_update_remote/out.test.toml b/acceptance/bundle/resource_deps/jobs_update_remote/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/jobs_update_remote/out.test.toml +++ b/acceptance/bundle/resource_deps/jobs_update_remote/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/loop_jobs/out.test.toml b/acceptance/bundle/resource_deps/loop_jobs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/loop_jobs/out.test.toml +++ b/acceptance/bundle/resource_deps/loop_jobs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/loop_self/out.test.toml b/acceptance/bundle/resource_deps/loop_self/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/loop_self/out.test.toml +++ b/acceptance/bundle/resource_deps/loop_self/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/missing_ingestion_definition/out.test.toml b/acceptance/bundle/resource_deps/missing_ingestion_definition/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/missing_ingestion_definition/out.test.toml +++ b/acceptance/bundle/resource_deps/missing_ingestion_definition/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/missing_map_key/out.test.toml b/acceptance/bundle/resource_deps/missing_map_key/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/missing_map_key/out.test.toml +++ b/acceptance/bundle/resource_deps/missing_map_key/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/missing_string_field/out.test.toml b/acceptance/bundle/resource_deps/missing_string_field/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/missing_string_field/out.test.toml +++ b/acceptance/bundle/resource_deps/missing_string_field/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/non_existent_field/out.test.toml b/acceptance/bundle/resource_deps/non_existent_field/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/non_existent_field/out.test.toml +++ b/acceptance/bundle/resource_deps/non_existent_field/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/permission_ref/out.test.toml b/acceptance/bundle/resource_deps/permission_ref/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resource_deps/permission_ref/out.test.toml +++ b/acceptance/bundle/resource_deps/permission_ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resource_deps/pipelines_recreate/out.test.toml b/acceptance/bundle/resource_deps/pipelines_recreate/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/pipelines_recreate/out.test.toml +++ b/acceptance/bundle/resource_deps/pipelines_recreate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/pipelines_recreate/output.txt b/acceptance/bundle/resource_deps/pipelines_recreate/output.txt index 7a8592a7cc5..4bd50ca8a96 100644 --- a/acceptance/bundle/resource_deps/pipelines_recreate/output.txt +++ b/acceptance/bundle/resource_deps/pipelines_recreate/output.txt @@ -45,24 +45,24 @@ Error: The specified pipeline [FOO_ID] was not found. >>> [CLI] pipelines get [FOO_ID_2] { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS][0], - "name":"pipeline foo", - "pipeline_id":"[FOO_ID_2]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS][0], + "name": "pipeline foo", + "pipeline_id": "[FOO_ID_2]", + "run_as_user_name": "[USERNAME]", "spec": { - "channel":"CURRENT", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/metadata.json" }, - "edition":"ADVANCED", - "id":"[FOO_ID_2]", - "name":"pipeline foo", - "storage":"dbfs:/my-new-storage" + "edition": "ADVANCED", + "id": "[FOO_ID_2]", + "name": "pipeline foo", + "storage": "dbfs:/my-new-storage" }, - "state":"IDLE" + "state": "IDLE" } >>> [CLI] jobs get [BAR_ID] diff --git a/acceptance/bundle/resource_deps/present_ingestion_definition/out.test.toml b/acceptance/bundle/resource_deps/present_ingestion_definition/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/present_ingestion_definition/out.test.toml +++ b/acceptance/bundle/resource_deps/present_ingestion_definition/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/remote_app_url/out.test.toml b/acceptance/bundle/resource_deps/remote_app_url/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/remote_app_url/out.test.toml +++ b/acceptance/bundle/resource_deps/remote_app_url/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/remote_field_storage_location/out.test.toml b/acceptance/bundle/resource_deps/remote_field_storage_location/out.test.toml index 1819a94c46c..8c738f635ac 100644 --- a/acceptance/bundle/resource_deps/remote_field_storage_location/out.test.toml +++ b/acceptance/bundle/resource_deps/remote_field_storage_location/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/remote_pipeline/out.test.toml b/acceptance/bundle/resource_deps/remote_pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/remote_pipeline/out.test.toml +++ b/acceptance/bundle/resource_deps/remote_pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/resources_var/out.test.toml b/acceptance/bundle/resource_deps/resources_var/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/resources_var/out.test.toml +++ b/acceptance/bundle/resource_deps/resources_var/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/resources_var_presets/out.test.toml b/acceptance/bundle/resource_deps/resources_var_presets/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/resources_var_presets/out.test.toml +++ b/acceptance/bundle/resource_deps/resources_var_presets/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resource_deps/resources_var_presets_implicit_deps/out.test.toml b/acceptance/bundle/resource_deps/resources_var_presets_implicit_deps/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resource_deps/resources_var_presets_implicit_deps/out.test.toml +++ b/acceptance/bundle/resource_deps/resources_var_presets_implicit_deps/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/basic/out.test.toml b/acceptance/bundle/resources/alerts/basic/out.test.toml index a20bba0bcbb..c45d8e76a8e 100644 --- a/acceptance/bundle/resources/alerts/basic/out.test.toml +++ b/acceptance/bundle/resources/alerts/basic/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file/out.test.toml b/acceptance/bundle/resources/alerts/with_file/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/resources/alerts/with_file/out.test.toml +++ b/acceptance/bundle/resources/alerts/with_file/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml +++ b/acceptance/bundle/resources/alerts/with_file_not_allowed_field_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml +++ b/acceptance/bundle/resources/alerts/with_file_variable_interpolation_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/apps/config-drift/app/app.py b/acceptance/bundle/resources/apps/config-drift/app/app.py index f1a18139c84..ad1e64f9fb0 100644 --- a/acceptance/bundle/resources/apps/config-drift/app/app.py +++ b/acceptance/bundle/resources/apps/config-drift/app/app.py @@ -1 +1,5 @@ -print("Hello world!") +import http.server, os + +http.server.HTTPServer( + ("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler +).serve_forever() diff --git a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json index 80989bc8fc8..0fe76b5ecf4 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json +++ b/acceptance/bundle/resources/apps/config-drift/out.plan.direct.json @@ -1,48 +1,5 @@ +{} { - "active_deployment": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "command": [ - "streamlit", - "run", - "dashboard.py" - ], - "deployment_id": "deploy-[NUMID]", - "env_vars": [ - { - "name": "MY_VAR", - "value": "changed_value" - }, - { - "name": "NEW_VAR", - "value": "new_value" - } - ], - "mode": "SNAPSHOT", - "source_code_path": "./app", - "status": { - "message": "Deployment succeeded", - "state": "SUCCEEDED" - } - } - }, - "app_status": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "message": "Application is running.", - "state": "RUNNING" - } - }, - "compute_status": { - "action": "skip", - "reason": "spec:output_only", - "remote": { - "message": "App compute is active.", - "state": "ACTIVE" - } - }, "config.command": { "action": "update", "old": [ @@ -83,41 +40,6 @@ "value": "new_value" } ] - }, - "default_source_code_path": { - "action": "skip", - "reason": "spec:output_only", - "remote": "./app" - }, - "id": { - "action": "skip", - "reason": "spec:output_only", - "remote": "1000" - }, - "service_principal_client_id": { - "action": "skip", - "reason": "spec:output_only", - "remote": "[UUID]" - }, - "service_principal_id": { - "action": "skip", - "reason": "spec:output_only", - "remote": [NUMID] - }, - "service_principal_name": { - "action": "skip", - "reason": "spec:output_only", - "remote": "app-[UNIQUE_NAME]" - }, - "source_code_path": { - "action": "update", - "old": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files/app", - "new": "/Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files/app", - "remote": "./app" - }, - "url": { - "action": "skip", - "reason": "spec:output_only", - "remote": "[UNIQUE_NAME]-123.cloud.databricksapps.com" } } +{} diff --git a/acceptance/bundle/resources/apps/config-drift/out.test.toml b/acceptance/bundle/resources/apps/config-drift/out.test.toml index 19b2c349a32..e90b6d5d1ba 100644 --- a/acceptance/bundle/resources/apps/config-drift/out.test.toml +++ b/acceptance/bundle/resources/apps/config-drift/out.test.toml @@ -1,5 +1,3 @@ Local = true -Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/config-drift/output.txt b/acceptance/bundle/resources/apps/config-drift/output.txt index 4e5d995ba91..6ad4310b318 100644 --- a/acceptance/bundle/resources/apps/config-drift/output.txt +++ b/acceptance/bundle/resources/apps/config-drift/output.txt @@ -1,22 +1,14 @@ -=== First deploy: creates app >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! -=== Second deploy: pushes code with config ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... -Deploying resources... -✓ Deployment succeeded -Updating deployment state... -Deployment complete! - === Verify no drift after deploy ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged +>>> [CLI] bundle plan -o json + +>>> [CLI] apps get [UNIQUE_NAME] --output json === Simulate out-of-band deployment with changed command and env === Plan should detect config drift @@ -29,20 +21,11 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/config-drift-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! === Verify no drift after fix ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged - -=== Simulate out-of-band deployment with git_source added -=== Plan should detect git_source drift ->>> [CLI] bundle plan -update apps.myapp - -Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged +>>> [CLI] bundle plan -o json >>> [CLI] bundle destroy --auto-approve The following resources will be deleted: diff --git a/acceptance/bundle/resources/apps/config-drift/script b/acceptance/bundle/resources/apps/config-drift/script index e37ac80fdea..4728255dc8f 100644 --- a/acceptance/bundle/resources/apps/config-drift/script +++ b/acceptance/bundle/resources/apps/config-drift/script @@ -6,18 +6,16 @@ cleanup() { } trap cleanup EXIT -title "First deploy: creates app" -trace $CLI bundle deploy - -title "Second deploy: pushes code with config" trace $CLI bundle deploy title "Verify no drift after deploy" -trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' > out.plan.direct.json + +SOURCE_CODE_PATH=$(trace $CLI apps get $UNIQUE_NAME --output json | jq -r '.active_deployment.source_code_path') title "Simulate out-of-band deployment with changed command and env" $CLI apps deploy $UNIQUE_NAME --no-wait --json '{ - "source_code_path": "./app", + "source_code_path": "'$SOURCE_CODE_PATH'", "mode": "SNAPSHOT", "command": ["streamlit", "run", "dashboard.py"], "env_vars": [ @@ -28,22 +26,14 @@ $CLI apps deploy $UNIQUE_NAME --no-wait --json '{ title "Plan should detect config drift" trace $CLI bundle plan -$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' > out.plan.direct.json +# Skip entries with action "skip" +$CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' >> out.plan.direct.json title "Redeploy to fix drift" trace $CLI bundle deploy title "Verify no drift after fix" -trace $CLI bundle plan +trace $CLI bundle plan -o json | jq '.plan."resources.apps.myapp".changes.config // .plan."resources.apps.myapp".changes' | jq 'del(.[] | select(.action == "skip"))' >> out.plan.direct.json -title "Simulate out-of-band deployment with git_source added" -$CLI apps deploy $UNIQUE_NAME --no-wait --json '{ - "source_code_path": "./app", - "mode": "SNAPSHOT", - "git_source": {"branch": "feature-branch"}, - "command": ["python", "app.py"], - "env_vars": [{"name": "MY_VAR", "value": "original_value"}] - }' > /dev/null - -title "Plan should detect git_source drift" -trace $CLI bundle plan +# TODO: add test for git_source drift when git_source is supported in the Deploy API +# Currently it fails with the error: Git source reference is required diff --git a/acceptance/bundle/resources/apps/config-drift/test.toml b/acceptance/bundle/resources/apps/config-drift/test.toml index bfe2b2f2a72..bf01b60b180 100644 --- a/acceptance/bundle/resources/apps/config-drift/test.toml +++ b/acceptance/bundle/resources/apps/config-drift/test.toml @@ -1,8 +1,14 @@ Local = true -Cloud = true +Cloud = false # This currently fails on Cloud due to incorrect API behaviour. RecordRequests = true Ignore = [".databricks", "databricks.yml"] +# App deployment progress messages are non-deterministic on cloud (different status messages +# per poll), so filter them out entirely. +[[Repls]] +Old = '(?m)^✓ [^\n]*\n' +New = "" + [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/create_already_exists/out.test.toml b/acceptance/bundle/resources/apps/create_already_exists/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resources/apps/create_already_exists/out.test.toml +++ b/acceptance/bundle/resources/apps/create_already_exists/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/create_already_exists/output.txt b/acceptance/bundle/resources/apps/create_already_exists/output.txt index 19af292ead4..e4438d47b04 100644 --- a/acceptance/bundle/resources/apps/create_already_exists/output.txt +++ b/acceptance/bundle/resources/apps/create_already_exists/output.txt @@ -1,20 +1,30 @@ >>> [CLI] apps create test-app-already-exists { + "active_deployment": { + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-app-already-exists", + "status": { + "message": "Deployment succeeded", + "state": "SUCCEEDED" + } + }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "id":"1000", - "name":"test-app-already-exists", - "service_principal_client_id":"[UUID]", - "service_principal_id":[NUMID], - "service_principal_name":"app-test-app-already-exists", - "url":"test-app-already-exists-123.cloud.databricksapps.com" + "default_source_code_path": "/Workspace/Users/[USERNAME]/test-app-already-exists", + "id": "1000", + "name": "test-app-already-exists", + "service_principal_client_id": "[UUID]", + "service_principal_id": [NUMID], + "service_principal_name": "app-test-app-already-exists", + "url": "test-app-already-exists-123.cloud.databricksapps.com" } >>> musterr [CLI] bundle deploy @@ -31,5 +41,5 @@ Updating deployment state... >>> [CLI] apps delete test-app-already-exists { - "name":"" + "name": "" } diff --git a/acceptance/bundle/resources/apps/default_description/out.test.toml b/acceptance/bundle/resources/apps/default_description/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/apps/default_description/out.test.toml +++ b/acceptance/bundle/resources/apps/default_description/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/apps/inline_config/out.test.toml b/acceptance/bundle/resources/apps/inline_config/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/resources/apps/inline_config/out.test.toml +++ b/acceptance/bundle/resources/apps/inline_config/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started-omitted/app/app.py b/acceptance/bundle/resources/apps/lifecycle-started-omitted/app/app.py index d56323cf53f..ad1e64f9fb0 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-omitted/app/app.py +++ b/acceptance/bundle/resources/apps/lifecycle-started-omitted/app/app.py @@ -1 +1,5 @@ -print("Hello world\!") +import http.server, os + +http.server.HTTPServer( + ("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler +).serve_forever() diff --git a/acceptance/bundle/resources/apps/lifecycle-started-omitted/out.test.toml b/acceptance/bundle/resources/apps/lifecycle-started-omitted/out.test.toml index 19b2c349a32..9cfad3fb0d5 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-omitted/out.test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started-omitted/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started-omitted/output.txt b/acceptance/bundle/resources/apps/lifecycle-started-omitted/output.txt index 514dc450b1f..c4b88e6634a 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-omitted/output.txt +++ b/acceptance/bundle/resources/apps/lifecycle-started-omitted/output.txt @@ -41,7 +41,6 @@ Deployment complete! >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/lifecycle-started-omitted-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/apps/lifecycle-started-omitted/test.toml b/acceptance/bundle/resources/apps/lifecycle-started-omitted/test.toml index bfe2b2f2a72..b5c148642af 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-omitted/test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started-omitted/test.toml @@ -4,5 +4,11 @@ RecordRequests = true Ignore = [".databricks", "databricks.yml"] +# App deployment progress messages are non-deterministic on cloud (different status messages +# per poll), so filter them out entirely. +[[Repls]] +Old = '(?m)^✓ [^\n]*\n' +New = "" + [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/app/app.py b/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/app/app.py index f1a18139c84..ad1e64f9fb0 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/app/app.py +++ b/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/app/app.py @@ -1 +1,5 @@ -print("Hello world!") +import http.server, os + +http.server.HTTPServer( + ("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler +).serve_forever() diff --git a/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/out.test.toml b/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/out.test.toml index a9f28de48a5..42c0997090a 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/out.test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started-terraform-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started-toggle/app/app.py b/acceptance/bundle/resources/apps/lifecycle-started-toggle/app/app.py index d56323cf53f..ad1e64f9fb0 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-toggle/app/app.py +++ b/acceptance/bundle/resources/apps/lifecycle-started-toggle/app/app.py @@ -1 +1,5 @@ -print("Hello world\!") +import http.server, os + +http.server.HTTPServer( + ("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler +).serve_forever() diff --git a/acceptance/bundle/resources/apps/lifecycle-started-toggle/out.test.toml b/acceptance/bundle/resources/apps/lifecycle-started-toggle/out.test.toml index 19b2c349a32..9cfad3fb0d5 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-toggle/out.test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started-toggle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started-toggle/output.txt b/acceptance/bundle/resources/apps/lifecycle-started-toggle/output.txt index 757c544f8b7..2f7ea7c2a84 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-toggle/output.txt +++ b/acceptance/bundle/resources/apps/lifecycle-started-toggle/output.txt @@ -28,7 +28,6 @@ Deployment complete! >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/lifecycle-started-toggle-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! diff --git a/acceptance/bundle/resources/apps/lifecycle-started-toggle/test.toml b/acceptance/bundle/resources/apps/lifecycle-started-toggle/test.toml index bfe2b2f2a72..b5c148642af 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started-toggle/test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started-toggle/test.toml @@ -4,5 +4,11 @@ RecordRequests = true Ignore = [".databricks", "databricks.yml"] +# App deployment progress messages are non-deterministic on cloud (different status messages +# per poll), so filter them out entirely. +[[Repls]] +Old = '(?m)^✓ [^\n]*\n' +New = "" + [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started/app/app.py b/acceptance/bundle/resources/apps/lifecycle-started/app/app.py index f1a18139c84..ad1e64f9fb0 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/app/app.py +++ b/acceptance/bundle/resources/apps/lifecycle-started/app/app.py @@ -1 +1,5 @@ -print("Hello world!") +import http.server, os + +http.server.HTTPServer( + ("", int(os.environ["DATABRICKS_APP_PORT"])), http.server.SimpleHTTPRequestHandler +).serve_forever() diff --git a/acceptance/bundle/resources/apps/lifecycle-started/out.test.toml b/acceptance/bundle/resources/apps/lifecycle-started/out.test.toml index 19b2c349a32..9cfad3fb0d5 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/out.test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/apps/lifecycle-started/output.txt b/acceptance/bundle/resources/apps/lifecycle-started/output.txt index f5cfeeb0214..cfe10a2d65e 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/output.txt +++ b/acceptance/bundle/resources/apps/lifecycle-started/output.txt @@ -15,6 +15,14 @@ Deployment complete! "name": "[UNIQUE_NAME]" } } +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/deployments", + "body": { + "mode": "SNAPSHOT", + "source_code_path": "/Workspace/Users/[USERNAME]/.bundle/lifecycle-started-[UNIQUE_NAME]/default/files/app" + } +} >>> errcode [CLI] apps get [UNIQUE_NAME] "ACTIVE" @@ -25,7 +33,6 @@ Deployment complete! >>> errcode [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/lifecycle-started-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! @@ -52,7 +59,6 @@ Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged >>> errcode [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/lifecycle-started-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! @@ -70,7 +76,23 @@ Deploying resources... Updating deployment state... Deployment complete! ->>> print_requests.py //deployments +>>> print_requests.py //apps +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/stop", + "body": {} +} +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/update", + "body": { + "app": { + "description": "MY_APP_DESCRIPTION_2", + "name": "[UNIQUE_NAME]" + }, + "update_mask": "description,budget_policy_id,usage_policy_id,resources,user_api_scopes,compute_size,git_repository,telemetry_export_destinations" + } +} >>> errcode [CLI] apps get [UNIQUE_NAME] "STOPPED" @@ -83,11 +105,26 @@ Deployment complete! >>> errcode [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/lifecycle-started-[UNIQUE_NAME]/default/files... Deploying resources... -✓ Deployment succeeded Updating deployment state... Deployment complete! ->>> print_requests.py //deployments +>>> print_requests.py //apps +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/update", + "body": { + "app": { + "description": "MY_APP_DESCRIPTION_3", + "name": "[UNIQUE_NAME]" + }, + "update_mask": "description,budget_policy_id,usage_policy_id,resources,user_api_scopes,compute_size,git_repository,telemetry_export_destinations" + } +} +{ + "method": "POST", + "path": "/api/2.0/apps/[UNIQUE_NAME]/start", + "body": {} +} { "method": "POST", "path": "/api/2.0/apps/[UNIQUE_NAME]/deployments", diff --git a/acceptance/bundle/resources/apps/lifecycle-started/script b/acceptance/bundle/resources/apps/lifecycle-started/script index f356cea4b3d..710dec5a10a 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/script +++ b/acceptance/bundle/resources/apps/lifecycle-started/script @@ -31,7 +31,7 @@ title "Stop app externally, then deploy with started=false: app stays stopped" trace update_file.py databricks.yml "started: true" "started: false" trace update_file.py databricks.yml MY_APP_DESCRIPTION MY_APP_DESCRIPTION_2 trace errcode $CLI bundle deploy -trace print_requests.py //deployments +trace print_requests.py //apps rm -f out.requests.txt { trace errcode $CLI apps get $UNIQUE_NAME | jq '.compute_status.state'; } || true @@ -39,6 +39,6 @@ title "Deploy with started=true: compute restarted and code deployed" trace update_file.py databricks.yml "started: false" "started: true" trace update_file.py databricks.yml MY_APP_DESCRIPTION_2 MY_APP_DESCRIPTION_3 trace errcode $CLI bundle deploy -trace print_requests.py //deployments +trace print_requests.py //apps rm -f out.requests.txt { trace errcode $CLI apps get $UNIQUE_NAME | jq '.compute_status.state'; } || true diff --git a/acceptance/bundle/resources/apps/lifecycle-started/test.toml b/acceptance/bundle/resources/apps/lifecycle-started/test.toml index bfe2b2f2a72..b5c148642af 100644 --- a/acceptance/bundle/resources/apps/lifecycle-started/test.toml +++ b/acceptance/bundle/resources/apps/lifecycle-started/test.toml @@ -4,5 +4,11 @@ RecordRequests = true Ignore = [".databricks", "databricks.yml"] +# App deployment progress messages are non-deterministic on cloud (different status messages +# per poll), so filter them out entirely. +[[Repls]] +Old = '(?m)^✓ [^\n]*\n' +New = "" + [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/src/{{.project_name}}/__init__.py.tmpl b/acceptance/bundle/resources/apps/resource-refs/app/app.py similarity index 100% rename from libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/src/{{.project_name}}/__init__.py.tmpl rename to acceptance/bundle/resources/apps/resource-refs/app/app.py diff --git a/acceptance/bundle/resources/apps/resource-refs/databricks.yml b/acceptance/bundle/resources/apps/resource-refs/databricks.yml new file mode 100644 index 00000000000..9db4f36681c --- /dev/null +++ b/acceptance/bundle/resources/apps/resource-refs/databricks.yml @@ -0,0 +1,41 @@ +bundle: + name: resource-refs + +variables: + example_var: + default: "example_value" + +resources: + apps: + data_app: + name: "data-app" + source_code_path: ./app + description: "A Streamlit app that uses a SQL warehouse" + config: + command: ["streamlit", "run", "app.py"] + env: + - name: MY_EXAMPLE_SCHEMA + value: ${resources.schemas.example.catalog_name} + - name: MY_EXAMPLE_JOB + value: ${resources.jobs.example_job.name} + - name: MY_EXAMPLE_JOB_ID + value: ${resources.jobs.example_job.id} + - name: MY_EXAMPLE_VAR + value: ${var.example_var} + schemas: + example: + name: "example_schema" + catalog_name: "main" + + jobs: + example_job: + name: "example_job" + tasks: + - task_key: "example_task" + spark_python_task: + python_file: "./app/app.py" + environment_key: "default" + environments: + - environment_key: "default" + spec: + client: "1" diff --git a/acceptance/bundle/resources/apps/resource-refs/out.test.toml b/acceptance/bundle/resources/apps/resource-refs/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/resources/apps/resource-refs/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/apps/resource-refs/output.txt b/acceptance/bundle/resources/apps/resource-refs/output.txt new file mode 100644 index 00000000000..c2af531b97c --- /dev/null +++ b/acceptance/bundle/resources/apps/resource-refs/output.txt @@ -0,0 +1,65 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/resource-refs/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle run data_app +✓ Getting the status of the app data-app +✓ App is in RUNNING state +✓ App compute is in STOPPED state +✓ Starting the app data-app +✓ App is starting... +✓ App is started! +✓ Deployment succeeded +You can access the app at data-app-123.cloud.databricksapps.com + +>>> print_requests.py //apps +{ + "method": "POST", + "path": "/api/2.0/apps", + "q": { + "no_compute": "true" + }, + "body": { + "description": "A Streamlit app that uses a SQL warehouse", + "name": "data-app" + } +} +{ + "method": "POST", + "path": "/api/2.0/apps/data-app/start", + "body": {} +} +{ + "method": "POST", + "path": "/api/2.0/apps/data-app/deployments", + "body": { + "command": [ + "streamlit", + "run", + "app.py" + ], + "env_vars": [ + { + "name": "MY_EXAMPLE_SCHEMA", + "value": "main" + }, + { + "name": "MY_EXAMPLE_JOB", + "value": "example_job" + }, + { + "name": "MY_EXAMPLE_JOB_ID", + "value": "[NUMID]" + }, + { + "name": "MY_EXAMPLE_VAR", + "value": "example_value" + } + ], + "mode": "SNAPSHOT", + "source_code_path": "/Workspace/Users/[USERNAME]/.bundle/resource-refs/default/files/app" + } +} diff --git a/acceptance/bundle/resources/apps/resource-refs/script b/acceptance/bundle/resources/apps/resource-refs/script new file mode 100644 index 00000000000..ace337470da --- /dev/null +++ b/acceptance/bundle/resources/apps/resource-refs/script @@ -0,0 +1,3 @@ +trace $CLI bundle deploy +trace $CLI bundle run data_app +trace print_requests.py //apps diff --git a/acceptance/bundle/resources/apps/resource-refs/test.toml b/acceptance/bundle/resources/apps/resource-refs/test.toml new file mode 100644 index 00000000000..7d36fb9dc18 --- /dev/null +++ b/acceptance/bundle/resources/apps/resource-refs/test.toml @@ -0,0 +1,2 @@ +Local = true +Cloud = false diff --git a/acceptance/bundle/resources/apps/update/out.requests.direct.json b/acceptance/bundle/resources/apps/update/out.requests.direct.json index 6767b96ee03..e33ad270576 100644 --- a/acceptance/bundle/resources/apps/update/out.requests.direct.json +++ b/acceptance/bundle/resources/apps/update/out.requests.direct.json @@ -15,7 +15,7 @@ "description": "MY_APP_DESCRIPTION", "name": "myappname" }, - "update_mask": "*" + "update_mask": "description,budget_policy_id,usage_policy_id,resources,user_api_scopes,compute_size,git_repository,telemetry_export_destinations" }, "method": "POST", "path": "/api/2.0/apps/myappname/update" diff --git a/acceptance/bundle/resources/apps/update/out.test.toml b/acceptance/bundle/resources/apps/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/apps/update/out.test.toml +++ b/acceptance/bundle/resources/apps/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/catalogs/auto-approve/out.test.toml b/acceptance/bundle/resources/catalogs/auto-approve/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/resources/catalogs/auto-approve/out.test.toml +++ b/acceptance/bundle/resources/catalogs/auto-approve/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/catalogs/basic/out.test.toml b/acceptance/bundle/resources/catalogs/basic/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/resources/catalogs/basic/out.test.toml +++ b/acceptance/bundle/resources/catalogs/basic/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/catalogs/with-schemas/out.test.toml b/acceptance/bundle/resources/catalogs/with-schemas/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/resources/catalogs/with-schemas/out.test.toml +++ b/acceptance/bundle/resources/catalogs/with-schemas/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/data_security_mode/out.test.toml b/acceptance/bundle/resources/clusters/deploy/data_security_mode/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/clusters/deploy/data_security_mode/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/data_security_mode/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/instance_pool/out.test.toml b/acceptance/bundle/resources/clusters/deploy/instance_pool/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/instance_pool/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/instance_pool/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/instance_pool_and_node_type/out.test.toml b/acceptance/bundle/resources/clusters/deploy/instance_pool_and_node_type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/instance_pool_and_node_type/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/instance_pool_and_node_type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/local_ssd_count/out.test.toml b/acceptance/bundle/resources/clusters/deploy/local_ssd_count/out.test.toml index bf217fa2255..5a821e39edc 100644 --- a/acceptance/bundle/resources/clusters/deploy/local_ssd_count/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/local_ssd_count/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true - -[CloudEnvs] - aws = false - azure = false - gcp = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.aws = false +CloudEnvs.azure = false +CloudEnvs.gcp = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/num_workers_absent/out.test.toml b/acceptance/bundle/resources/clusters/deploy/num_workers_absent/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/num_workers_absent/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/num_workers_absent/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/simple/out.test.toml b/acceptance/bundle/resources/clusters/deploy/simple/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/clusters/deploy/simple/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/simple/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/update-after-create/out.test.toml b/acceptance/bundle/resources/clusters/deploy/update-after-create/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/clusters/deploy/update-after-create/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/update-after-create/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/out.test.toml b/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/output.txt b/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/output.txt index 78b00d25ee7..7aa65c2212e 100644 --- a/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/output.txt +++ b/acceptance/bundle/resources/clusters/deploy/update-and-resize-autoscale/output.txt @@ -85,21 +85,21 @@ Deployment complete! === Starting the cluster { "autoscale": { - "max_workers":5, - "min_workers":3 + "max_workers": 5, + "min_workers": 3 }, - "autotermination_minutes":60, + "autotermination_minutes": 60, "aws_attributes": { - "availability":"SPOT_WITH_FALLBACK", - "zone_id":"us-east-1c" + "availability": "SPOT_WITH_FALLBACK", + "zone_id": "us-east-1c" }, - "cluster_id":"[CLUSTER_ID]", - "cluster_name":"test-cluster-[UNIQUE_NAME]", - "driver_node_type_id":"[NODE_TYPE_ID]", - "enable_elastic_disk":false, - "node_type_id":"[NODE_TYPE_ID]", - "spark_version":"13.3.x-snapshot-scala2.12", - "state":"RUNNING" + "cluster_id": "[CLUSTER_ID]", + "cluster_name": "test-cluster-[UNIQUE_NAME]", + "driver_node_type_id": "[NODE_TYPE_ID]", + "enable_elastic_disk": false, + "node_type_id": "[NODE_TYPE_ID]", + "spark_version": "13.3.x-snapshot-scala2.12", + "state": "RUNNING" } === Changing autoscale should call resize API on running cluster diff --git a/acceptance/bundle/resources/clusters/deploy/update-and-resize/out.test.toml b/acceptance/bundle/resources/clusters/deploy/update-and-resize/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/update-and-resize/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/update-and-resize/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/deploy/update-and-resize/output.txt b/acceptance/bundle/resources/clusters/deploy/update-and-resize/output.txt index 43c78fa780e..c202d13080f 100644 --- a/acceptance/bundle/resources/clusters/deploy/update-and-resize/output.txt +++ b/acceptance/bundle/resources/clusters/deploy/update-and-resize/output.txt @@ -44,22 +44,22 @@ Deployment complete! === Starting the cluster { - "autotermination_minutes":60, + "autotermination_minutes": 60, "aws_attributes": { - "availability":"SPOT_WITH_FALLBACK", - "zone_id":"us-east-1c" + "availability": "SPOT_WITH_FALLBACK", + "zone_id": "us-east-1c" }, - "cluster_id":"[CLUSTER_ID]", - "cluster_name":"test-cluster-[UNIQUE_NAME]", - "driver_node_type_id":"[NODE_TYPE_ID]", - "enable_elastic_disk":false, - "node_type_id":"[NODE_TYPE_ID]", - "num_workers":3, + "cluster_id": "[CLUSTER_ID]", + "cluster_name": "test-cluster-[UNIQUE_NAME]", + "driver_node_type_id": "[NODE_TYPE_ID]", + "enable_elastic_disk": false, + "node_type_id": "[NODE_TYPE_ID]", + "num_workers": 3, "spark_conf": { - "spark.executor.memory":"2g" + "spark.executor.memory": "2g" }, - "spark_version":"13.3.x-snapshot-scala2.12", - "state":"RUNNING" + "spark_version": "13.3.x-snapshot-scala2.12", + "state": "RUNNING" } === Changing num_workers should call resize API on running cluster diff --git a/acceptance/bundle/resources/clusters/deploy/workload_type/out.test.toml b/acceptance/bundle/resources/clusters/deploy/workload_type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/clusters/deploy/workload_type/out.test.toml +++ b/acceptance/bundle/resources/clusters/deploy/workload_type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/clusters/run/spark_python_task/out.test.toml b/acceptance/bundle/resources/clusters/run/spark_python_task/out.test.toml index 2f71d08ba8e..af50cb9b76b 100644 --- a/acceptance/bundle/resources/clusters/run/spark_python_task/out.test.toml +++ b/acceptance/bundle/resources/clusters/run/spark_python_task/out.test.toml @@ -2,6 +2,4 @@ Local = false Cloud = true CloudSlow = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/change-embed-credentials/out.test.toml b/acceptance/bundle/resources/dashboards/change-embed-credentials/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/resources/dashboards/change-embed-credentials/out.test.toml +++ b/acceptance/bundle/resources/dashboards/change-embed-credentials/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/change-name/out.test.toml b/acceptance/bundle/resources/dashboards/change-name/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/resources/dashboards/change-name/out.test.toml +++ b/acceptance/bundle/resources/dashboards/change-name/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/change-parent-path/out.test.toml b/acceptance/bundle/resources/dashboards/change-parent-path/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/resources/dashboards/change-parent-path/out.test.toml +++ b/acceptance/bundle/resources/dashboards/change-parent-path/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/change-serialized-dashboard/out.test.toml b/acceptance/bundle/resources/dashboards/change-serialized-dashboard/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/resources/dashboards/change-serialized-dashboard/out.test.toml +++ b/acceptance/bundle/resources/dashboards/change-serialized-dashboard/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/dataset-catalog-schema/out.test.toml b/acceptance/bundle/resources/dashboards/dataset-catalog-schema/out.test.toml index f53dec026c2..c2ac722e76a 100644 --- a/acceptance/bundle/resources/dashboards/dataset-catalog-schema/out.test.toml +++ b/acceptance/bundle/resources/dashboards/dataset-catalog-schema/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/dashboards/delete-trashed-out-of-band/out.test.toml b/acceptance/bundle/resources/dashboards/delete-trashed-out-of-band/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/delete-trashed-out-of-band/out.test.toml +++ b/acceptance/bundle/resources/dashboards/delete-trashed-out-of-band/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/destroy/out.test.toml b/acceptance/bundle/resources/dashboards/destroy/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/destroy/out.test.toml +++ b/acceptance/bundle/resources/dashboards/destroy/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/detect-change/out.test.toml b/acceptance/bundle/resources/dashboards/detect-change/out.test.toml index 87248584bc7..96be4fdfe9d 100644 --- a/acceptance/bundle/resources/dashboards/detect-change/out.test.toml +++ b/acceptance/bundle/resources/dashboards/detect-change/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/generate_inplace/out.test.toml b/acceptance/bundle/resources/dashboards/generate_inplace/out.test.toml index ed27be1295b..bbcef543fa6 100644 --- a/acceptance/bundle/resources/dashboards/generate_inplace/out.test.toml +++ b/acceptance/bundle/resources/dashboards/generate_inplace/out.test.toml @@ -2,6 +2,4 @@ Local = false Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/nested-folders/out.test.toml b/acceptance/bundle/resources/dashboards/nested-folders/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/nested-folders/out.test.toml +++ b/acceptance/bundle/resources/dashboards/nested-folders/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.test.toml b/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.test.toml index 6feb8784c89..8f6c4a03c57 100644 --- a/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.test.toml +++ b/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false RequiresWarehouse = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/simple/out.test.toml b/acceptance/bundle/resources/dashboards/simple/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/simple/out.test.toml +++ b/acceptance/bundle/resources/dashboards/simple/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/simple_outside_bundle_root/out.test.toml b/acceptance/bundle/resources/dashboards/simple_outside_bundle_root/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/simple_outside_bundle_root/out.test.toml +++ b/acceptance/bundle/resources/dashboards/simple_outside_bundle_root/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/simple_syncroot/out.test.toml b/acceptance/bundle/resources/dashboards/simple_syncroot/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/simple_syncroot/out.test.toml +++ b/acceptance/bundle/resources/dashboards/simple_syncroot/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.get_published.direct.txt b/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.get_published.direct.txt index a788614152f..d8dcec89d55 100644 --- a/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.get_published.direct.txt +++ b/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.get_published.direct.txt @@ -1,8 +1,8 @@ >>> errcode [CLI] lakeview get-published [DASHBOARD1_ID] { - "display_name":"test bundle-deploy-dashboard [UNIQUE_NAME]", - "embed_credentials":false, - "revision_create_time":"[TIMESTAMP]", - "warehouse_id":"[TEST_DEFAULT_WAREHOUSE_ID]" + "display_name": "test bundle-deploy-dashboard [UNIQUE_NAME]", + "embed_credentials": false, + "revision_create_time": "[TIMESTAMP]", + "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" } diff --git a/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.test.toml b/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.test.toml index 8b01f72900d..7edd52865f7 100644 --- a/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.test.toml +++ b/acceptance/bundle/resources/dashboards/unpublish-out-of-band/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresWarehouse = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/database_catalogs/basic/out.test.toml b/acceptance/bundle/resources/database_catalogs/basic/out.test.toml index 5b15f017db0..1f9d1fc1b75 100644 --- a/acceptance/bundle/resources/database_catalogs/basic/out.test.toml +++ b/acceptance/bundle/resources/database_catalogs/basic/out.test.toml @@ -3,9 +3,5 @@ Cloud = true CloudSlow = true RequiresUnityCatalog = true RunsOnDbr = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/database_catalogs/basic/output.txt b/acceptance/bundle/resources/database_catalogs/basic/output.txt index 46105a3e8c6..ce8eb0746eb 100644 --- a/acceptance/bundle/resources/database_catalogs/basic/output.txt +++ b/acceptance/bundle/resources/database_catalogs/basic/output.txt @@ -47,6 +47,10 @@ The following resources will be deleted: delete resources.database_catalogs.my_catalog delete resources.database_instances.my_instance +This action will result in the deletion of the following Lakebase database instances. +All data stored in them will be permanently lost: + delete resources.database_instances.my_instance + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-catalog-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/database_instances/single-instance/out.test.toml b/acceptance/bundle/resources/database_instances/single-instance/out.test.toml index 8d2e954f48d..12ab4ea7f78 100644 --- a/acceptance/bundle/resources/database_instances/single-instance/out.test.toml +++ b/acceptance/bundle/resources/database_instances/single-instance/out.test.toml @@ -2,9 +2,5 @@ Local = true Cloud = true CloudSlow = true RequiresUnityCatalog = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/database_instances/single-instance/output.txt b/acceptance/bundle/resources/database_instances/single-instance/output.txt index 8a08317172c..e6b8b506a71 100644 --- a/acceptance/bundle/resources/database_instances/single-instance/output.txt +++ b/acceptance/bundle/resources/database_instances/single-instance/output.txt @@ -59,6 +59,10 @@ Resources: The following resources will be deleted: delete resources.database_instances.my_database +This action will result in the deletion of the following Lakebase database instances. +All data stored in them will be permanently lost: + delete resources.database_instances.my_database + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-single-instance-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/experiments/basic/out.test.toml b/acceptance/bundle/resources/experiments/basic/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/experiments/basic/out.test.toml +++ b/acceptance/bundle/resources/experiments/basic/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/external_locations/out.test.toml b/acceptance/bundle/resources/external_locations/out.test.toml index 5566892a0d7..88423408186 100644 --- a/acceptance/bundle/resources/external_locations/out.test.toml +++ b/acceptance/bundle/resources/external_locations/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/grants/catalogs/out.test.toml b/acceptance/bundle/resources/grants/catalogs/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/resources/grants/catalogs/out.test.toml +++ b/acceptance/bundle/resources/grants/catalogs/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/grants/registered_models/out.test.toml b/acceptance/bundle/resources/grants/registered_models/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/registered_models/out.test.toml +++ b/acceptance/bundle/resources/grants/registered_models/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/all_privileges/out.test.toml b/acceptance/bundle/resources/grants/schemas/all_privileges/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/all_privileges/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/all_privileges/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/change_privilege/out.test.toml b/acceptance/bundle/resources/grants/schemas/change_privilege/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/change_privilege/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/change_privilege/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/duplicate_principals/out.test.toml b/acceptance/bundle/resources/grants/schemas/duplicate_principals/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/duplicate_principals/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/duplicate_principals/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/duplicate_privileges/out.test.toml b/acceptance/bundle/resources/grants/schemas/duplicate_privileges/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/duplicate_privileges/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/duplicate_privileges/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/empty_array/out.test.toml b/acceptance/bundle/resources/grants/schemas/empty_array/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/empty_array/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/empty_array/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/out_of_band_principal/out.test.toml b/acceptance/bundle/resources/grants/schemas/out_of_band_principal/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/out_of_band_principal/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/out_of_band_principal/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/schemas/remove_principal/out.test.toml b/acceptance/bundle/resources/grants/schemas/remove_principal/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/schemas/remove_principal/out.test.toml +++ b/acceptance/bundle/resources/grants/schemas/remove_principal/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/grants/volumes/out.test.toml b/acceptance/bundle/resources/grants/volumes/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/grants/volumes/out.test.toml +++ b/acceptance/bundle/resources/grants/volumes/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/independent/out.test.toml b/acceptance/bundle/resources/independent/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/independent/out.test.toml +++ b/acceptance/bundle/resources/independent/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/alert-task/databricks.yml.tmpl b/acceptance/bundle/resources/jobs/alert-task/databricks.yml.tmpl new file mode 100644 index 00000000000..8334e4af795 --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/databricks.yml.tmpl @@ -0,0 +1,12 @@ +bundle: + name: alert-task-$UNIQUE_NAME + +resources: + jobs: + my_job: + name: alert-task-$UNIQUE_NAME + tasks: + - task_key: alert_task + alert_task: + workspace_path: ./my_alert.dbalert.json + warehouse_id: $TEST_DEFAULT_WAREHOUSE_ID diff --git a/acceptance/bundle/resources/jobs/alert-task/my_alert.dbalert.json b/acceptance/bundle/resources/jobs/alert-task/my_alert.dbalert.json new file mode 100644 index 00000000000..9b033f913fd --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/my_alert.dbalert.json @@ -0,0 +1,19 @@ +{ + "query_lines": ["SELECT 1"], + "schedule": { + "quartz_cron_schedule": "0 0 * * * ?", + "timezone_id": "UTC" + }, + "evaluation": { + "comparison_operator": "EQUAL", + "source": { + "name": "1", + "aggregation": "MAX" + }, + "threshold": { + "value": { + "double_value": 1 + } + } + } +} diff --git a/acceptance/bundle/resources/jobs/alert-task/out.test.toml b/acceptance/bundle/resources/jobs/alert-task/out.test.toml new file mode 100644 index 00000000000..bbc7fcfd1bd --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/alert-task/output.txt b/acceptance/bundle/resources/jobs/alert-task/output.txt new file mode 100644 index 00000000000..569e0f98454 --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/output.txt @@ -0,0 +1,21 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/alert-task-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] jobs get [JOB_ID] +{ + "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]", + "workspace_path": "/Workspace/Users/[USERNAME]/.bundle/alert-task-[UNIQUE_NAME]/default/files/my_alert.dbalert.json" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.my_job + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/alert-task-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/jobs/alert-task/script b/acceptance/bundle/resources/jobs/alert-task/script new file mode 100644 index 00000000000..f2d3d0fc9b2 --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/script @@ -0,0 +1,13 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve +} +trap cleanup EXIT + +trace $CLI bundle deploy + +job_id=$($CLI bundle summary -o json | jq -r '.resources.jobs.my_job.id') +echo "$job_id:JOB_ID" >> ACC_REPLS + +trace $CLI jobs get $job_id | jq '.settings.tasks[0].alert_task' diff --git a/acceptance/bundle/resources/jobs/alert-task/test.toml b/acceptance/bundle/resources/jobs/alert-task/test.toml new file mode 100644 index 00000000000..838184afdd9 --- /dev/null +++ b/acceptance/bundle/resources/jobs/alert-task/test.toml @@ -0,0 +1,7 @@ +Local = true +Cloud = true +RecordRequests = false +Ignore = ["databricks.yml", ".databricks"] + +[Env] +MSYS_NO_PATHCONV = "1" diff --git a/acceptance/bundle/resources/jobs/big_id/out.test.toml b/acceptance/bundle/resources/jobs/big_id/out.test.toml index 77244ff10ac..71970b719d4 100644 --- a/acceptance/bundle/resources/jobs/big_id/out.test.toml +++ b/acceptance/bundle/resources/jobs/big_id/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/resources/jobs/check-metadata/out.test.toml b/acceptance/bundle/resources/jobs/check-metadata/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/bundle/resources/jobs/check-metadata/out.test.toml +++ b/acceptance/bundle/resources/jobs/check-metadata/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/create-error/out.test.toml b/acceptance/bundle/resources/jobs/create-error/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resources/jobs/create-error/out.test.toml +++ b/acceptance/bundle/resources/jobs/create-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/jobs/delete_job/databricks.yml b/acceptance/bundle/resources/jobs/delete_job/databricks.yml index eefbdf73190..d39301fe0be 100644 --- a/acceptance/bundle/resources/jobs/delete_job/databricks.yml +++ b/acceptance/bundle/resources/jobs/delete_job/databricks.yml @@ -11,7 +11,7 @@ resources: package_name: "whl" entry_point: "run" libraries: - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl - task_key: TestTask2 for_each_task: inputs: "[1]" @@ -22,4 +22,4 @@ resources: package_name: "whl" entry_point: "run" libraries: - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl diff --git a/acceptance/bundle/resources/jobs/delete_job/out.plan.direct.json b/acceptance/bundle/resources/jobs/delete_job/out.plan.direct.json index dd328b616f8..8d1518fc1a0 100644 --- a/acceptance/bundle/resources/jobs/delete_job/out.plan.direct.json +++ b/acceptance/bundle/resources/jobs/delete_job/out.plan.direct.json @@ -29,7 +29,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -48,7 +48,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { diff --git a/acceptance/bundle/resources/jobs/delete_job/out.test.toml b/acceptance/bundle/resources/jobs/delete_job/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/delete_job/out.test.toml +++ b/acceptance/bundle/resources/jobs/delete_job/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/delete_job/output.txt b/acceptance/bundle/resources/jobs/delete_job/output.txt index 11e04449cfe..0fc5fc36e61 100644 --- a/acceptance/bundle/resources/jobs/delete_job/output.txt +++ b/acceptance/bundle/resources/jobs/delete_job/output.txt @@ -30,7 +30,7 @@ Deployment complete! "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -46,7 +46,7 @@ Deployment complete! "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { diff --git a/acceptance/bundle/resources/jobs/delete_task/databricks.yml b/acceptance/bundle/resources/jobs/delete_task/databricks.yml index 6b01047f7a6..e830bdc8ad0 100644 --- a/acceptance/bundle/resources/jobs/delete_task/databricks.yml +++ b/acceptance/bundle/resources/jobs/delete_task/databricks.yml @@ -11,7 +11,7 @@ resources: package_name: "whl" # TO_DELETE entry_point: "run" # TO_DELETE libraries: # TO_DELETE - - whl: /Workspace/Users/foo@bar.com/mywheel.whl # TO_DELETE + - whl: /Workspace/Users/foo@bar.test/mywheel.whl # TO_DELETE - task_key: TestTask2 for_each_task: inputs: "[1]" @@ -22,4 +22,4 @@ resources: package_name: "whl" entry_point: "run" libraries: - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl diff --git a/acceptance/bundle/resources/jobs/delete_task/out.plan_create.direct.json b/acceptance/bundle/resources/jobs/delete_task/out.plan_create.direct.json index 439cdc2473b..5fd55841a57 100644 --- a/acceptance/bundle/resources/jobs/delete_task/out.plan_create.direct.json +++ b/acceptance/bundle/resources/jobs/delete_task/out.plan_create.direct.json @@ -22,7 +22,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -38,7 +38,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { diff --git a/acceptance/bundle/resources/jobs/delete_task/out.plan_update.direct.json b/acceptance/bundle/resources/jobs/delete_task/out.plan_update.direct.json index 929dfdff928..332c54a6efb 100644 --- a/acceptance/bundle/resources/jobs/delete_task/out.plan_update.direct.json +++ b/acceptance/bundle/resources/jobs/delete_task/out.plan_update.direct.json @@ -27,7 +27,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -65,7 +65,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -84,7 +84,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -114,7 +114,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { @@ -128,7 +128,7 @@ "existing_cluster_id": "0717-132531-5opeqon1", "libraries": [ { - "whl": "/Workspace/Users/foo@bar.com/mywheel.whl" + "whl": "/Workspace/Users/foo@bar.test/mywheel.whl" } ], "python_wheel_task": { diff --git a/acceptance/bundle/resources/jobs/delete_task/out.test.toml b/acceptance/bundle/resources/jobs/delete_task/out.test.toml index c820fbee966..8ffbd40f24c 100644 --- a/acceptance/bundle/resources/jobs/delete_task/out.test.toml +++ b/acceptance/bundle/resources/jobs/delete_task/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/resources/jobs/delete_task/output.txt b/acceptance/bundle/resources/jobs/delete_task/output.txt index f2605f57d1c..eebf003d74b 100644 --- a/acceptance/bundle/resources/jobs/delete_task/output.txt +++ b/acceptance/bundle/resources/jobs/delete_task/output.txt @@ -21,7 +21,7 @@ resources: package_name: "whl" entry_point: "run" libraries: - - whl: /Workspace/Users/foo@bar.com/mywheel.whl + - whl: /Workspace/Users/foo@bar.test/mywheel.whl Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... Deploying resources... Updating deployment state... diff --git a/acceptance/bundle/resources/jobs/double-underscore-keys/out.test.toml b/acceptance/bundle/resources/jobs/double-underscore-keys/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/jobs/double-underscore-keys/out.test.toml +++ b/acceptance/bundle/resources/jobs/double-underscore-keys/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/fail-on-active-runs/out.test.toml b/acceptance/bundle/resources/jobs/fail-on-active-runs/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/jobs/fail-on-active-runs/out.test.toml +++ b/acceptance/bundle/resources/jobs/fail-on-active-runs/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/instance_pool_and_node_type/out.test.toml b/acceptance/bundle/resources/jobs/instance_pool_and_node_type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/instance_pool_and_node_type/out.test.toml +++ b/acceptance/bundle/resources/jobs/instance_pool_and_node_type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml b/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml index 0ebfd0a96bd..e5a4c7283a6 100644 --- a/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml +++ b/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/num_workers/out.test.toml b/acceptance/bundle/resources/jobs/num_workers/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/num_workers/out.test.toml +++ b/acceptance/bundle/resources/jobs/num_workers/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/on_failure_empty_slice/out.test.toml b/acceptance/bundle/resources/jobs/on_failure_empty_slice/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/on_failure_empty_slice/out.test.toml +++ b/acceptance/bundle/resources/jobs/on_failure_empty_slice/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/remote_add_tag/out.test.toml b/acceptance/bundle/resources/jobs/remote_add_tag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/remote_add_tag/out.test.toml +++ b/acceptance/bundle/resources/jobs/remote_add_tag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/remote_delete/deploy/out.test.toml b/acceptance/bundle/resources/jobs/remote_delete/deploy/out.test.toml index c820fbee966..8ffbd40f24c 100644 --- a/acceptance/bundle/resources/jobs/remote_delete/deploy/out.test.toml +++ b/acceptance/bundle/resources/jobs/remote_delete/deploy/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/resources/jobs/remote_delete/destroy/out.test.toml b/acceptance/bundle/resources/jobs/remote_delete/destroy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/remote_delete/destroy/out.test.toml +++ b/acceptance/bundle/resources/jobs/remote_delete/destroy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/remote_matches_config/out.test.toml b/acceptance/bundle/resources/jobs/remote_matches_config/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/remote_matches_config/out.test.toml +++ b/acceptance/bundle/resources/jobs/remote_matches_config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/shared-root-path/out.test.toml b/acceptance/bundle/resources/jobs/shared-root-path/out.test.toml index 0ebfd0a96bd..e5a4c7283a6 100644 --- a/acceptance/bundle/resources/jobs/shared-root-path/out.test.toml +++ b/acceptance/bundle/resources/jobs/shared-root-path/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/tags_empty_map/out.test.toml b/acceptance/bundle/resources/jobs/tags_empty_map/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/tags_empty_map/out.test.toml +++ b/acceptance/bundle/resources/jobs/tags_empty_map/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/task-source/out.test.toml b/acceptance/bundle/resources/jobs/task-source/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/task-source/out.test.toml +++ b/acceptance/bundle/resources/jobs/task-source/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/tasks-reorder-locally/out.test.toml b/acceptance/bundle/resources/jobs/tasks-reorder-locally/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/tasks-reorder-locally/out.test.toml +++ b/acceptance/bundle/resources/jobs/tasks-reorder-locally/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/update/out.test.toml b/acceptance/bundle/resources/jobs/update/out.test.toml index c820fbee966..8ffbd40f24c 100644 --- a/acceptance/bundle/resources/jobs/update/out.test.toml +++ b/acceptance/bundle/resources/jobs/update/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/resources/jobs/update_single_node/out.test.toml b/acceptance/bundle/resources/jobs/update_single_node/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/jobs/update_single_node/out.test.toml +++ b/acceptance/bundle/resources/jobs/update_single_node/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml index 7190c9b30bf..2d812727e32 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/basic/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/catalog-name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/name-change/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt index defc19c4592..2f784d9f37d 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/route-optimized/output.txt @@ -49,22 +49,22 @@ Deployment complete! "config": { "served_entities": [ { - "entity_name":"system.ai.llama_v3_2_1b_instruct", - "entity_version":"1", - "name":"llama", - "scale_to_zero_enabled":true, - "workload_size":"Small" + "entity_name": "system.ai.llama_v3_2_1b_instruct", + "entity_version": "1", + "name": "llama", + "scale_to_zero_enabled": true, + "workload_size": "Small" } ] }, - "creator":"[USERNAME]", - "id":"[UUID]", - "name":"[ORIGINAL_ENDPOINT_ID]", - "permission_level":"CAN_MANAGE", - "route_optimized":true, + "creator": "[USERNAME]", + "id": "[UUID]", + "name": "[ORIGINAL_ENDPOINT_ID]", + "permission_level": "CAN_MANAGE", + "route_optimized": true, "state": { - "config_update":"NOT_UPDATING", - "ready":"NOT_READY" + "config_update": "NOT_UPDATING", + "ready": "NOT_READY" } } diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/schema-name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/recreate/table-prefix/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml index 1573e025f6e..68b04957a4c 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/running-endpoint/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true CloudSlow = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/ai-gateway/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/update/ai-gateway/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/ai-gateway/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/update/ai-gateway/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/both_gateway_and_tags/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/update/both_gateway_and_tags/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/both_gateway_and_tags/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/update/both_gateway_and_tags/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/config/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/update/config/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/config/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/update/config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/update/email-notifications/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/model_serving_endpoints/update/tags/out.test.toml b/acceptance/bundle/resources/model_serving_endpoints/update/tags/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/model_serving_endpoints/update/tags/out.test.toml +++ b/acceptance/bundle/resources/model_serving_endpoints/update/tags/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/models/basic/out.test.toml b/acceptance/bundle/resources/models/basic/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/models/basic/out.test.toml +++ b/acceptance/bundle/resources/models/basic/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/analyze_requests.py b/acceptance/bundle/resources/permissions/analyze_requests.py index bd540e017fe..185b22df4f3 100755 --- a/acceptance/bundle/resources/permissions/analyze_requests.py +++ b/acceptance/bundle/resources/permissions/analyze_requests.py @@ -3,10 +3,10 @@ Analyze all requests recorded in subtests to highlight differences between direct and terraform. """ -import os -import re import json +import re import sys +import tomllib from pathlib import Path from difflib import unified_diff @@ -91,6 +91,20 @@ def to_slash(x): return str(x).replace("\\", "/") +def load_supported_engines(path): + current = path + while True: + for name in ("out.test.toml", "test.toml"): + config_file = current / name + if config_file.exists(): + with config_file.open("rb") as fobj: + config = tomllib.load(fobj) + return set(config.get("EnvMatrix", {}).get("DATABRICKS_BUNDLE_ENGINE", [])) + if current == Path("."): + return set() + current = current.parent + + def main(): current_dir = Path(".") @@ -104,10 +118,13 @@ def main(): terraform_file = direct_file.parent / direct_file.name.replace(".direct.", ".terraform.") fname = to_slash(direct_file) + supported_engines = load_supported_engines(direct_file.parent) if terraform_file.exists(): result, diff = compare_files(direct_file, terraform_file) print(result + " " + fname + diff) + elif "terraform" not in supported_engines: + print(f"DIRECT_ONLY {fname}") else: print(f"ERROR {fname}: Missing terraform file {to_slash(terraform_file)}") diff --git a/acceptance/bundle/resources/permissions/apps/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/apps/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/apps/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/apps/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/apps/other_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/apps/other_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/apps/other_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/apps/other_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/clusters/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/clusters/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/clusters/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/clusters/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/clusters/target/out.test.toml b/acceptance/bundle/resources/permissions/clusters/target/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/clusters/target/out.test.toml +++ b/acceptance/bundle/resources/permissions/clusters/target/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml b/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml index 39d757a11ab..2cae6e8241f 100644 --- a/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml +++ b/acceptance/bundle/resources/permissions/dashboards/create/out.test.toml @@ -1,9 +1,5 @@ Local = false Cloud = true RequiresWarehouse = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/database_instances/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/database_instances/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/database_instances/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/database_instances/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/database_instances/current_can_manage/output.txt b/acceptance/bundle/resources/permissions/database_instances/current_can_manage/output.txt index 092bc944ed2..87a9a4f993c 100644 --- a/acceptance/bundle/resources/permissions/database_instances/current_can_manage/output.txt +++ b/acceptance/bundle/resources/permissions/database_instances/current_can_manage/output.txt @@ -61,6 +61,10 @@ Warning: unknown field: instance_profile_arn The following resources will be deleted: delete resources.database_instances.foo +This action will result in the deletion of the following Lakebase database instances. +All data stored in them will be permanently lost: + delete resources.database_instances.foo + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default Deleting files... diff --git a/acceptance/bundle/resources/permissions/experiments/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/experiments/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/experiments/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/experiments/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/factcheck/out.test.toml b/acceptance/bundle/resources/permissions/factcheck/out.test.toml index 746cd40b8c9..581c975b773 100644 --- a/acceptance/bundle/resources/permissions/factcheck/out.test.toml +++ b/acceptance/bundle/resources/permissions/factcheck/out.test.toml @@ -2,9 +2,5 @@ Local = true Cloud = true CloudSlow = true RunsOnDbr = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/added_remotely/out.test.toml b/acceptance/bundle/resources/permissions/jobs/added_remotely/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/added_remotely/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/added_remotely/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/added_remotely/output.txt b/acceptance/bundle/resources/permissions/jobs/added_remotely/output.txt index 0f8c9ce6e05..c55126ce725 100644 --- a/acceptance/bundle/resources/permissions/jobs/added_remotely/output.txt +++ b/acceptance/bundle/resources/permissions/jobs/added_remotely/output.txt @@ -20,38 +20,38 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } === Add permissions out of band @@ -61,47 +61,47 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_MANAGE" + "inherited": false, + "permission_level": "CAN_MANAGE" } ], - "group_name":"admin-team" + "group_name": "admin-team" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } >>> [CLI] bundle plan @@ -126,36 +126,36 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } diff --git a/acceptance/bundle/resources/permissions/jobs/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/jobs/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/current_can_manage_run/out.test.toml b/acceptance/bundle/resources/permissions/jobs/current_can_manage_run/out.test.toml index a888431266a..887e4650a78 100644 --- a/acceptance/bundle/resources/permissions/jobs/current_can_manage_run/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/current_can_manage_run/out.test.toml @@ -1,8 +1,4 @@ Local = true Cloud = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/current_is_owner/out.test.toml b/acceptance/bundle/resources/permissions/jobs/current_is_owner/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/current_is_owner/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/current_is_owner/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_create.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_create.json deleted file mode 100644 index 924f06a6f42..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_create.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited": true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level": "CAN_MANAGE" - } - ], - "group_name": "admins" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "CAN_MANAGE" - } - ], - "group_name": "test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "CAN_MANAGE_RUN" - } - ], - "display_name": "test-dabs-1@databricks.com", - "user_name": "test-dabs-1@databricks.com" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "CAN_VIEW" - } - ], - "display_name": "test-dabs-2@databricks.com", - "user_name": "test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "IS_OWNER" - } - ], - "display_name": "[USERNAME]", - "service_principal_name": "[USERNAME]" - } - ], - "object_id": "/jobs/[NUMID]", - "object_type": "job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_update.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_update.json deleted file mode 100644 index 5d43ee3943b..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.permissions_update.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited": true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level": "CAN_MANAGE" - } - ], - "group_name": "admins" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "CAN_MANAGE" - } - ], - "group_name": "test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "CAN_VIEW" - } - ], - "display_name": "test-dabs-2@databricks.com", - "user_name": "test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited": false, - "permission_level": "IS_OWNER" - } - ], - "display_name": "[USERNAME]", - "service_principal_name": "[USERNAME]" - } - ], - "object_id": "/jobs/[NUMID]", - "object_type": "job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_create.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_create.json deleted file mode 100644 index c9ad7651154..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_create.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "method": "POST", - "path": "/api/2.2/jobs/create", - "body": { - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "format": "MULTI_TASK", - "max_concurrent_runs": 1, - "name": "job with permissions", - "queue": { - "enabled": true - }, - "tasks": [ - { - "notebook_task": { - "notebook_path": "/Workspace/Users/tester@databricks.com/notebook", - "source": "WORKSPACE" - }, - "task_key": "main" - } - ], - "access_control_list": null - } -} -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.direct.json deleted file mode 100644 index 6564b6f8bde..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.direct.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "method": "POST", - "path": "/api/2.2/jobs/delete", - "body": { - "job_id": [NUMID] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.terraform.json deleted file mode 100644 index cd29de0d552..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_destroy.terraform.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "permission_level": "IS_OWNER", - "user_name": "[USERNAME]" - } - ] - } -} -{ - "method": "POST", - "path": "/api/2.2/jobs/delete", - "body": { - "job_id": [NUMID] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_update.json b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_update.json deleted file mode 100644 index a2fb2f6ed1a..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.requests_update.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.test.toml deleted file mode 100644 index 7190c9b30bf..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.test.toml +++ /dev/null @@ -1,6 +0,0 @@ -Local = false -Cloud = true -RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/script b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/script deleted file mode 100644 index cc1d9464e1d..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/script +++ /dev/null @@ -1,49 +0,0 @@ -print_requests() { - jq 'select(.path | contains("/jobs/")) | select(.method != "GET")' < out.requests.txt - rm out.requests.txt -} - -envsubst < databricks.yml.tmpl > databricks.yml -# I've tried to create unique users but then 'databricks users delete' does not work, so I stopped with that. -$CLI users create --id test-dabs-1@databricks.com --user-name test-dabs-1@databricks.com &> /dev/null || true -$CLI users create --id test-dabs-2@databricks.com --user-name test-dabs-2@databricks.com &> /dev/null || true -$CLI groups create --id test-dabs-group-1@databricks.com --display-name test-dabs-group-1 &> /dev/null || true -rm -f out.requests.txt - -trace $CLI bundle plan -o json > out.plan_create.$DATABRICKS_BUNDLE_ENGINE.json - -cleanup() { - trace errcode $CLI bundle destroy --auto-approve - trace print_requests > out.requests_destroy.$DATABRICKS_BUNDLE_ENGINE.json -} -trap cleanup EXIT - -sort_acl_requests() { - # ideally we would not need this, but I cannot figure out why terraform does this order for create: - # CAN_MANAGE_RUN, IS_OWNER, CAN_VIEW, CAN_MANAGE - jq '.body.access_control_list |= if . != null then sort_by(.permission_level, (.group_name // ""), (.user_name != null), (.service_principal_name != null)) else . end' -} - -sort_acl() { - jq '.access_control_list |= if . != null then sort_by(.all_permissions[0].permission_level, (.group_name // ""), (.user_name != null), (.service_principal_name != null)) else . end' -} - -trace $CLI bundle deploy -# Terraform always puts group permissions after user permissions in the request, so we store requests in a different file -# although they are semantically the same. We're not doing the same transformation in direct, because permissions get endpoint uses a different order. -print_requests | sort_acl_requests > out.requests_create.json - -trace $CLI bundle plan - -job_id=$($CLI bundle summary --output json | jq -r '.resources.jobs.job_with_permissions.id') - -$CLI permissions get jobs "$job_id" | sort_acl > out.permissions_create.json -rm -f out.requests.txt - -title "Delete one permission and deploy again\n" -grep -v DELETE databricks.yml > tmp.yml && mv tmp.yml databricks.yml -trace $CLI bundle deploy -print_requests | sort_acl_requests > out.requests_update.json - -$CLI permissions get jobs "$job_id" | sort_acl > out.permissions_update.json -rm out.requests.txt diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/test.toml deleted file mode 100644 index b0201d85177..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = false -Cloud = true -RequiresUnityCatalog = true -RecordRequests = true -Ignore = ['.databricks'] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/databricks.yml.tmpl b/acceptance/bundle/resources/permissions/jobs/delete_one/databricks.yml.tmpl similarity index 100% rename from acceptance/bundle/resources/permissions/jobs/delete_one/cloud/databricks.yml.tmpl rename to acceptance/bundle/resources/permissions/jobs/delete_one/databricks.yml.tmpl diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/databricks.yml.tmpl b/acceptance/bundle/resources/permissions/jobs/delete_one/local/databricks.yml.tmpl deleted file mode 100644 index 4da90a66137..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/databricks.yml.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -bundle: - name: test-bundle-$UNIQUE_NAME - -resources: - jobs: - job_with_permissions: - name: job with permissions - tasks: - - task_key: main - notebook_task: - notebook_path: /Workspace/Users/tester@databricks.com/notebook - source: WORKSPACE # otherwise TF implementation adds this to request and triggers diff - permissions: - - level: CAN_MANAGE_RUN # DELETE - user_name: test-dabs-1@databricks.com # DELETE - - level: CAN_MANAGE - group_name: test-dabs-group-1 - - level: CAN_VIEW - user_name: test-dabs-2@databricks.com diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.direct.json deleted file mode 100644 index 254ddaba75b..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.direct.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE_RUN" - } - ], - "display_name":"test-dabs-1@databricks.com", - "user_name":"test-dabs-1@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_VIEW" - } - ], - "display_name":"test-dabs-2@databricks.com", - "user_name":"test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"IS_OWNER" - } - ], - "display_name":"[USERNAME]", - "service_principal_name":"[USERNAME]" - }, - { - "all_permissions": [ - { - "inherited":true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"admins" - } - ], - "object_id":"/jobs/[NUMID]", - "object_type":"job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.terraform.json deleted file mode 100644 index bf3184e0a29..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_create.terraform.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE_RUN" - } - ], - "display_name":"test-dabs-1@databricks.com", - "user_name":"test-dabs-1@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_VIEW" - } - ], - "display_name":"test-dabs-2@databricks.com", - "user_name":"test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"IS_OWNER" - } - ], - "display_name":"[USERNAME]", - "service_principal_name":"[USERNAME]" - }, - { - "all_permissions": [ - { - "inherited":true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"admins" - } - ], - "object_id":"/jobs/[NUMID]", - "object_type":"job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.direct.json deleted file mode 100644 index 08607add801..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.direct.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_VIEW" - } - ], - "display_name":"test-dabs-2@databricks.com", - "user_name":"test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"IS_OWNER" - } - ], - "display_name":"[USERNAME]", - "service_principal_name":"[USERNAME]" - }, - { - "all_permissions": [ - { - "inherited":true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"admins" - } - ], - "object_id":"/jobs/[NUMID]", - "object_type":"job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.terraform.json deleted file mode 100644 index 659f4699e22..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.permissions_update.terraform.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "access_control_list": [ - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_VIEW" - } - ], - "display_name":"test-dabs-2@databricks.com", - "user_name":"test-dabs-2@databricks.com" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"test-dabs-group-1" - }, - { - "all_permissions": [ - { - "inherited":false, - "permission_level":"IS_OWNER" - } - ], - "display_name":"[USERNAME]", - "service_principal_name":"[USERNAME]" - }, - { - "all_permissions": [ - { - "inherited":true, - "inherited_from_object": [ - "/jobs/" - ], - "permission_level":"CAN_MANAGE" - } - ], - "group_name":"admins" - } - ], - "object_id":"/jobs/[NUMID]", - "object_type":"job" -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.direct.json deleted file mode 100644 index 10b578fb83e..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.direct.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "plan_version": 2, - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.jobs.job_with_permissions": { - "action": "create", - "new_state": { - "value": { - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "format": "MULTI_TASK", - "max_concurrent_runs": 1, - "name": "job with permissions", - "queue": { - "enabled": true - }, - "tasks": [ - { - "notebook_task": { - "notebook_path": "/Workspace/Users/tester@databricks.com/notebook", - "source": "WORKSPACE" - }, - "task_key": "main" - } - ] - } - } - }, - "resources.jobs.job_with_permissions.permissions": { - "depends_on": [ - { - "node": "resources.jobs.job_with_permissions", - "label": "${resources.jobs.job_with_permissions.id}" - } - ], - "action": "create", - "new_state": { - "value": { - "object_id": "", - "__embed__": [ - { - "level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - { - "level": "CAN_MANAGE", - "group_name": "test-dabs-group-1" - }, - { - "level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - }, - "vars": { - "object_id": "/jobs/${resources.jobs.job_with_permissions.id}" - } - } - } - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.terraform.json deleted file mode 100644 index 839b2168abe..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_create.terraform.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.jobs.job_with_permissions": { - "action": "create" - }, - "resources.jobs.job_with_permissions.permissions": { - "action": "create" - } - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.direct.json deleted file mode 100644 index d2bd98bad89..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.direct.json +++ /dev/null @@ -1,137 +0,0 @@ -{ - "plan_version": 2, - "cli_version": "[DEV_VERSION]", - "lineage": "[UUID]", - "serial": 1, - "plan": { - "resources.jobs.job_with_permissions": { - "action": "skip", - "remote_state": { - "created_time": [UNIX_TIME_MILLIS], - "creator_user_name": "[USERNAME]", - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "email_notifications": {}, - "format": "MULTI_TASK", - "job_id": [NUMID], - "max_concurrent_runs": 1, - "name": "job with permissions", - "queue": { - "enabled": true - }, - "run_as_user_name": "[USERNAME]", - "tasks": [ - { - "email_notifications": {}, - "notebook_task": { - "notebook_path": "/Workspace/Users/tester@databricks.com/notebook", - "source": "WORKSPACE" - }, - "run_if": "ALL_SUCCESS", - "task_key": "main", - "timeout_seconds": 0 - } - ], - "timeout_seconds": 0, - "webhook_notifications": {} - }, - "changes": { - "email_notifications": { - "action": "skip", - "reason": "empty", - "remote": {} - }, - "tasks[task_key='main'].email_notifications": { - "action": "skip", - "reason": "empty", - "remote": {} - }, - "tasks[task_key='main'].run_if": { - "action": "skip", - "reason": "backend_default", - "remote": "ALL_SUCCESS" - }, - "tasks[task_key='main'].timeout_seconds": { - "action": "skip", - "reason": "empty", - "remote": 0 - }, - "timeout_seconds": { - "action": "skip", - "reason": "empty", - "remote": 0 - }, - "webhook_notifications": { - "action": "skip", - "reason": "empty", - "remote": {} - } - } - }, - "resources.jobs.job_with_permissions.permissions": { - "depends_on": [ - { - "node": "resources.jobs.job_with_permissions", - "label": "${resources.jobs.job_with_permissions.id}" - } - ], - "action": "update", - "new_state": { - "value": { - "object_id": "/jobs/[NUMID]", - "__embed__": [ - { - "level": "CAN_MANAGE", - "group_name": "test-dabs-group-1" - }, - { - "level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } - }, - "remote_state": { - "object_id": "/jobs/[NUMID]", - "__embed__": [ - { - "level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - { - "level": "CAN_MANAGE", - "group_name": "test-dabs-group-1" - }, - { - "level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - }, - "changes": { - "[user_name='test-dabs-1@databricks.com']": { - "action": "update", - "old": { - "level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - "remote": { - "level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - } - } - } - } - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.terraform.json deleted file mode 100644 index a88eeedacc0..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.plan_update.terraform.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.jobs.job_with_permissions": { - "action": "skip" - }, - "resources.jobs.job_with_permissions.permissions": { - "action": "update" - } - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.direct.json deleted file mode 100644 index 3ae13b77ba5..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.direct.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "method": "POST", - "path": "/api/2.2/jobs/create", - "body": { - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "format": "MULTI_TASK", - "max_concurrent_runs": 1, - "name": "job with permissions", - "queue": { - "enabled": true - }, - "tasks": [ - { - "notebook_task": { - "notebook_path": "/Workspace/Users/tester@databricks.com/notebook", - "source": "WORKSPACE" - }, - "task_key": "main" - } - ] - } -} -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "permission_level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.terraform.json deleted file mode 100644 index e1b772f9aa8..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_create.terraform.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "method": "POST", - "path": "/api/2.2/jobs/create", - "body": { - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "format": "MULTI_TASK", - "max_concurrent_runs": 1, - "name": "job with permissions", - "queue": { - "enabled": true - }, - "tasks": [ - { - "notebook_task": { - "notebook_path": "/Workspace/Users/tester@databricks.com/notebook", - "source": "WORKSPACE" - }, - "task_key": "main" - } - ] - } -} -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "permission_level": "CAN_MANAGE_RUN", - "user_name": "test-dabs-1@databricks.com" - }, - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.direct.json deleted file mode 100644 index a2fb2f6ed1a..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.direct.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.terraform.json deleted file mode 100644 index 3ccacad2ea7..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_update.terraform.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", - "body": { - "access_control_list": [ - { - "permission_level": "CAN_VIEW", - "user_name": "test-dabs-2@databricks.com" - }, - { - "group_name": "test-dabs-group-1", - "permission_level": "CAN_MANAGE" - }, - { - "permission_level": "IS_OWNER", - "service_principal_name": "[USERNAME]" - } - ] - } -} diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.test.toml deleted file mode 100644 index d560f1de043..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/output.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/local/output.txt deleted file mode 100644 index cefbf2e73ec..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/output.txt +++ /dev/null @@ -1,37 +0,0 @@ - ->>> [CLI] bundle plan -o json - ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... -Deploying resources... -Updating deployment state... -Deployment complete! - ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged - -=== Delete one permission and deploy again - ->>> [CLI] bundle plan -update jobs.job_with_permissions.permissions - -Plan: 0 to add, 1 to change, 0 to delete, 1 unchanged - ->>> [CLI] bundle plan -o json - ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... -Deploying resources... -Updating deployment state... -Deployment complete! - ->>> errcode [CLI] bundle destroy --auto-approve -The following resources will be deleted: - delete resources.jobs.job_with_permissions - -All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default - -Deleting files... -Destroy complete! - ->>> print_requests diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/script b/acceptance/bundle/resources/permissions/jobs/delete_one/local/script deleted file mode 100644 index acbea93b2ad..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/script +++ /dev/null @@ -1,33 +0,0 @@ -print_requests() { - jq 'select(.path | contains("/jobs/")) | select(.method != "GET")' < out.requests.txt - rm out.requests.txt -} - -envsubst < databricks.yml.tmpl > databricks.yml - -trace $CLI bundle plan -o json > out.plan_create.$DATABRICKS_BUNDLE_ENGINE.json - -cleanup() { - trace errcode $CLI bundle destroy --auto-approve - trace print_requests > out.requests_destroy.$DATABRICKS_BUNDLE_ENGINE.json -} -trap cleanup EXIT - -trace $CLI bundle deploy -print_requests > out.requests_create.$DATABRICKS_BUNDLE_ENGINE.json -trace $CLI bundle plan - -job_id=$($CLI bundle summary --output json | jq -r '.resources.jobs.job_with_permissions.id') - -$CLI permissions get jobs "$job_id" > out.permissions_create.$DATABRICKS_BUNDLE_ENGINE.json -rm -f out.requests.txt - -title "Delete one permission and deploy again\n" -grep -v DELETE databricks.yml > tmp.yml && mv tmp.yml databricks.yml -trace $CLI bundle plan -trace $CLI bundle plan -o json > out.plan_update.$DATABRICKS_BUNDLE_ENGINE.json -trace $CLI bundle deploy -print_requests > out.requests_update.$DATABRICKS_BUNDLE_ENGINE.json - -$CLI permissions get jobs "$job_id" > out.permissions_update.$DATABRICKS_BUNDLE_ENGINE.json -rm out.requests.txt diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/local/test.toml deleted file mode 100644 index 21058a311c6..00000000000 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false -RecordRequests = true -IsServicePrincipal = true -Ignore = ['.databricks'] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_create.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_create.txt new file mode 100644 index 00000000000..6c4f4bc19aa --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_create.txt @@ -0,0 +1,18 @@ +json.access_control_list[0].all_permissions[0].inherited = false; +json.access_control_list[0].all_permissions[0].permission_level = "CAN_MANAGE"; +json.access_control_list[0].group_name = "test-dabs-group-1"; +json.access_control_list[1].all_permissions[0].inherited = false; +json.access_control_list[1].all_permissions[0].permission_level = "CAN_MANAGE_RUN"; +json.access_control_list[1].user_name = "test-dabs-1@databricks.com"; +json.access_control_list[2].all_permissions[0].inherited = false; +json.access_control_list[2].all_permissions[0].permission_level = "CAN_VIEW"; +json.access_control_list[2].user_name = "test-dabs-2@databricks.com"; +json.access_control_list[3].all_permissions[0].inherited = false; +json.access_control_list[3].all_permissions[0].permission_level = "IS_OWNER"; +json.access_control_list[3].service_principal_name = "[USERNAME]"; +json.access_control_list[4].all_permissions[0].inherited = true; +json.access_control_list[4].all_permissions[0].inherited_from_object[0] = "/jobs/"; +json.access_control_list[4].all_permissions[0].permission_level = "CAN_MANAGE"; +json.access_control_list[4].group_name = "admins"; +json.object_id = "/jobs/[JOB_WITH_PERMISSIONS_ID]"; +json.object_type = "job"; diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_update.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_update.txt new file mode 100644 index 00000000000..7440ea401a4 --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.permissions_update.txt @@ -0,0 +1,15 @@ +json.access_control_list[0].all_permissions[0].inherited = false; +json.access_control_list[0].all_permissions[0].permission_level = "CAN_MANAGE"; +json.access_control_list[0].group_name = "test-dabs-group-1"; +json.access_control_list[1].all_permissions[0].inherited = false; +json.access_control_list[1].all_permissions[0].permission_level = "CAN_VIEW"; +json.access_control_list[1].user_name = "test-dabs-2@databricks.com"; +json.access_control_list[2].all_permissions[0].inherited = false; +json.access_control_list[2].all_permissions[0].permission_level = "IS_OWNER"; +json.access_control_list[2].service_principal_name = "[USERNAME]"; +json.access_control_list[3].all_permissions[0].inherited = true; +json.access_control_list[3].all_permissions[0].inherited_from_object[0] = "/jobs/"; +json.access_control_list[3].all_permissions[0].permission_level = "CAN_MANAGE"; +json.access_control_list[3].group_name = "admins"; +json.object_id = "/jobs/[JOB_WITH_PERMISSIONS_ID]"; +json.object_type = "job"; diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.plan_create.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/out.plan_create.direct.json similarity index 100% rename from acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.plan_create.direct.json rename to acceptance/bundle/resources/permissions/jobs/delete_one/out.plan_create.direct.json diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.plan_create.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/out.plan_create.terraform.json similarity index 100% rename from acceptance/bundle/resources/permissions/jobs/delete_one/cloud/out.plan_create.terraform.json rename to acceptance/bundle/resources/permissions/jobs/delete_one/out.plan_create.terraform.json diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_create.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_create.txt new file mode 100644 index 00000000000..1473810b3c9 --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_create.txt @@ -0,0 +1,22 @@ +json[0].method = "POST"; +json[0].path = "/api/2.2/jobs/create"; +json[0].body.deployment.kind = "BUNDLE"; +json[0].body.deployment.metadata_file_path = "/Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/state/metadata.json"; +json[0].body.edit_mode = "UI_LOCKED"; +json[0].body.format = "MULTI_TASK"; +json[0].body.max_concurrent_runs = 1; +json[0].body.name = "job with permissions"; +json[0].body.queue.enabled = true; +json[0].body.tasks[0].notebook_task.notebook_path = "/Workspace/Users/tester@databricks.com/notebook"; +json[0].body.tasks[0].notebook_task.source = "WORKSPACE"; +json[0].body.tasks[0].task_key = "main"; +json[1].method = "PUT"; +json[1].path = "/api/2.0/permissions/jobs/[JOB_WITH_PERMISSIONS_ID]"; +json[1].body.access_control_list[0].group_name = "test-dabs-group-1"; +json[1].body.access_control_list[0].permission_level = "CAN_MANAGE"; +json[1].body.access_control_list[1].permission_level = "CAN_MANAGE_RUN"; +json[1].body.access_control_list[1].user_name = "test-dabs-1@databricks.com"; +json[1].body.access_control_list[2].permission_level = "CAN_VIEW"; +json[1].body.access_control_list[2].user_name = "test-dabs-2@databricks.com"; +json[1].body.access_control_list[3].permission_level = "IS_OWNER"; +json[1].body.access_control_list[3].service_principal_name = "[USERNAME]"; diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.direct.json b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.direct.json similarity index 64% rename from acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.direct.json rename to acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.direct.json index 6564b6f8bde..73495bdc38f 100644 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.direct.json +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.direct.json @@ -2,6 +2,6 @@ "method": "POST", "path": "/api/2.2/jobs/delete", "body": { - "job_id": [NUMID] + "job_id": [JOB_WITH_PERMISSIONS_ID] } } diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.terraform.json b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.terraform.json similarity index 69% rename from acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.terraform.json rename to acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.terraform.json index cd29de0d552..f66a05564c6 100644 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/local/out.requests_destroy.terraform.json +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_destroy.terraform.json @@ -1,6 +1,6 @@ { "method": "PUT", - "path": "/api/2.0/permissions/jobs/[NUMID]", + "path": "/api/2.0/permissions/jobs/[JOB_WITH_PERMISSIONS_ID]", "body": { "access_control_list": [ { @@ -14,6 +14,6 @@ "method": "POST", "path": "/api/2.2/jobs/delete", "body": { - "job_id": [NUMID] + "job_id": [JOB_WITH_PERMISSIONS_ID] } } diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_update.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_update.txt new file mode 100644 index 00000000000..c97c396a7d5 --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.requests_update.txt @@ -0,0 +1,8 @@ +json.method = "PUT"; +json.path = "/api/2.0/permissions/jobs/[JOB_WITH_PERMISSIONS_ID]"; +json.body.access_control_list[0].group_name = "test-dabs-group-1"; +json.body.access_control_list[0].permission_level = "CAN_MANAGE"; +json.body.access_control_list[1].permission_level = "CAN_VIEW"; +json.body.access_control_list[1].user_name = "test-dabs-2@databricks.com"; +json.body.access_control_list[2].permission_level = "IS_OWNER"; +json.body.access_control_list[2].service_principal_name = "[USERNAME]"; diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/out.test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/out.test.toml new file mode 100644 index 00000000000..e849ec85ace --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/output.txt b/acceptance/bundle/resources/permissions/jobs/delete_one/output.txt similarity index 97% rename from acceptance/bundle/resources/permissions/jobs/delete_one/cloud/output.txt rename to acceptance/bundle/resources/permissions/jobs/delete_one/output.txt index b0cd23c833a..007d1a8c942 100644 --- a/acceptance/bundle/resources/permissions/jobs/delete_one/cloud/output.txt +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/output.txt @@ -26,5 +26,3 @@ All files and directories at the following location will be deleted: /Workspace/ Deleting files... Destroy complete! - ->>> print_requests diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/script b/acceptance/bundle/resources/permissions/jobs/delete_one/script new file mode 100644 index 00000000000..b51fed3b894 --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/script @@ -0,0 +1,35 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +if [ -n "$CLOUD_ENV" ]; then + # Cloud workspace needs these principals to exist; mock testserver doesn't care. + $CLI users create --user-name test-dabs-1@databricks.com &> /dev/null || true + $CLI users create --user-name test-dabs-2@databricks.com &> /dev/null || true + $CLI groups create --display-name test-dabs-group-1 &> /dev/null || true +fi +rm -f out.requests.txt + +trace $CLI bundle plan -o json > out.plan_create.$DATABRICKS_BUNDLE_ENGINE.json + +cleanup() { + trace errcode $CLI bundle destroy --auto-approve + print_requests.py //jobs/ > out.requests_destroy.$DATABRICKS_BUNDLE_ENGINE.json +} +trap cleanup EXIT + +trace $CLI bundle deploy +print_requests.py //jobs/ | gron.py --sort-arrays access_control_list > out.requests_create.txt + +trace $CLI bundle plan + +job_id="$(read_id.py job_with_permissions)" +# display_name is omitted from cloud responses for freshly-created principals; drop it for parity with local mock. +$CLI permissions get jobs "$job_id" | gron.py --sort-arrays access_control_list | grep -v display_name > out.permissions_create.txt +rm -f out.requests.txt + +title "Delete one permission and deploy again\n" +grep -v DELETE databricks.yml > tmp.yml && mv tmp.yml databricks.yml +trace $CLI bundle deploy +print_requests.py //jobs/ | gron.py --sort-arrays access_control_list > out.requests_update.txt + +$CLI permissions get jobs "$job_id" | gron.py --sort-arrays access_control_list | grep -v display_name > out.permissions_update.txt +rm -f out.requests.txt diff --git a/acceptance/bundle/resources/permissions/jobs/delete_one/test.toml b/acceptance/bundle/resources/permissions/jobs/delete_one/test.toml new file mode 100644 index 00000000000..f993a98ecc7 --- /dev/null +++ b/acceptance/bundle/resources/permissions/jobs/delete_one/test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +IsServicePrincipal = true +RequiresUnityCatalog = true diff --git a/acceptance/bundle/resources/permissions/jobs/deleted_remotely/out.test.toml b/acceptance/bundle/resources/permissions/jobs/deleted_remotely/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/deleted_remotely/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/deleted_remotely/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/deleted_remotely/output.txt b/acceptance/bundle/resources/permissions/jobs/deleted_remotely/output.txt index c5a72a734e8..2493f03161d 100644 --- a/acceptance/bundle/resources/permissions/jobs/deleted_remotely/output.txt +++ b/acceptance/bundle/resources/permissions/jobs/deleted_remotely/output.txt @@ -15,47 +15,47 @@ Deployment complete! { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_MANAGE" + "inherited": false, + "permission_level": "CAN_MANAGE" } ], - "group_name":"data-team" + "group_name": "data-team" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } === Delete permissions remotely @@ -65,28 +65,28 @@ Deployment complete! { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } >>> print_requests @@ -119,45 +119,45 @@ Deployment complete! { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_MANAGE" + "inherited": false, + "permission_level": "CAN_MANAGE" } ], - "group_name":"data-team" + "group_name": "data-team" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } diff --git a/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/out.test.toml b/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/out.test.toml index 626b7427cfb..4037e35bf87 100644 --- a/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/out.test.toml @@ -1,10 +1,6 @@ Local = false Cloud = true RunsOnDbr = false - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/out.test.toml b/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/out.test.toml index 626b7427cfb..4037e35bf87 100644 --- a/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/out.test.toml @@ -1,10 +1,6 @@ Local = false Cloud = true RunsOnDbr = false - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/empty_list/out.test.toml b/acceptance/bundle/resources/permissions/jobs/empty_list/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/empty_list/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/empty_list/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/other_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/jobs/other_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/other_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/other_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/other_can_manage_run/out.test.toml b/acceptance/bundle/resources/permissions/jobs/other_can_manage_run/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/other_can_manage_run/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/other_can_manage_run/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/other_is_owner/out.test.toml b/acceptance/bundle/resources/permissions/jobs/other_is_owner/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/other_is_owner/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/other_is_owner/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/reorder_locally/out.test.toml b/acceptance/bundle/resources/permissions/jobs/reorder_locally/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/reorder_locally/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/reorder_locally/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/reorder_remotely/out.test.toml b/acceptance/bundle/resources/permissions/jobs/reorder_remotely/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/reorder_remotely/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/reorder_remotely/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/update/out.test.toml b/acceptance/bundle/resources/permissions/jobs/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/update/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/jobs/update/output.txt b/acceptance/bundle/resources/permissions/jobs/update/output.txt index 43c4ec92b2a..6fcfd6d03f3 100644 --- a/acceptance/bundle/resources/permissions/jobs/update/output.txt +++ b/acceptance/bundle/resources/permissions/jobs/update/output.txt @@ -26,47 +26,47 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_MANAGE" + "inherited": false, + "permission_level": "CAN_MANAGE" } ], - "group_name":"data-team" + "group_name": "data-team" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" }, { "all_permissions": [ { - "inherited":true, + "inherited": true, "inherited_from_object": [ "/jobs/" ], - "permission_level":"CAN_MANAGE" + "permission_level": "CAN_MANAGE" } ], - "group_name":"admins" + "group_name": "admins" } ], - "object_id":"/jobs/[JOB_WITH_PERMISSIONS_ID]", - "object_type":"job" + "object_id": "/jobs/[JOB_WITH_PERMISSIONS_ID]", + "object_type": "job" } === Update one permission and deploy again diff --git a/acceptance/bundle/resources/permissions/jobs/update/test.toml b/acceptance/bundle/resources/permissions/jobs/update/test.toml index 159efe02696..61ef8ced4ea 100644 --- a/acceptance/bundle/resources/permissions/jobs/update/test.toml +++ b/acceptance/bundle/resources/permissions/jobs/update/test.toml @@ -1 +1,3 @@ RecordRequests = true +# Many CLI invocations; extra headroom for heavy parallel runs. +Timeout = '2m' diff --git a/acceptance/bundle/resources/permissions/jobs/viewers/out.test.toml b/acceptance/bundle/resources/permissions/jobs/viewers/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/jobs/viewers/out.test.toml +++ b/acceptance/bundle/resources/permissions/jobs/viewers/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.direct.json b/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.direct.json index 1c37bc73549..52723d328fb 100644 --- a/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.direct.json +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.direct.json @@ -14,7 +14,7 @@ "depends_on": [ { "node": "resources.models.foo", - "label": "${resources.models.foo.id}" + "label": "${resources.models.foo.model_id}" } ], "action": "create", @@ -41,7 +41,7 @@ ] }, "vars": { - "object_id": "/registered-models/${resources.models.foo.id}" + "object_id": "/registered-models/${resources.models.foo.model_id}" } } } diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.json b/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.terraform.json similarity index 54% rename from acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.json rename to acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.terraform.json index 0266fceefcd..c564bfda0ac 100644 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.json +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.plan.terraform.json @@ -1,10 +1,10 @@ { "cli_version": "[DEV_VERSION]", "plan": { - "resources.secret_scopes.my_scope": { + "resources.models.foo": { "action": "create" }, - "resources.secret_scopes.my_scope.permissions": { + "resources.models.foo.permissions": { "action": "create" } } diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.direct.json b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.direct.json index d9f74a8ed6b..568657ccaca 100644 --- a/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.direct.json +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.direct.json @@ -1,6 +1,6 @@ { "method": "PUT", - "path": "/api/2.0/permissions/registered-models/test-model", + "path": "/api/2.0/permissions/registered-models/[FOO_MODEL_ID]", "body": { "access_control_list": [ { diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.terraform.json b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.terraform.json new file mode 100644 index 00000000000..06c635ad4ad --- /dev/null +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.deploy.terraform.json @@ -0,0 +1,24 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/registered-models/[FOO_MODEL_ID]", + "body": { + "access_control_list": [ + { + "permission_level": "CAN_READ", + "user_name": "viewer@example.com" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[UUID]" + }, + { + "group_name": "data-team", + "permission_level": "CAN_MANAGE" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.destroy.terraform.json b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.destroy.terraform.json new file mode 100644 index 00000000000..62901b29fba --- /dev/null +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.requests.destroy.terraform.json @@ -0,0 +1,12 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/registered-models/[FOO_MODEL_ID]", + "body": { + "access_control_list": [ + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/models/current_can_manage/out.test.toml index 54146af5645..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/models/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/models/current_can_manage/script b/acceptance/bundle/resources/permissions/models/current_can_manage/script index 3c691fdff67..19146a993f3 100644 --- a/acceptance/bundle/resources/permissions/models/current_can_manage/script +++ b/acceptance/bundle/resources/permissions/models/current_can_manage/script @@ -10,6 +10,12 @@ print_requests() { rm out.requests.txt trace errcode $CLI bundle deploy &> out.deploy.txt + +# Register the model's numeric ID for output replacement. +# The permissions API uses the numeric ID, not the model name. +MODEL_ID=$($CLI model-registry get-model test-model | jq -r '.registered_model_databricks.id') +add_repl.py "$MODEL_ID" "FOO_MODEL_ID" + print_requests > out.requests.deploy.$DATABRICKS_BUNDLE_ENGINE.json trace $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/resources/permissions/models/test.toml b/acceptance/bundle/resources/permissions/models/test.toml index 7a5f405eb38..d545b1b896d 100644 --- a/acceptance/bundle/resources/permissions/models/test.toml +++ b/acceptance/bundle/resources/permissions/models/test.toml @@ -1,2 +1,2 @@ Env.RESOURCE = "models" # for ../_script -EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] # terraform mapping issue +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/out.test.toml b/acceptance/bundle/resources/permissions/out.test.toml index 4cfe03e9f9d..be193812ec2 100644 --- a/acceptance/bundle/resources/permissions/out.test.toml +++ b/acceptance/bundle/resources/permissions/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false Phase = 1 - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/output.txt b/acceptance/bundle/resources/permissions/output.txt index ab4953f6b5e..85eea2e6e9c 100644 --- a/acceptance/bundle/resources/permissions/output.txt +++ b/acceptance/bundle/resources/permissions/output.txt @@ -157,9 +157,9 @@ DIFF jobs/current_is_owner/out.requests.destroy.direct.json + "path": "/api/2.0/permissions/jobs/[NUMID]" + } +] -DIFF jobs/delete_one/cloud/out.requests_destroy.direct.json ---- jobs/delete_one/cloud/out.requests_destroy.direct.json -+++ jobs/delete_one/cloud/out.requests_destroy.terraform.json +DIFF jobs/delete_one/out.requests_destroy.direct.json +--- jobs/delete_one/out.requests_destroy.direct.json ++++ jobs/delete_one/out.requests_destroy.terraform.json @@ -1,4 +1,16 @@ [ + { @@ -172,35 +172,11 @@ DIFF jobs/delete_one/cloud/out.requests_destroy.direct.json + ] + }, + "method": "PUT", -+ "path": "/api/2.0/permissions/jobs/[NUMID]" -+ }, - { - "body": { - "job_id": "[NUMID]" -MATCH jobs/delete_one/local/out.permissions_create.direct.json -MATCH jobs/delete_one/local/out.permissions_update.direct.json -MATCH jobs/delete_one/local/out.requests_create.direct.json -DIFF jobs/delete_one/local/out.requests_destroy.direct.json ---- jobs/delete_one/local/out.requests_destroy.direct.json -+++ jobs/delete_one/local/out.requests_destroy.terraform.json -@@ -1,4 +1,16 @@ - [ -+ { -+ "body": { -+ "access_control_list": [ -+ { -+ "permission_level": "IS_OWNER", -+ "user_name": "[USERNAME]" -+ } -+ ] -+ }, -+ "method": "PUT", -+ "path": "/api/2.0/permissions/jobs/[NUMID]" ++ "path": "/api/2.0/permissions/jobs/[JOB_WITH_PERMISSIONS_ID]" + }, { "body": { - "job_id": "[NUMID]" -MATCH jobs/delete_one/local/out.requests_update.direct.json + "job_id": "[JOB_WITH_PERMISSIONS_ID]" EXACT jobs/empty_list/out.requests.deploy.direct.json EXACT jobs/empty_list/out.requests.destroy.direct.json MATCH jobs/other_can_manage/out.requests.deploy.direct.json @@ -319,8 +295,25 @@ DIFF jobs/viewers/out.requests.destroy.direct.json + "path": "/api/2.0/permissions/jobs/[NUMID]" + } +] -ERROR models/current_can_manage/out.requests.deploy.direct.json: Missing terraform file models/current_can_manage/out.requests.deploy.terraform.json -ERROR models/current_can_manage/out.requests.destroy.direct.json: Missing terraform file models/current_can_manage/out.requests.destroy.terraform.json +MATCH models/current_can_manage/out.requests.deploy.direct.json +DIFF models/current_can_manage/out.requests.destroy.direct.json +--- models/current_can_manage/out.requests.destroy.direct.json ++++ models/current_can_manage/out.requests.destroy.terraform.json +@@ -1 +1,14 @@ +-[]+[ ++ { ++ "body": { ++ "access_control_list": [ ++ { ++ "permission_level": "CAN_MANAGE", ++ "user_name": "[USERNAME]" ++ } ++ ] ++ }, ++ "method": "PUT", ++ "path": "/api/2.0/permissions/registered-models/[FOO_MODEL_ID]" ++ } ++] MATCH pipelines/current_can_manage/out.requests.deploy.direct.json EXACT pipelines/current_can_manage/out.requests.destroy.direct.json EXACT pipelines/current_is_owner/out.requests.deploy.direct.json @@ -394,3 +387,5 @@ DIFF target_permissions/out.requests_delete.direct.json { "body": { "job_id": "[NUMID]" +DIRECT_ONLY vector_search_endpoints/current_can_manage/out.requests.deploy.direct.json +DIRECT_ONLY vector_search_endpoints/current_can_manage/out.requests.destroy.direct.json diff --git a/acceptance/bundle/resources/permissions/pipelines/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/current_is_owner/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/current_is_owner/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/current_is_owner/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/current_is_owner/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/empty_list/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/empty_list/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/empty_list/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/empty_list/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/other_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/other_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/other_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/other_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/other_is_owner/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/other_is_owner/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/other_is_owner/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/other_is_owner/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/update/out.test.toml b/acceptance/bundle/resources/permissions/pipelines/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/pipelines/update/out.test.toml +++ b/acceptance/bundle/resources/permissions/pipelines/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/pipelines/update/output.txt b/acceptance/bundle/resources/permissions/pipelines/update/output.txt index 84b53daf5c8..869c92cd5cb 100644 --- a/acceptance/bundle/resources/permissions/pipelines/update/output.txt +++ b/acceptance/bundle/resources/permissions/pipelines/update/output.txt @@ -18,35 +18,35 @@ Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_VIEW" + "inherited": false, + "permission_level": "CAN_VIEW" } ], - "display_name":"viewer@example.com", - "user_name":"viewer@example.com" + "display_name": "viewer@example.com", + "user_name": "viewer@example.com" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"CAN_MANAGE" + "inherited": false, + "permission_level": "CAN_MANAGE" } ], - "group_name":"data-team" + "group_name": "data-team" }, { "all_permissions": [ { - "inherited":false, - "permission_level":"IS_OWNER" + "inherited": false, + "permission_level": "IS_OWNER" } ], - "display_name":"[USERNAME]", - "user_name":"[USERNAME]" + "display_name": "[USERNAME]", + "user_name": "[USERNAME]" } ], - "object_id":"/pipelines/[FOO_ID]", - "object_type":"pipelines" + "object_id": "/pipelines/[FOO_ID]", + "object_type": "pipelines" } === Update one permission and deploy again diff --git a/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/output.txt b/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/output.txt index 10e8ce61d53..876d077e601 100644 --- a/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/output.txt +++ b/acceptance/bundle/resources/permissions/postgres_projects/current_can_manage/output.txt @@ -29,6 +29,10 @@ Deployment complete! The following resources will be deleted: delete resources.postgres_projects.foo +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.foo + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default Deleting files... diff --git a/acceptance/bundle/resources/permissions/sql_warehouses/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/sql_warehouses/current_can_manage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/sql_warehouses/current_can_manage/out.test.toml +++ b/acceptance/bundle/resources/permissions/sql_warehouses/current_can_manage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/target_permissions/out.test.toml b/acceptance/bundle/resources/permissions/target_permissions/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/permissions/target_permissions/out.test.toml +++ b/acceptance/bundle/resources/permissions/target_permissions/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/databricks.yml b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/databricks.yml new file mode 100644 index 00000000000..a4419ad44b7 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/databricks.yml @@ -0,0 +1,17 @@ +bundle: + name: test-bundle + +resources: + vector_search_endpoints: + foo: + name: vs-permissions-endpoint + endpoint_type: STANDARD + permissions: + - level: CAN_USE + user_name: viewer@example.com + - level: CAN_MANAGE + group_name: data-team + - level: CAN_MANAGE + service_principal_name: f37d18cd-98a8-4db5-8112-12dd0a6bfe38 + - level: CAN_MANAGE + user_name: tester@databricks.com diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.plan.direct.json b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.plan.direct.json new file mode 100644 index 00000000000..2e15fc05560 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.plan.direct.json @@ -0,0 +1,50 @@ +{ + "plan_version": 2, + "cli_version": "[DEV_VERSION]", + "plan": { + "resources.vector_search_endpoints.foo": { + "action": "create", + "new_state": { + "value": { + "endpoint_type": "STANDARD", + "name": "vs-permissions-endpoint" + } + } + }, + "resources.vector_search_endpoints.foo.permissions": { + "depends_on": [ + { + "node": "resources.vector_search_endpoints.foo", + "label": "${resources.vector_search_endpoints.foo.endpoint_uuid}" + } + ], + "action": "create", + "new_state": { + "value": { + "object_id": "", + "__embed__": [ + { + "level": "CAN_USE", + "user_name": "viewer@example.com" + }, + { + "level": "CAN_MANAGE", + "group_name": "data-team" + }, + { + "level": "CAN_MANAGE", + "service_principal_name": "[UUID]" + }, + { + "level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + }, + "vars": { + "object_id": "/vector-search-endpoints/${resources.vector_search_endpoints.foo.endpoint_uuid}" + } + } + } + } +} diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.deploy.direct.json b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.deploy.direct.json new file mode 100644 index 00000000000..9118a4da780 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.deploy.direct.json @@ -0,0 +1,24 @@ +{ + "method": "PUT", + "path": "/api/2.0/permissions/vector-search-endpoints/[UUID]", + "body": { + "access_control_list": [ + { + "permission_level": "CAN_USE", + "user_name": "viewer@example.com" + }, + { + "group_name": "data-team", + "permission_level": "CAN_MANAGE" + }, + { + "permission_level": "CAN_MANAGE", + "service_principal_name": "[UUID]" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.destroy.direct.json b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.destroy.direct.json new file mode 100644 index 00000000000..84c87416aa2 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.requests.destroy.direct.json @@ -0,0 +1,4 @@ +{ + "method": "DELETE", + "path": "/api/2.0/vector-search/endpoints/vs-permissions-endpoint" +} diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.test.toml b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/output.txt b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/output.txt new file mode 100644 index 00000000000..4e848ac23e6 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/output.txt @@ -0,0 +1,35 @@ + +>>> [CLI] bundle validate -o json +[ + { + "level": "CAN_USE", + "user_name": "viewer@example.com" + }, + { + "group_name": "data-team", + "level": "CAN_MANAGE" + }, + { + "level": "CAN_MANAGE", + "service_principal_name": "[UUID]" + }, + { + "level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } +] + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.foo + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/script b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/script new file mode 100644 index 00000000000..7d1e9fc8e26 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/current_can_manage/script @@ -0,0 +1 @@ +source $TESTDIR/../../_script diff --git a/acceptance/bundle/resources/permissions/vector_search_endpoints/test.toml b/acceptance/bundle/resources/permissions/vector_search_endpoints/test.toml new file mode 100644 index 00000000000..1217a2ec963 --- /dev/null +++ b/acceptance/bundle/resources/permissions/vector_search_endpoints/test.toml @@ -0,0 +1,2 @@ +Env.RESOURCE = "vector_search_endpoints" # for ../_script +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/pipelines/allow-duplicate-names/out.test.toml b/acceptance/bundle/resources/pipelines/allow-duplicate-names/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/pipelines/allow-duplicate-names/out.test.toml +++ b/acceptance/bundle/resources/pipelines/allow-duplicate-names/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/auto-approve/out.test.toml b/acceptance/bundle/resources/pipelines/auto-approve/out.test.toml index a9766d99c9b..5ad0addb75e 100644 --- a/acceptance/bundle/resources/pipelines/auto-approve/out.test.toml +++ b/acceptance/bundle/resources/pipelines/auto-approve/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/lakeflow-pipeline/out.test.toml b/acceptance/bundle/resources/pipelines/lakeflow-pipeline/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/pipelines/lakeflow-pipeline/out.test.toml +++ b/acceptance/bundle/resources/pipelines/lakeflow-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/num-workers-zero/out.test.toml b/acceptance/bundle/resources/pipelines/num-workers-zero/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/pipelines/num-workers-zero/out.test.toml +++ b/acceptance/bundle/resources/pipelines/num-workers-zero/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/out.test.toml b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/out.test.toml +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt index 5951bbfc01b..77bfdc09d32 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-ingestion-definition/output.txt @@ -107,22 +107,22 @@ Deployment complete! === Fetch pipeline ID and verify remote state >>> [CLI] pipelines get [MY_ID_2] { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS], - "name":"test-pipeline-[UNIQUE_NAME]", - "pipeline_id":"[MY_ID_2]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS], + "name": "test-pipeline-[UNIQUE_NAME]", + "pipeline_id": "[MY_ID_2]", + "run_as_user_name": "[USERNAME]", "spec": { - "channel":"CURRENT", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" }, - "edition":"ADVANCED", - "id":"[MY_ID_2]", + "edition": "ADVANCED", + "id": "[MY_ID_2]", "ingestion_definition": { - "connection_name":"my_new_connection", + "connection_name": "my_new_connection", "objects": [ {} ] @@ -130,14 +130,14 @@ Deployment complete! "libraries": [ { "file": { - "path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/foo.py" + "path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/foo.py" } } ], - "name":"test-pipeline-[UNIQUE_NAME]", - "storage":"dbfs:/pipelines/[MY_ID_2]" + "name": "test-pipeline-[UNIQUE_NAME]", + "storage": "dbfs:/pipelines/[MY_ID_2]" }, - "state":"IDLE" + "state": "IDLE" } === Verify that original pipeline is gone diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/out.test.toml b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/out.test.toml +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt index 58e58039ebe..74943571027 100644 --- a/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate-keys/change-storage/output.txt @@ -97,31 +97,31 @@ Deployment complete! === Fetch pipeline ID and verify remote state >>> [CLI] pipelines get [MY_ID_2] { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS], - "name":"test-pipeline-[UNIQUE_NAME]", - "pipeline_id":"[MY_ID_2]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS], + "name": "test-pipeline-[UNIQUE_NAME]", + "pipeline_id": "[MY_ID_2]", + "run_as_user_name": "[USERNAME]", "spec": { - "channel":"CURRENT", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" }, - "edition":"ADVANCED", - "id":"[MY_ID_2]", + "edition": "ADVANCED", + "id": "[MY_ID_2]", "libraries": [ { "file": { - "path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/foo.py" + "path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/foo.py" } } ], - "name":"test-pipeline-[UNIQUE_NAME]", - "storage":"dbfs:/pipelines/newcustom" + "name": "test-pipeline-[UNIQUE_NAME]", + "storage": "dbfs:/pipelines/newcustom" }, - "state":"IDLE" + "state": "IDLE" } === Verify that original pipeline is gone diff --git a/acceptance/bundle/resources/pipelines/recreate/out.test.toml b/acceptance/bundle/resources/pipelines/recreate/out.test.toml index 8d6b9baeb5b..6e3397efe53 100644 --- a/acceptance/bundle/resources/pipelines/recreate/out.test.toml +++ b/acceptance/bundle/resources/pipelines/recreate/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresUnityCatalog = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/update/out.test.toml b/acceptance/bundle/resources/pipelines/update/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/pipelines/update/out.test.toml +++ b/acceptance/bundle/resources/pipelines/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/pipelines/update/output.txt b/acceptance/bundle/resources/pipelines/update/output.txt index e34efaf84e0..597df75da99 100644 --- a/acceptance/bundle/resources/pipelines/update/output.txt +++ b/acceptance/bundle/resources/pipelines/update/output.txt @@ -39,31 +39,31 @@ Deployment complete! === Fetch pipeline ID and verify remote state >>> [CLI] pipelines get [MY_ID] { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS], - "name":"test-pipeline-[UNIQUE_NAME]", - "pipeline_id":"[MY_ID]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS], + "name": "test-pipeline-[UNIQUE_NAME]", + "pipeline_id": "[MY_ID]", + "run_as_user_name": "[USERNAME]", "spec": { - "channel":"CURRENT", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/state/metadata.json" }, - "edition":"ADVANCED", - "id":"[MY_ID]", + "edition": "ADVANCED", + "id": "[MY_ID]", "libraries": [ { "file": { - "path":"/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/bar.py" + "path": "/Workspace/Users/[USERNAME]/.bundle/acc-[UNIQUE_NAME]/default/files/bar.py" } } ], - "name":"test-pipeline-[UNIQUE_NAME]", - "storage":"dbfs:/pipelines/[MY_ID]" + "name": "test-pipeline-[UNIQUE_NAME]", + "storage": "dbfs:/pipelines/[MY_ID]" }, - "state":"IDLE" + "state": "IDLE" } === Destroy the pipeline and verify that it's removed from the state and from remote diff --git a/acceptance/bundle/resources/postgres_branches/basic/out.test.toml b/acceptance/bundle/resources/postgres_branches/basic/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_branches/basic/out.test.toml +++ b/acceptance/bundle/resources/postgres_branches/basic/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_branches/basic/output.txt b/acceptance/bundle/resources/postgres_branches/basic/output.txt index ae9053d377c..9da42b6da2a 100644 --- a/acceptance/bundle/resources/postgres_branches/basic/output.txt +++ b/acceptance/bundle/resources/postgres_branches/basic/output.txt @@ -69,6 +69,14 @@ The following resources will be deleted: delete resources.postgres_branches.main delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.main + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_branches/recreate/out.test.toml b/acceptance/bundle/resources/postgres_branches/recreate/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_branches/recreate/out.test.toml +++ b/acceptance/bundle/resources/postgres_branches/recreate/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_branches/recreate/output.txt b/acceptance/bundle/resources/postgres_branches/recreate/output.txt index fa929532591..bcc1528946b 100644 --- a/acceptance/bundle/resources/postgres_branches/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_branches/recreate/output.txt @@ -64,6 +64,10 @@ resources: >>> [CLI] bundle deploy --auto-approve Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-recreate-[UNIQUE_NAME]/default/files... + +This action will result in the deletion or recreation of the following Lakebase branches. +All data stored in them will be permanently lost: + recreate resources.postgres_branches.main Deploying resources... Updating deployment state... Deployment complete! @@ -75,6 +79,14 @@ The following resources will be deleted: delete resources.postgres_branches.main delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.main + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-branch-recreate-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/out.test.toml b/acceptance/bundle/resources/postgres_branches/update_protected/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/out.test.toml +++ b/acceptance/bundle/resources/postgres_branches/update_protected/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/output.txt b/acceptance/bundle/resources/postgres_branches/update_protected/output.txt index b32d48a9093..7380da0adce 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/output.txt +++ b/acceptance/bundle/resources/postgres_branches/update_protected/output.txt @@ -154,6 +154,14 @@ The following resources will be deleted: delete resources.postgres_branches.dev_branch delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.dev_branch + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-postgres-branch-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_branches/without_branch_id/out.test.toml b/acceptance/bundle/resources/postgres_branches/without_branch_id/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_branches/without_branch_id/out.test.toml +++ b/acceptance/bundle/resources/postgres_branches/without_branch_id/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_endpoints/basic/out.test.toml b/acceptance/bundle/resources/postgres_endpoints/basic/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_endpoints/basic/out.test.toml +++ b/acceptance/bundle/resources/postgres_endpoints/basic/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_endpoints/basic/output.txt b/acceptance/bundle/resources/postgres_endpoints/basic/output.txt index 53de63481dd..898cb54c465 100644 --- a/acceptance/bundle/resources/postgres_endpoints/basic/output.txt +++ b/acceptance/bundle/resources/postgres_endpoints/basic/output.txt @@ -87,6 +87,14 @@ The following resources will be deleted: delete resources.postgres_endpoints.custom delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.main + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-endpoint-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_endpoints/recreate/out.test.toml b/acceptance/bundle/resources/postgres_endpoints/recreate/out.test.toml index f3d5b03e2ee..4fe23e297fe 100644 --- a/acceptance/bundle/resources/postgres_endpoints/recreate/out.test.toml +++ b/acceptance/bundle/resources/postgres_endpoints/recreate/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/postgres_endpoints/recreate/output.txt b/acceptance/bundle/resources/postgres_endpoints/recreate/output.txt index 9f9bb4faa8a..8b5725834e7 100644 --- a/acceptance/bundle/resources/postgres_endpoints/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_endpoints/recreate/output.txt @@ -147,6 +147,14 @@ The following resources will be deleted: delete resources.postgres_endpoints.my_endpoint delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.main + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-endpoint-recreate-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.test.toml b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.test.toml +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt index a53bd840536..9192423fa68 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt @@ -187,6 +187,14 @@ The following resources will be deleted: delete resources.postgres_endpoints.my_endpoint delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + +This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost: + delete resources.postgres_branches.main + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-postgres-endpoint-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.test.toml b/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.test.toml +++ b/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_projects/basic/out.test.toml b/acceptance/bundle/resources/postgres_projects/basic/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_projects/basic/out.test.toml +++ b/acceptance/bundle/resources/postgres_projects/basic/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_projects/basic/output.txt b/acceptance/bundle/resources/postgres_projects/basic/output.txt index 8c1288cb084..919f56ab17a 100644 --- a/acceptance/bundle/resources/postgres_projects/basic/output.txt +++ b/acceptance/bundle/resources/postgres_projects/basic/output.txt @@ -31,6 +31,7 @@ Deployment complete! "name": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, @@ -64,6 +65,10 @@ Resources: The following resources will be deleted: delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-single-project-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_projects/recreate/out.get_project.txt b/acceptance/bundle/resources/postgres_projects/recreate/out.get_project.txt index 749301889f3..463738d93e1 100644 --- a/acceptance/bundle/resources/postgres_projects/recreate/out.get_project.txt +++ b/acceptance/bundle/resources/postgres_projects/recreate/out.get_project.txt @@ -1,20 +1,21 @@ { - "create_time":"[TIMESTAMP]", - "name":"[MY_PROJECT_ID_2]", + "create_time": "[TIMESTAMP]", + "name": "[MY_PROJECT_ID_2]", "status": { - "branch_logical_size_limit_bytes":[NUMID], + "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID_2]/branches/production", "default_endpoint_settings": { - "autoscaling_limit_max_cu":4, - "autoscaling_limit_min_cu":0.5, - "suspend_timeout_duration":"300s" + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" }, - "display_name":"Test Recreate", - "enable_pg_native_login":true, - "history_retention_duration":"604800s", - "owner":"[USERNAME]", - "pg_version":16, - "synthetic_storage_size_bytes":0 + "display_name": "Test Recreate", + "enable_pg_native_login": true, + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "synthetic_storage_size_bytes": 0 }, - "uid":"[UUID]", - "update_time":"[TIMESTAMP]" + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" } diff --git a/acceptance/bundle/resources/postgres_projects/recreate/out.test.toml b/acceptance/bundle/resources/postgres_projects/recreate/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_projects/recreate/out.test.toml +++ b/acceptance/bundle/resources/postgres_projects/recreate/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_projects/recreate/output.txt b/acceptance/bundle/resources/postgres_projects/recreate/output.txt index 950ef936b9e..dad581aa51e 100644 --- a/acceptance/bundle/resources/postgres_projects/recreate/output.txt +++ b/acceptance/bundle/resources/postgres_projects/recreate/output.txt @@ -51,6 +51,10 @@ resources: >>> [CLI] bundle deploy --auto-approve Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-recreate-[UNIQUE_NAME]/default/files... + +This action will result in the deletion or recreation of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + recreate resources.postgres_projects.my_project Deploying resources... Updating deployment state... Deployment complete! @@ -61,6 +65,10 @@ Deployment complete! The following resources will be deleted: delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-recreate-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json index d47936775c5..927916f980c 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.no_change.direct.json @@ -5,6 +5,7 @@ "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json index 5af03b7d36f..7791dc5e524 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.restore.direct.json @@ -18,6 +18,7 @@ "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json index 830bdf74f6c..a7791f9b56b 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.plan.update.direct.json @@ -18,6 +18,7 @@ "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.restore.terraform.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.restore.terraform.json index 85a5668aa31..aabcba8c34e 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.restore.terraform.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.restore.terraform.json @@ -6,7 +6,7 @@ "method": "PATCH", "path": "/api/2.0/postgres/[MY_PROJECT_ID]", "q": { - "update_mask": "initial_endpoint_spec,spec" + "update_mask": "spec" }, "body": { "name": "[MY_PROJECT_ID]", diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.update.terraform.json b/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.update.terraform.json index daa3b4c7d93..d68d893ad28 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.update.terraform.json +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.requests.update.terraform.json @@ -6,7 +6,7 @@ "method": "PATCH", "path": "/api/2.0/postgres/[MY_PROJECT_ID]", "q": { - "update_mask": "initial_endpoint_spec,spec" + "update_mask": "spec" }, "body": { "name": "[MY_PROJECT_ID]", diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/out.test.toml b/acceptance/bundle/resources/postgres_projects/update_display_name/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/out.test.toml +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt b/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt index 61829ead707..6397e689f54 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt @@ -29,6 +29,7 @@ Deployment complete! "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, @@ -100,6 +101,7 @@ Deployment complete! "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, @@ -138,6 +140,7 @@ Deployment complete! "name": "[MY_PROJECT_ID]", "status": { "branch_logical_size_limit_bytes": [NUMID], + "default_branch": "[MY_PROJECT_ID]/branches/production", "default_endpoint_settings": { "autoscaling_limit_max_cu": 4, "autoscaling_limit_min_cu": 0.5, @@ -157,6 +160,10 @@ Deployment complete! The following resources will be deleted: delete resources.postgres_projects.my_project +This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost: + delete resources.postgres_projects.my_project + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-postgres-project-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/postgres_projects/without_project_id/out.test.toml b/acceptance/bundle/resources/postgres_projects/without_project_id/out.test.toml index 7035e08badb..110f841fa05 100644 --- a/acceptance/bundle/resources/postgres_projects/without_project_id/out.test.toml +++ b/acceptance/bundle/resources/postgres_projects/without_project_id/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/quality_monitors/change_assets_dir/out.test.toml b/acceptance/bundle/resources/quality_monitors/change_assets_dir/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/quality_monitors/change_assets_dir/out.test.toml +++ b/acceptance/bundle/resources/quality_monitors/change_assets_dir/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/quality_monitors/change_output_schema_name/out.test.toml b/acceptance/bundle/resources/quality_monitors/change_output_schema_name/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/quality_monitors/change_output_schema_name/out.test.toml +++ b/acceptance/bundle/resources/quality_monitors/change_output_schema_name/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/quality_monitors/change_table_name/out.get.direct.json b/acceptance/bundle/resources/quality_monitors/change_table_name/out.get.direct.json index feb478a878b..f6e2bce85de 100644 --- a/acceptance/bundle/resources/quality_monitors/change_table_name/out.get.direct.json +++ b/acceptance/bundle/resources/quality_monitors/change_table_name/out.get.direct.json @@ -1,11 +1,11 @@ { - "assets_dir":"/Workspace/Users/[USERNAME]/monitor_assets_[UNIQUE_NAME]", + "assets_dir": "/Workspace/Users/[USERNAME]/monitor_assets_[UNIQUE_NAME]", "dashboard_id": "(redacted)", - "drift_metrics_table_name":"main.qm_test_[UNIQUE_NAME].test_table_2_drift_metrics", - "monitor_version":0, - "output_schema_name":"main.qm_test_[UNIQUE_NAME]", - "profile_metrics_table_name":"main.qm_test_[UNIQUE_NAME].test_table_2_profile_metrics", + "drift_metrics_table_name": "main.qm_test_[UNIQUE_NAME].test_table_2_drift_metrics", + "monitor_version": 0, + "output_schema_name": "main.qm_test_[UNIQUE_NAME]", + "profile_metrics_table_name": "main.qm_test_[UNIQUE_NAME].test_table_2_profile_metrics", "snapshot": {}, - "status":"MONITOR_STATUS_ACTIVE", - "table_name":"main.qm_test_[UNIQUE_NAME].test_table_2" + "status": "MONITOR_STATUS_ACTIVE", + "table_name": "main.qm_test_[UNIQUE_NAME].test_table_2" } diff --git a/acceptance/bundle/resources/quality_monitors/change_table_name/out.test.toml b/acceptance/bundle/resources/quality_monitors/change_table_name/out.test.toml index f1d40380d02..fe4076cdf9b 100644 --- a/acceptance/bundle/resources/quality_monitors/change_table_name/out.test.toml +++ b/acceptance/bundle/resources/quality_monitors/change_table_name/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/quality_monitors/create/out.test.toml b/acceptance/bundle/resources/quality_monitors/create/out.test.toml index d61c11e25c7..e849ec85ace 100644 --- a/acceptance/bundle/resources/quality_monitors/create/out.test.toml +++ b/acceptance/bundle/resources/quality_monitors/create/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/registered_models/basic/out.test.toml b/acceptance/bundle/resources/registered_models/basic/out.test.toml index 8d6b9baeb5b..6e3397efe53 100644 --- a/acceptance/bundle/resources/registered_models/basic/out.test.toml +++ b/acceptance/bundle/resources/registered_models/basic/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresUnityCatalog = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/schemas/auto-approve/out.test.toml b/acceptance/bundle/resources/schemas/auto-approve/out.test.toml index 8d6b9baeb5b..6e3397efe53 100644 --- a/acceptance/bundle/resources/schemas/auto-approve/out.test.toml +++ b/acceptance/bundle/resources/schemas/auto-approve/out.test.toml @@ -2,6 +2,4 @@ Local = true Cloud = true RequiresUnityCatalog = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/schemas/recreate/out.test.toml b/acceptance/bundle/resources/schemas/recreate/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/schemas/recreate/out.test.toml +++ b/acceptance/bundle/resources/schemas/recreate/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/schemas/recreate/output.txt b/acceptance/bundle/resources/schemas/recreate/output.txt index 4241fe2b1db..7c173eb11f0 100644 --- a/acceptance/bundle/resources/schemas/recreate/output.txt +++ b/acceptance/bundle/resources/schemas/recreate/output.txt @@ -67,23 +67,23 @@ Error: Resource catalog.SchemaInfo not found: main.myschema >>> [CLI] schemas get newmain.myschema { - "browse_only":false, - "catalog_name":"newmain", - "catalog_type":"MANAGED_CATALOG", - "comment":"COMMENT1", - "created_at":[UNIX_TIME_MILLIS][0], - "created_by":"[USERNAME]", + "browse_only": false, + "catalog_name": "newmain", + "catalog_type": "MANAGED_CATALOG", + "comment": "COMMENT1", + "created_at": [UNIX_TIME_MILLIS][0], + "created_by": "[USERNAME]", "effective_predictive_optimization_flag": { - "inherited_from_name":"[METASTORE_NAME]", - "inherited_from_type":"METASTORE", - "value":"ENABLE" + "inherited_from_name": "[METASTORE_NAME]", + "inherited_from_type": "METASTORE", + "value": "ENABLE" }, - "enable_predictive_optimization":"INHERIT", - "full_name":"newmain.myschema", - "metastore_id":"[UUID]", - "name":"myschema", - "owner":"[USERNAME]", - "schema_id":"[UUID]", - "updated_at":[UNIX_TIME_MILLIS][0], - "updated_by":"[USERNAME]" + "enable_predictive_optimization": "INHERIT", + "full_name": "newmain.myschema", + "metastore_id": "[UUID]", + "name": "myschema", + "owner": "[USERNAME]", + "schema_id": "[UUID]", + "updated_at": [UNIX_TIME_MILLIS][0], + "updated_by": "[USERNAME]" } diff --git a/acceptance/bundle/resources/schemas/update/out.test.toml b/acceptance/bundle/resources/schemas/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/schemas/update/out.test.toml +++ b/acceptance/bundle/resources/schemas/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/secret_scopes/backend-type/out.test.toml b/acceptance/bundle/resources/secret_scopes/backend-type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/secret_scopes/backend-type/out.test.toml +++ b/acceptance/bundle/resources/secret_scopes/backend-type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.json b/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.json deleted file mode 100644 index ada0d0e46d6..00000000000 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "plan_version": 2, - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.secret_scopes.my_scope": { - "action": "create", - "new_state": { - "value": { - "scope": "test-scope-[UNIQUE_NAME]-1", - "scope_backend_type": "DATABRICKS" - } - } - }, - "resources.secret_scopes.my_scope.permissions": { - "depends_on": [ - { - "node": "resources.secret_scopes.my_scope", - "label": "${resources.secret_scopes.my_scope.name}" - } - ], - "action": "create", - "new_state": { - "value": { - "scope_name": "", - "acls": [ - { - "permission": "MANAGE", - "principal": "[USERNAME]" - }, - { - "permission": "WRITE", - "principal": "deco-test-user@databricks.com" - } - ] - }, - "vars": { - "scope_name": "${resources.secret_scopes.my_scope.name}" - } - } - } - } -} diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.txt new file mode 100644 index 00000000000..977ea6d0ef8 --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan1.direct.txt @@ -0,0 +1,14 @@ +json.plan_version = 2; +json.cli_version = "[DEV_VERSION]"; +json.plan.resources.secret_scopes.my_scope.action = "create"; +json.plan.resources.secret_scopes.my_scope.new_state.value.scope = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.new_state.value.scope_backend_type = "DATABRICKS"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].node = "resources.secret_scopes.my_scope"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].label = "${resources.secret_scopes.my_scope.name}"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "create"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.scope_name = ""; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[0].permission = "MANAGE"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[0].principal = "[USERNAME]"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[1].permission = "WRITE"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[1].principal = "deco-test-user@databricks.com"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.vars.scope_name = "${resources.secret_scopes.my_scope.name}"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.txt new file mode 100644 index 00000000000..c681ad6bb05 --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan1.terraform.txt @@ -0,0 +1,3 @@ +json.cli_version = "[DEV_VERSION]"; +json.plan.resources.secret_scopes.my_scope.action = "create"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "create"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.json b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.json deleted file mode 100644 index 7e87b3508c6..00000000000 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "plan_version": 2, - "cli_version": "[DEV_VERSION]", - "lineage": "[UUID]", - "serial": 1, - "plan": { - "resources.secret_scopes.my_scope": { - "action": "recreate", - "new_state": { - "value": { - "scope": "test-scope-[UNIQUE_NAME]-2", - "scope_backend_type": "DATABRICKS" - } - }, - "remote_state": { - "backend_type": "DATABRICKS", - "name": "test-scope-[UNIQUE_NAME]-1" - }, - "changes": { - "scope": { - "action": "recreate", - "reason": "immutable", - "old": "test-scope-[UNIQUE_NAME]-1", - "new": "test-scope-[UNIQUE_NAME]-2", - "remote": "test-scope-[UNIQUE_NAME]-1" - } - } - }, - "resources.secret_scopes.my_scope.permissions": { - "depends_on": [ - { - "node": "resources.secret_scopes.my_scope", - "label": "${resources.secret_scopes.my_scope.name}" - } - ], - "action": "update_id", - "new_state": { - "value": { - "scope_name": "", - "acls": [ - { - "permission": "MANAGE", - "principal": "[USERNAME]" - }, - { - "permission": "WRITE", - "principal": "deco-test-user@databricks.com" - } - ] - }, - "vars": { - "scope_name": "${resources.secret_scopes.my_scope.name}" - } - }, - "remote_state": { - "scope_name": "test-scope-[UNIQUE_NAME]-1", - "acls": [ - { - "permission": "MANAGE", - "principal": "[USERNAME]" - }, - { - "permission": "WRITE", - "principal": "deco-test-user@databricks.com" - } - ] - }, - "changes": { - "scope_name": { - "action": "update_id", - "reason": "id_changes", - "old": "test-scope-[UNIQUE_NAME]-1", - "new": "", - "remote": "test-scope-[UNIQUE_NAME]-1" - } - } - } - } -} diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.txt new file mode 100644 index 00000000000..6eb60bdf463 --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.direct.txt @@ -0,0 +1,33 @@ +json.plan_version = 2; +json.cli_version = "[DEV_VERSION]"; +json.lineage = "[UUID]"; +json.serial = 1; +json.plan.resources.secret_scopes.my_scope.action = "recreate"; +json.plan.resources.secret_scopes.my_scope.new_state.value.scope = "test-scope-[UNIQUE_NAME]-2"; +json.plan.resources.secret_scopes.my_scope.new_state.value.scope_backend_type = "DATABRICKS"; +json.plan.resources.secret_scopes.my_scope.remote_state.backend_type = "DATABRICKS"; +json.plan.resources.secret_scopes.my_scope.remote_state.name = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.changes.scope.action = "recreate"; +json.plan.resources.secret_scopes.my_scope.changes.scope.reason = "immutable"; +json.plan.resources.secret_scopes.my_scope.changes.scope.old = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.changes.scope.new = "test-scope-[UNIQUE_NAME]-2"; +json.plan.resources.secret_scopes.my_scope.changes.scope.remote = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].node = "resources.secret_scopes.my_scope"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].label = "${resources.secret_scopes.my_scope.name}"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "update_id"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.scope_name = ""; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[0].permission = "MANAGE"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[0].principal = "[USERNAME]"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[1].permission = "WRITE"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.value.acls[1].principal = "deco-test-user@databricks.com"; +json.plan.resources.secret_scopes.my_scope.permissions.new_state.vars.scope_name = "${resources.secret_scopes.my_scope.name}"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.scope_name = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[0].permission = "MANAGE"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[0].principal = "[USERNAME]"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[1].permission = "WRITE"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[1].principal = "deco-test-user@databricks.com"; +json.plan.resources.secret_scopes.my_scope.permissions.changes.scope_name.action = "update_id"; +json.plan.resources.secret_scopes.my_scope.permissions.changes.scope_name.reason = "id_changes"; +json.plan.resources.secret_scopes.my_scope.permissions.changes.scope_name.old = "test-scope-[UNIQUE_NAME]-1"; +json.plan.resources.secret_scopes.my_scope.permissions.changes.scope_name.new = ""; +json.plan.resources.secret_scopes.my_scope.permissions.changes.scope_name.remote = "test-scope-[UNIQUE_NAME]-1"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.json b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.json deleted file mode 100644 index d61ac7b77ae..00000000000 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.secret_scopes.my_scope": { - "action": "recreate" - }, - "resources.secret_scopes.my_scope.permissions": { - "action": "recreate" - } - } -} diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.txt new file mode 100644 index 00000000000..0ea7642fdd8 --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan2.terraform.txt @@ -0,0 +1,3 @@ +json.cli_version = "[DEV_VERSION]"; +json.plan.resources.secret_scopes.my_scope.action = "recreate"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "recreate"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.json b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.json deleted file mode 100644 index 2c911e3e5b3..00000000000 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "plan_version": 2, - "cli_version": "[DEV_VERSION]", - "lineage": "[UUID]", - "serial": 2, - "plan": { - "resources.secret_scopes.my_scope": { - "action": "skip", - "remote_state": { - "backend_type": "DATABRICKS", - "name": "test-scope-[UNIQUE_NAME]-2" - } - }, - "resources.secret_scopes.my_scope.permissions": { - "depends_on": [ - { - "node": "resources.secret_scopes.my_scope", - "label": "${resources.secret_scopes.my_scope.name}" - } - ], - "action": "skip", - "remote_state": { - "scope_name": "test-scope-[UNIQUE_NAME]-2", - "acls": [ - { - "permission": "MANAGE", - "principal": "[USERNAME]" - }, - { - "permission": "WRITE", - "principal": "deco-test-user@databricks.com" - } - ] - } - } - } -} diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.txt new file mode 100644 index 00000000000..e23e73418a4 --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.direct.txt @@ -0,0 +1,15 @@ +json.plan_version = 2; +json.cli_version = "[DEV_VERSION]"; +json.lineage = "[UUID]"; +json.serial = 2; +json.plan.resources.secret_scopes.my_scope.action = "skip"; +json.plan.resources.secret_scopes.my_scope.remote_state.backend_type = "DATABRICKS"; +json.plan.resources.secret_scopes.my_scope.remote_state.name = "test-scope-[UNIQUE_NAME]-2"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].node = "resources.secret_scopes.my_scope"; +json.plan.resources.secret_scopes.my_scope.permissions.depends_on[0].label = "${resources.secret_scopes.my_scope.name}"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "skip"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.scope_name = "test-scope-[UNIQUE_NAME]-2"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[0].permission = "MANAGE"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[0].principal = "[USERNAME]"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[1].permission = "WRITE"; +json.plan.resources.secret_scopes.my_scope.permissions.remote_state.acls[1].principal = "deco-test-user@databricks.com"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.json b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.json deleted file mode 100644 index 2bf6a06f489..00000000000 --- a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "cli_version": "[DEV_VERSION]", - "plan": { - "resources.secret_scopes.my_scope": { - "action": "skip" - }, - "resources.secret_scopes.my_scope.permissions": { - "action": "skip" - } - } -} diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.txt b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.txt new file mode 100644 index 00000000000..953b4317b2e --- /dev/null +++ b/acceptance/bundle/resources/secret_scopes/basic/out.plan_verify_no_drift.terraform.txt @@ -0,0 +1,3 @@ +json.cli_version = "[DEV_VERSION]"; +json.plan.resources.secret_scopes.my_scope.action = "skip"; +json.plan.resources.secret_scopes.my_scope.permissions.action = "skip"; diff --git a/acceptance/bundle/resources/secret_scopes/basic/out.test.toml b/acceptance/bundle/resources/secret_scopes/basic/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/secret_scopes/basic/out.test.toml +++ b/acceptance/bundle/resources/secret_scopes/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/secret_scopes/basic/output.txt b/acceptance/bundle/resources/secret_scopes/basic/output.txt index 5788225a450..9b6f7f340a5 100644 --- a/acceptance/bundle/resources/secret_scopes/basic/output.txt +++ b/acceptance/bundle/resources/secret_scopes/basic/output.txt @@ -19,8 +19,8 @@ Deployment complete! >>> [CLI] secrets get-secret test-scope-[UNIQUE_NAME]-1 my-key { - "key":"my-key", - "value":"bXktc2VjcmV0LXZhbHVl" + "key": "my-key", + "value": "bXktc2VjcmV0LXZhbHVl" } >>> [CLI] secrets list-acls test-scope-[UNIQUE_NAME]-1 @@ -77,8 +77,8 @@ Deployment complete! >>> [CLI] secrets get-secret test-scope-[UNIQUE_NAME]-2 another-key { - "key":"another-key", - "value":"YW5vdGhlci1zZWNyZXQtdmFsdWU=" + "key": "another-key", + "value": "YW5vdGhlci1zZWNyZXQtdmFsdWU=" } >>> [CLI] secrets list-acls test-scope-[UNIQUE_NAME]-2 diff --git a/acceptance/bundle/resources/secret_scopes/basic/script b/acceptance/bundle/resources/secret_scopes/basic/script index d0f3adeced3..ad967e1ba27 100755 --- a/acceptance/bundle/resources/secret_scopes/basic/script +++ b/acceptance/bundle/resources/secret_scopes/basic/script @@ -9,7 +9,7 @@ cleanup() { trap cleanup EXIT title "create the secret scope" -trace $CLI bundle plan -o json | sort_acls_json.py > out.plan1.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle plan -o json | gron.py --sort-arrays acls > out.plan1.$DATABRICKS_BUNDLE_ENGINE.txt trace $CLI bundle deploy scope_name=$($CLI bundle summary --output json | jq -r '.resources.secret_scopes.my_scope.name') @@ -26,7 +26,7 @@ title "update the name of the scope (should recreate)" export SECRET_SCOPE_NAME="test-scope-$UNIQUE_NAME-2" envsubst < databricks.yml.tmpl > databricks.yml -trace $CLI bundle plan -o json | sort_acls_json.py > out.plan2.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle plan -o json | gron.py --sort-arrays acls > out.plan2.$DATABRICKS_BUNDLE_ENGINE.txt trace $CLI bundle deploy # Capture API requests for verification. Terraform cleans up ACLs before deleting the scope, but direct does not, hence the difference in requests. @@ -43,4 +43,4 @@ trace $CLI secrets list-acls $scope_name | jq -c '.[]' | sort trace print_requests.py //secrets title "verify there's no persistent drift" -trace $CLI bundle plan -o json | sort_acls_json.py > out.plan_verify_no_drift.$DATABRICKS_BUNDLE_ENGINE.json +trace $CLI bundle plan -o json | gron.py --sort-arrays acls > out.plan_verify_no_drift.$DATABRICKS_BUNDLE_ENGINE.txt diff --git a/acceptance/bundle/resources/secret_scopes/delete_scope/out.test.toml b/acceptance/bundle/resources/secret_scopes/delete_scope/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/resources/secret_scopes/delete_scope/out.test.toml +++ b/acceptance/bundle/resources/secret_scopes/delete_scope/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/secret_scopes/permissions-collapse/out.test.toml b/acceptance/bundle/resources/secret_scopes/permissions-collapse/out.test.toml index b9c4b0e467b..6fc644d5164 100644 --- a/acceptance/bundle/resources/secret_scopes/permissions-collapse/out.test.toml +++ b/acceptance/bundle/resources/secret_scopes/permissions-collapse/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = true RunsOnDbr = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/secret_scopes/permissions/out.test.toml b/acceptance/bundle/resources/secret_scopes/permissions/out.test.toml index b426ff341a7..6b858c4df47 100644 --- a/acceptance/bundle/resources/secret_scopes/permissions/out.test.toml +++ b/acceptance/bundle/resources/secret_scopes/permissions/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = true RunsOnDbr = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/sql_warehouses/out.test.toml b/acceptance/bundle/resources/sql_warehouses/out.test.toml index b66365ef371..355ae0775bc 100644 --- a/acceptance/bundle/resources/sql_warehouses/out.test.toml +++ b/acceptance/bundle/resources/sql_warehouses/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false CloudSlow = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/synced_database_tables/basic/out.test.toml b/acceptance/bundle/resources/synced_database_tables/basic/out.test.toml index a9ae49e264e..e991fce9180 100644 --- a/acceptance/bundle/resources/synced_database_tables/basic/out.test.toml +++ b/acceptance/bundle/resources/synced_database_tables/basic/out.test.toml @@ -2,9 +2,5 @@ Local = true Cloud = true RequiresUnityCatalog = true RunsOnDbr = false - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/synced_database_tables/basic/output.txt b/acceptance/bundle/resources/synced_database_tables/basic/output.txt index 7e8871433fd..b1fac3c7678 100644 --- a/acceptance/bundle/resources/synced_database_tables/basic/output.txt +++ b/acceptance/bundle/resources/synced_database_tables/basic/output.txt @@ -50,6 +50,14 @@ The following resources will be deleted: delete resources.database_instances.my_instance delete resources.synced_database_tables.my_synced_table +This action will result in the deletion of the following Lakebase database instances. +All data stored in them will be permanently lost: + delete resources.database_instances.my_instance + +This action will result in the deletion of the following synced database tables. +The synced data in the destination database will be lost (the source table is preserved): + delete resources.synced_database_tables.my_synced_table + All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-lakebase-synced-table-[UNIQUE_NAME]/default Deleting files... diff --git a/acceptance/bundle/resources/synced_database_tables/basic/test.toml b/acceptance/bundle/resources/synced_database_tables/basic/test.toml index d41d9b917cc..191670590b5 100644 --- a/acceptance/bundle/resources/synced_database_tables/basic/test.toml +++ b/acceptance/bundle/resources/synced_database_tables/basic/test.toml @@ -20,5 +20,5 @@ Pattern = "POST /api/2.0/sql/statements/" Response.Body = '{"status": {"state": "SUCCEEDED"}, "manifest": {"schema": {"columns": []}}}' [[Server]] -Pattern = "DELETE /api/2.1/unity-catalog/tables/{name}" +Pattern = "DELETE /api/2.1/unity-catalog/tables/{full_name}" Response.Body = '{"status": "OK"}' diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/basic/databricks.yml.tmpl new file mode 100644 index 00000000000..05a9447facf --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: deploy-vs-endpoint-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/out.requests.direct.json b/acceptance/bundle/resources/vector_search_endpoints/basic/out.requests.direct.json new file mode 100644 index 00000000000..bcd2b5094d3 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/out.requests.direct.json @@ -0,0 +1,8 @@ +{ + "method": "POST", + "path": "/api/2.0/vector-search/endpoints", + "body": { + "endpoint_type": "STANDARD", + "name": "vs-endpoint-[UNIQUE_NAME]" + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/basic/out.test.toml new file mode 100644 index 00000000000..fe4076cdf9b --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/output.txt b/acceptance/bundle/resources/vector_search_endpoints/basic/output.txt new file mode 100644 index 00000000000..d3c70f81e40 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/output.txt @@ -0,0 +1,56 @@ + +>>> [CLI] bundle validate +Name: deploy-vs-endpoint-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-vs-endpoint-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle summary +Name: deploy-vs-endpoint-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-vs-endpoint-[UNIQUE_NAME]/default +Resources: + Vector Search Endpoints: + my_endpoint: + Name: vs-endpoint-[UNIQUE_NAME] + URL: [DATABRICKS_URL]/compute/vector-search/vs-endpoint-[UNIQUE_NAME]?o=[NUMID] + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-vs-endpoint-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] bundle summary +Name: deploy-vs-endpoint-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-vs-endpoint-[UNIQUE_NAME]/default +Resources: + Vector Search Endpoints: + my_endpoint: + Name: vs-endpoint-[UNIQUE_NAME] + URL: [DATABRICKS_URL]/compute/vector-search/vs-endpoint-[UNIQUE_NAME]?o=[NUMID] + +>>> print_requests.py //vector-search/endpoints + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-vs-endpoint-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/script b/acceptance/bundle/resources/vector_search_endpoints/basic/script new file mode 100644 index 00000000000..e68232aab32 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/script @@ -0,0 +1,22 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle validate + +trace $CLI bundle summary + +rm -f out.requests.txt +trace $CLI bundle deploy + +# Get endpoint details +endpoint_name="vs-endpoint-${UNIQUE_NAME}" +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' + +trace $CLI bundle summary + +trace print_requests.py //vector-search/endpoints > out.requests.$DATABRICKS_BUNDLE_ENGINE.json diff --git a/acceptance/bundle/resources/vector_search_endpoints/basic/test.toml b/acceptance/bundle/resources/vector_search_endpoints/basic/test.toml new file mode 100644 index 00000000000..f8b3bbe49dd --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/basic/test.toml @@ -0,0 +1 @@ +# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/databricks.yml.tmpl new file mode 100644 index 00000000000..5dce5dd5d45 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: drift-vs-endpoint-budget-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt new file mode 100644 index 00000000000..45555a83ff1 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt @@ -0,0 +1,28 @@ + +=== Initial deployment (no budget_policy_id) +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-budget-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Simulate remote drift: set budget_policy_id outside the bundle +>>> [CLI] vector-search-endpoints update-endpoint-budget-policy vs-endpoint-[UNIQUE_NAME] remote-policy +{ + "effective_budget_policy_id": "remote-policy" +} + +=== Plan detects drift and proposes update +>>> [CLI] bundle plan +update vector_search_endpoints.my_endpoint + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-budget-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script new file mode 100644 index 00000000000..c02467d6528 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/script @@ -0,0 +1,18 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +title "Initial deployment (no budget_policy_id)" +trace $CLI bundle deploy + +endpoint_name="vs-endpoint-${UNIQUE_NAME}" + +title "Simulate remote drift: set budget_policy_id outside the bundle" +trace $CLI vector-search-endpoints update-endpoint-budget-policy "${endpoint_name}" "remote-policy" + +title "Plan detects drift and proposes update" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged" diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/databricks.yml.tmpl new file mode 100644 index 00000000000..7936e98b23d --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/databricks.yml.tmpl @@ -0,0 +1,12 @@ +bundle: + name: drift-vs-endpoint-min-qps-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD + min_qps: 1 diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json new file mode 100644 index 00000000000..93aa4f1a24d --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json @@ -0,0 +1,15 @@ +{ + "plan": { + "resources.vector_search_endpoints.my_endpoint": { + "action": "update", + "changes": { + "min_qps": { + "action": "update", + "old": 1, + "new": 1, + "remote": 5 + } + } + } + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt new file mode 100644 index 00000000000..5a5f6d22f0c --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt @@ -0,0 +1,62 @@ + +=== Initial deployment +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Simulate remote drift: change min_qps to 5 outside the bundle +>>> [CLI] vector-search-endpoints patch-endpoint vs-endpoint-[UNIQUE_NAME] --min-qps 5 +{ + "creation_timestamp": [UNIX_TIME_MILLIS][0], + "creator": "[USERNAME]", + "endpoint_status": { + "state": "ONLINE" + }, + "endpoint_type": "STANDARD", + "id": "[UUID]", + "last_updated_timestamp": [UNIX_TIME_MILLIS][1], + "last_updated_user": "[USERNAME]", + "name": "vs-endpoint-[UNIQUE_NAME]", + "scaling_info": { + "requested_min_qps": 5 + } +} + +=== Plan detects drift and proposes update +>>> [CLI] bundle plan +update vector_search_endpoints.my_endpoint + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +=== Deploy restores min_qps to 1 +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //vector-search/endpoints +{ + "method": "PATCH", + "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]", + "body": { + "min_qps": 1 + } +} + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-min-qps-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script new file mode 100644 index 00000000000..81e86fefcb2 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script @@ -0,0 +1,26 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +title "Initial deployment" +trace $CLI bundle deploy + +endpoint_name="vs-endpoint-${UNIQUE_NAME}" + +title "Simulate remote drift: change min_qps to 5 outside the bundle" +trace $CLI vector-search-endpoints patch-endpoint "${endpoint_name}" --min-qps 5 + +title "Plan detects drift and proposes update" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged" +$CLI bundle plan --output json | jq '{plan: .plan | map_values({action, changes})}' > out.plan.$DATABRICKS_BUNDLE_ENGINE.json + +title "Deploy restores min_qps to 1" +rm -f out.requests.txt +trace $CLI bundle deploy +trace print_requests.py '//vector-search/endpoints' + +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl new file mode 100644 index 00000000000..b67caeebad6 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/databricks.yml.tmpl @@ -0,0 +1,14 @@ +bundle: + name: drift-vs-endpoint-recreated-same-name-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD + permissions: + - level: CAN_USE + group_name: admins diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/out.test.toml new file mode 100644 index 00000000000..fe4076cdf9b --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt new file mode 100644 index 00000000000..dece842119f --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/output.txt @@ -0,0 +1,65 @@ + +=== Initial deployment +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "id": "[ORIGINAL_ENDPOINT_UUID]", + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +=== Delete and recreate remotely with the same name +>>> [CLI] vector-search-endpoints delete-endpoint vs-endpoint-[UNIQUE_NAME] + +>>> [CLI] vector-search-endpoints create-endpoint vs-endpoint-[UNIQUE_NAME] STANDARD +{ + "id": "[REMOTE_RECREATED_ENDPOINT_UUID]", + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "id": "[REMOTE_RECREATED_ENDPOINT_UUID]", + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} +Original endpoint UUID: [ORIGINAL_ENDPOINT_UUID] +Remote recreated endpoint UUID: [REMOTE_RECREATED_ENDPOINT_UUID] + +=== Plan after out-of-band recreate +>>> [CLI] bundle plan +create vector_search_endpoints.my_endpoint.permissions + +Plan: 1 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "id": "[REMOTE_RECREATED_ENDPOINT_UUID]", + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +=== Verify no permanent drift after deploy +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 2 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script new file mode 100644 index 00000000000..dbef9250f28 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script @@ -0,0 +1,41 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +endpoint_name="vs-endpoint-${UNIQUE_NAME}" + +title "Initial deployment" +trace $CLI bundle deploy + +original_endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id') +add_repl.py "$original_endpoint_uuid" "ORIGINAL_ENDPOINT_UUID" +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}' + +title "Delete and recreate remotely with the same name" +trace $CLI vector-search-endpoints delete-endpoint "${endpoint_name}" +trace $CLI vector-search-endpoints create-endpoint "${endpoint_name}" STANDARD | jq '{id, name, endpoint_type}' + +remote_recreated_endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id') +add_repl.py "$remote_recreated_endpoint_uuid" "REMOTE_RECREATED_ENDPOINT_UUID" +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}' + +printf "Original endpoint UUID: %s\n" "$original_endpoint_uuid" +printf "Remote recreated endpoint UUID: %s\n" "$remote_recreated_endpoint_uuid" + +if [ "$original_endpoint_uuid" = "$remote_recreated_endpoint_uuid" ]; then + echo "Expected remote recreation to assign a different endpoint UUID" >&2 + exit 1 +fi + +title "Plan after out-of-band recreate" +trace $CLI bundle plan + +trace $CLI bundle deploy +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{id, name, endpoint_type}' + +title "Verify no permanent drift after deploy" +trace $CLI bundle plan | contains.py "Plan: 0 to add, 0 to change, 0 to delete" diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/test.toml new file mode 100644 index 00000000000..5646ee98875 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/test.toml @@ -0,0 +1,3 @@ +Badness = "After deleting and recreating a vector search endpoint remotely with the same name but a different UUID, bundle plan/deploy treats it as unchanged instead of deleting and recreating it." + +RecordRequests = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/databricks.yml.tmpl new file mode 100644 index 00000000000..8ad973b6d52 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/databricks.yml.tmpl @@ -0,0 +1,14 @@ +bundle: + name: recreate-create-fails-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-a-$UNIQUE_NAME + endpoint_type: STANDARD + blocker_endpoint: + name: vs-endpoint-b-$UNIQUE_NAME + endpoint_type: STORAGE_OPTIMIZED diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/output.txt b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/output.txt new file mode 100644 index 00000000000..67b3cb6182c --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/output.txt @@ -0,0 +1,42 @@ + +=== Initial deploy creates two endpoints with distinct names +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-create-fails-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Edit my_endpoint: rename onto blocker_endpoint's name and switch endpoint_type to trigger Recreate +>>> update_file.py databricks.yml vs-endpoint-a-[UNIQUE_NAME] vs-endpoint-b-[UNIQUE_NAME] + +>>> update_file.py databricks.yml endpoint_type: STANDARD endpoint_type: STORAGE_OPTIMIZED + +=== Deploy: Recreate of my_endpoint runs Delete (ok) then Create (409, name taken by blocker) +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-create-fails-[UNIQUE_NAME]/default/files... +Deploying resources... +Error: cannot recreate resources.vector_search_endpoints.my_endpoint: Vector search endpoint with name vs-endpoint-b-[UNIQUE_NAME] already exists (409 RESOURCE_ALREADY_EXISTS) + +Endpoint: POST [DATABRICKS_URL]/api/2.0/vector-search/endpoints +HTTP Status: 409 Conflict +API error_code: RESOURCE_ALREADY_EXISTS +API message: Vector search endpoint with name vs-endpoint-b-[UNIQUE_NAME] already exists + +Updating deployment state... + +Exit code: 1 + +=== Subsequent plan recovers: my_endpoint state was dropped, replan as Create +>>> [CLI] bundle plan +create vector_search_endpoints.my_endpoint + +Plan: 1 to add, 0 to change, 0 to delete, 1 unchanged + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.blocker_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/recreate-create-fails-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/script b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/script new file mode 100644 index 00000000000..b48a7e7a3cf --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/script @@ -0,0 +1,20 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve || true + rm -f out.requests.txt +} +trap cleanup EXIT + +title "Initial deploy creates two endpoints with distinct names" +trace $CLI bundle deploy + +title "Edit my_endpoint: rename onto blocker_endpoint's name and switch endpoint_type to trigger Recreate" +trace update_file.py databricks.yml "vs-endpoint-a-$UNIQUE_NAME" "vs-endpoint-b-$UNIQUE_NAME" +trace update_file.py databricks.yml " endpoint_type: STANDARD" " endpoint_type: STORAGE_OPTIMIZED" + +title "Deploy: Recreate of my_endpoint runs Delete (ok) then Create (409, name taken by blocker)" +errcode trace $CLI bundle deploy --auto-approve + +title "Subsequent plan recovers: my_endpoint state was dropped, replan as Create" +trace $CLI bundle plan diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/test.toml b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/databricks.yml.tmpl new file mode 100644 index 00000000000..b4528cc03bc --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: recreate-vs-endpoint-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.create.direct.json b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.create.direct.json new file mode 100644 index 00000000000..bcd2b5094d3 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.create.direct.json @@ -0,0 +1,8 @@ +{ + "method": "POST", + "path": "/api/2.0/vector-search/endpoints", + "body": { + "endpoint_type": "STANDARD", + "name": "vs-endpoint-[UNIQUE_NAME]" + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.recreate.direct.json b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.recreate.direct.json new file mode 100644 index 00000000000..90e1aff4cce --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.requests.recreate.direct.json @@ -0,0 +1,12 @@ +{ + "method": "DELETE", + "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]" +} +{ + "method": "POST", + "path": "/api/2.0/vector-search/endpoints", + "body": { + "endpoint_type": "STORAGE_OPTIMIZED", + "name": "vs-endpoint-[UNIQUE_NAME]" + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/output.txt b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/output.txt new file mode 100644 index 00000000000..c86cd5a682b --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/output.txt @@ -0,0 +1,40 @@ + +=== Initial deployment with STANDARD endpoint_type +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-vs-endpoint-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep //vector-search/endpoints + +=== Change endpoint_type (should trigger recreation) +>>> update_file.py databricks.yml endpoint_type: STANDARD endpoint_type: STORAGE_OPTIMIZED + +>>> [CLI] bundle plan +recreate vector_search_endpoints.my_endpoint + +Plan: 1 to add, 0 to change, 1 to delete, 0 unchanged + +>>> [CLI] bundle deploy --auto-approve +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-vs-endpoint-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep //vector-search/endpoints + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STORAGE_OPTIMIZED" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/recreate-vs-endpoint-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/script b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/script new file mode 100644 index 00000000000..b3920cacbbd --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/script @@ -0,0 +1,31 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +print_requests() { + local name=$1 + trace print_requests.py --keep '//vector-search/endpoints' > out.requests.${name}.$DATABRICKS_BUNDLE_ENGINE.json + rm -f out.requests.txt +} + +title "Initial deployment with STANDARD endpoint_type" +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests create + +title "Change endpoint_type (should trigger recreation)" +trace update_file.py databricks.yml "endpoint_type: STANDARD" "endpoint_type: STORAGE_OPTIMIZED" + +trace $CLI bundle plan +rm -f out.requests.txt +trace $CLI bundle deploy --auto-approve + +print_requests recreate + +endpoint_name="vs-endpoint-${UNIQUE_NAME}" +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' diff --git a/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/test.toml b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/recreate/endpoint_type/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/test.toml b/acceptance/bundle/resources/vector_search_endpoints/test.toml new file mode 100644 index 00000000000..aed959c0621 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/test.toml @@ -0,0 +1,11 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +# Vector Search endpoints are only available in direct mode (no Terraform provider) +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] + +Ignore = [ + "databricks.yml", + ".databricks", +] diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/databricks.yml.tmpl new file mode 100644 index 00000000000..5dfbdf6c35a --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/databricks.yml.tmpl @@ -0,0 +1,11 @@ +bundle: + name: update-vs-endpoint-budget-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.create.direct.json b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.create.direct.json new file mode 100644 index 00000000000..bcd2b5094d3 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.create.direct.json @@ -0,0 +1,8 @@ +{ + "method": "POST", + "path": "/api/2.0/vector-search/endpoints", + "body": { + "endpoint_type": "STANDARD", + "name": "vs-endpoint-[UNIQUE_NAME]" + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.update.direct.json b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.update.direct.json new file mode 100644 index 00000000000..36375854405 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.requests.update.direct.json @@ -0,0 +1,7 @@ +{ + "method": "PATCH", + "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]/budget-policy", + "body": { + "budget_policy_id": "test-policy-id" + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/output.txt b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/output.txt new file mode 100644 index 00000000000..3b2bb7bf1cf --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/output.txt @@ -0,0 +1,35 @@ + +=== Initial deployment (no budget policy) +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-budget-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep //vector-search/endpoints + +=== Add budget_policy_id +>>> update_file.py databricks.yml endpoint_type: STANDARD endpoint_type: STANDARD + budget_policy_id: test-policy-id + +>>> [CLI] bundle plan +update vector_search_endpoints.my_endpoint + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-budget-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py --keep //vector-search/endpoints + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-budget-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/script b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/script new file mode 100644 index 00000000000..8d19b9f60c8 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/script @@ -0,0 +1,29 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +print_requests() { + local name=$1 + trace print_requests.py --keep '//vector-search/endpoints' > out.requests.${name}.$DATABRICKS_BUNDLE_ENGINE.json + rm -f out.requests.txt +} + +title "Initial deployment (no budget policy)" +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests create + +title "Add budget_policy_id" +trace update_file.py databricks.yml "endpoint_type: STANDARD" "endpoint_type: STANDARD + budget_policy_id: test-policy-id" + +trace $CLI bundle plan +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests update diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/test.toml new file mode 100644 index 00000000000..18b1a88417e --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/budget_policy/test.toml @@ -0,0 +1 @@ +Cloud = false diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/databricks.yml.tmpl new file mode 100644 index 00000000000..7c326b69d51 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/databricks.yml.tmpl @@ -0,0 +1,15 @@ +bundle: + name: update-vs-endpoint-min-qps-$UNIQUE_NAME + +sync: + paths: [] + +resources: + vector_search_endpoints: + my_endpoint: + name: vs-endpoint-$UNIQUE_NAME + endpoint_type: STANDARD + min_qps: 1 + permissions: + - level: CAN_USE + group_name: admins diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.create.direct.json b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.create.direct.json new file mode 100644 index 00000000000..0a1d51a3512 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.create.direct.json @@ -0,0 +1,25 @@ +{ + "method": "POST", + "path": "/api/2.0/vector-search/endpoints", + "body": { + "endpoint_type": "STANDARD", + "min_qps": 1, + "name": "vs-endpoint-[UNIQUE_NAME]" + } +} +{ + "method": "PUT", + "path": "/api/2.0/permissions/vector-search-endpoints/[UUID]", + "body": { + "access_control_list": [ + { + "group_name": "admins", + "permission_level": "CAN_USE" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.update.direct.json b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.update.direct.json new file mode 100644 index 00000000000..24876c67bed --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.update.direct.json @@ -0,0 +1,23 @@ +{ + "method": "PATCH", + "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]", + "body": { + "min_qps": 2 + } +} +{ + "method": "PUT", + "path": "/api/2.0/permissions/vector-search-endpoints/[UUID]", + "body": { + "access_control_list": [ + { + "group_name": "admins", + "permission_level": "CAN_USE" + }, + { + "permission_level": "CAN_MANAGE", + "user_name": "[USERNAME]" + } + ] + } +} diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +RequiresUnityCatalog = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/output.txt b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/output.txt new file mode 100644 index 00000000000..8dec120bc4a --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/output.txt @@ -0,0 +1,47 @@ + +=== Initial deployment +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //vector-search/endpoints //permissions + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +=== Update min_qps +>>> update_file.py databricks.yml min_qps: 1 min_qps: 2 + +>>> [CLI] bundle plan +update vector_search_endpoints.my_endpoint +update vector_search_endpoints.my_endpoint.permissions + +Plan: 0 to add, 2 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //vector-search/endpoints //permissions + +>>> [CLI] vector-search-endpoints get-endpoint vs-endpoint-[UNIQUE_NAME] +{ + "name": "vs-endpoint-[UNIQUE_NAME]", + "endpoint_type": "STANDARD" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.vector_search_endpoints.my_endpoint + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-min-qps-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/script b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/script new file mode 100644 index 00000000000..c21d64b9d97 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/script @@ -0,0 +1,32 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +print_requests() { + local name=$1 + trace print_requests.py '//vector-search/endpoints' '//permissions' > out.requests.${name}.$DATABRICKS_BUNDLE_ENGINE.json +} + +title "Initial deployment" +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests create + +endpoint_name="vs-endpoint-${UNIQUE_NAME}" +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' + +title "Update min_qps" +trace update_file.py databricks.yml "min_qps: 1" "min_qps: 2" + +trace $CLI bundle plan +rm -f out.requests.txt +trace $CLI bundle deploy + +print_requests update + +trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/test.toml new file mode 100644 index 00000000000..ac17c7f22fe --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/test.toml @@ -0,0 +1,2 @@ +Cloud = false +Badness = "Updating min_qps also plans and applies permissions due to unresolved permissions object_id during planning" diff --git a/acceptance/bundle/resources/volumes/catalog-var-ref/out.test.toml b/acceptance/bundle/resources/volumes/catalog-var-ref/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/resources/volumes/catalog-var-ref/out.test.toml +++ b/acceptance/bundle/resources/volumes/catalog-var-ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/volumes/change-comment/out.test.toml b/acceptance/bundle/resources/volumes/change-comment/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/volumes/change-comment/out.test.toml +++ b/acceptance/bundle/resources/volumes/change-comment/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/change-comment/output.txt b/acceptance/bundle/resources/volumes/change-comment/output.txt index 5277d987e0f..51ea42102d1 100644 --- a/acceptance/bundle/resources/volumes/change-comment/output.txt +++ b/acceptance/bundle/resources/volumes/change-comment/output.txt @@ -40,18 +40,18 @@ Deployment complete! === Verify deployment >>> [CLI] volumes read main.myschema.myvolume { - "catalog_name":"main", - "comment":"COMMENT1", - "created_at":[UNIX_TIME_MILLIS][0], - "created_by":"[USERNAME]", - "full_name":"main.myschema.myvolume", - "name":"myvolume", - "owner":"[USERNAME]", - "schema_name":"myschema", - "storage_location":"s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", - "updated_at":[UNIX_TIME_MILLIS][0], - "volume_id":"[UUID]", - "volume_type":"MANAGED" + "catalog_name": "main", + "comment": "COMMENT1", + "created_at": [UNIX_TIME_MILLIS][0], + "created_by": "[USERNAME]", + "full_name": "main.myschema.myvolume", + "name": "myvolume", + "owner": "[USERNAME]", + "schema_name": "myschema", + "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", + "updated_at": [UNIX_TIME_MILLIS][0], + "volume_id": "[UUID]", + "volume_type": "MANAGED" } === Update comment diff --git a/acceptance/bundle/resources/volumes/change-name/out.test.toml b/acceptance/bundle/resources/volumes/change-name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/volumes/change-name/out.test.toml +++ b/acceptance/bundle/resources/volumes/change-name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/change-name/output.txt b/acceptance/bundle/resources/volumes/change-name/output.txt index b3ad13b9479..e34fadd4a61 100644 --- a/acceptance/bundle/resources/volumes/change-name/output.txt +++ b/acceptance/bundle/resources/volumes/change-name/output.txt @@ -59,19 +59,19 @@ Deployment complete! >>> [CLI] volumes read main.myschema.mynewvolume { - "catalog_name":"main", - "comment":"COMMENT1", - "created_at":[UNIX_TIME_MILLIS][0], - "created_by":"[USERNAME]", - "full_name":"main.myschema.mynewvolume", - "name":"mynewvolume", - "owner":"[USERNAME]", - "schema_name":"myschema", - "storage_location":"s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", - "updated_at":[UNIX_TIME_MILLIS][1], - "updated_by":"[USERNAME]", - "volume_id":"[UUID]", - "volume_type":"MANAGED" + "catalog_name": "main", + "comment": "COMMENT1", + "created_at": [UNIX_TIME_MILLIS][0], + "created_by": "[USERNAME]", + "full_name": "main.myschema.mynewvolume", + "name": "mynewvolume", + "owner": "[USERNAME]", + "schema_name": "myschema", + "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", + "updated_at": [UNIX_TIME_MILLIS][1], + "updated_by": "[USERNAME]", + "volume_id": "[UUID]", + "volume_type": "MANAGED" } >>> musterr [CLI] volumes read main.myschema.myvolume diff --git a/acceptance/bundle/resources/volumes/change-schema-name/out.requests.txt b/acceptance/bundle/resources/volumes/change-schema-name/out.requests.txt index 6356a3f868b..0c03eb00fd1 100644 --- a/acceptance/bundle/resources/volumes/change-schema-name/out.requests.txt +++ b/acceptance/bundle/resources/volumes/change-schema-name/out.requests.txt @@ -1,15 +1,7 @@ -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/volumes/main.myschema.myvolume" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/volumes/main.myschema.mynewvolume" diff --git a/acceptance/bundle/resources/volumes/change-schema-name/out.test.toml b/acceptance/bundle/resources/volumes/change-schema-name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/volumes/change-schema-name/out.test.toml +++ b/acceptance/bundle/resources/volumes/change-schema-name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/recreate/out.test.toml b/acceptance/bundle/resources/volumes/recreate/out.test.toml index 6acc5cec9dc..30aca39e5f2 100644 --- a/acceptance/bundle/resources/volumes/recreate/out.test.toml +++ b/acceptance/bundle/resources/volumes/recreate/out.test.toml @@ -2,6 +2,4 @@ Local = false Cloud = true RequiresUnityCatalog = true RunsOnDbr = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/remote-change-name/out.test.toml b/acceptance/bundle/resources/volumes/remote-change-name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/volumes/remote-change-name/out.test.toml +++ b/acceptance/bundle/resources/volumes/remote-change-name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/remote-change-name/output.txt b/acceptance/bundle/resources/volumes/remote-change-name/output.txt index b008fe33369..fb8551f5bc4 100644 --- a/acceptance/bundle/resources/volumes/remote-change-name/output.txt +++ b/acceptance/bundle/resources/volumes/remote-change-name/output.txt @@ -7,34 +7,34 @@ Deployment complete! >>> [CLI] volumes update mycatalog.myschema.myname --json {"new_name": "my_new_name"} { - "catalog_name":"mycatalog", - "created_at":[UNIX_TIME_MILLIS][0], - "created_by":"[USERNAME]", - "full_name":"mycatalog.myschema.my_new_name", - "name":"my_new_name", - "owner":"[USERNAME]", - "schema_name":"myschema", - "storage_location":"s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", - "updated_at":[UNIX_TIME_MILLIS][1], - "updated_by":"[USERNAME]", - "volume_id":"[UUID]", - "volume_type":"MANAGED" + "catalog_name": "mycatalog", + "created_at": [UNIX_TIME_MILLIS][0], + "created_by": "[USERNAME]", + "full_name": "mycatalog.myschema.my_new_name", + "name": "my_new_name", + "owner": "[USERNAME]", + "schema_name": "myschema", + "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", + "updated_at": [UNIX_TIME_MILLIS][1], + "updated_by": "[USERNAME]", + "volume_id": "[UUID]", + "volume_type": "MANAGED" } >>> [CLI] volumes read mycatalog.myschema.my_new_name { - "catalog_name":"mycatalog", - "created_at":[UNIX_TIME_MILLIS][0], - "created_by":"[USERNAME]", - "full_name":"mycatalog.myschema.my_new_name", - "name":"my_new_name", - "owner":"[USERNAME]", - "schema_name":"myschema", - "storage_location":"s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", - "updated_at":[UNIX_TIME_MILLIS][1], - "updated_by":"[USERNAME]", - "volume_id":"[UUID]", - "volume_type":"MANAGED" + "catalog_name": "mycatalog", + "created_at": [UNIX_TIME_MILLIS][0], + "created_by": "[USERNAME]", + "full_name": "mycatalog.myschema.my_new_name", + "name": "my_new_name", + "owner": "[USERNAME]", + "schema_name": "myschema", + "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", + "updated_at": [UNIX_TIME_MILLIS][1], + "updated_by": "[USERNAME]", + "volume_id": "[UUID]", + "volume_type": "MANAGED" } >>> [CLI] bundle plan diff --git a/acceptance/bundle/resources/volumes/remote-delete/out.test.toml b/acceptance/bundle/resources/volumes/remote-delete/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/resources/volumes/remote-delete/out.test.toml +++ b/acceptance/bundle/resources/volumes/remote-delete/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/volumes/set-storage-location/out.test.toml b/acceptance/bundle/resources/volumes/set-storage-location/out.test.toml index 1819a94c46c..8c738f635ac 100644 --- a/acceptance/bundle/resources/volumes/set-storage-location/out.test.toml +++ b/acceptance/bundle/resources/volumes/set-storage-location/out.test.toml @@ -1,10 +1,6 @@ Local = true Cloud = true RequiresUnityCatalog = true - -[CloudEnvs] - azure = false - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/app-with-job/out.test.toml b/acceptance/bundle/run/app-with-job/out.test.toml index e26b67058ae..9ff781c1f13 100644 --- a/acceptance/bundle/run/app-with-job/out.test.toml +++ b/acceptance/bundle/run/app-with-job/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true CloudSlow = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/basic/out.test.toml b/acceptance/bundle/run/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/basic/out.test.toml +++ b/acceptance/bundle/run/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/diagnostics/out.test.toml b/acceptance/bundle/run/diagnostics/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/diagnostics/out.test.toml +++ b/acceptance/bundle/run/diagnostics/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/basic/out.test.toml b/acceptance/bundle/run/inline-script/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/basic/out.test.toml +++ b/acceptance/bundle/run/inline-script/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/cwd/out.test.toml b/acceptance/bundle/run/inline-script/cwd/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/cwd/out.test.toml +++ b/acceptance/bundle/run/inline-script/cwd/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.requests.txt b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.requests.txt index c5b36c8f9cd..0c01e54a7b1 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.requests.txt +++ b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.requests.txt @@ -16,10 +16,6 @@ "path": "/oidc/v1/token", "raw_body": "grant_type=client_credentials\u0026scope=all-apis" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/oidc/.well-known/oauth-authorization-server" diff --git a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.test.toml b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.test.toml +++ b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/output.txt b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/output.txt index e2c200ce1de..0962b43283c 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/output.txt +++ b/acceptance/bundle/run/inline-script/databricks-cli/profile-is-passed/from_flag/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle run --profile myprofile -- [CLI] current-user me { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } diff --git a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.requests.txt b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.requests.txt index 7cf520fe0c1..0c815411ae9 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.requests.txt +++ b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.requests.txt @@ -2,10 +2,6 @@ "method": "GET", "path": "/.well-known/databricks-config" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "headers": { "Authorization": [ diff --git a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.test.toml b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.test.toml +++ b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/default/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.requests.txt b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.requests.txt index 3a134bc7758..6ba1aad2544 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.requests.txt +++ b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.requests.txt @@ -16,10 +16,6 @@ "path": "/oidc/v1/token", "raw_body": "grant_type=client_credentials\u0026scope=all-apis" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/oidc/.well-known/oauth-authorization-server" diff --git a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.test.toml b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.test.toml +++ b/acceptance/bundle/run/inline-script/databricks-cli/target-is-passed/from_flag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/no-auth/out.test.toml b/acceptance/bundle/run/inline-script/no-auth/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/no-auth/out.test.toml +++ b/acceptance/bundle/run/inline-script/no-auth/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/no-bundle/out.test.toml b/acceptance/bundle/run/inline-script/no-bundle/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/no-bundle/out.test.toml +++ b/acceptance/bundle/run/inline-script/no-bundle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/inline-script/no-separator/out.test.toml b/acceptance/bundle/run/inline-script/no-separator/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/inline-script/no-separator/out.test.toml +++ b/acceptance/bundle/run/inline-script/no-separator/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/jobs/partial_run/out.test.toml b/acceptance/bundle/run/jobs/partial_run/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/jobs/partial_run/out.test.toml +++ b/acceptance/bundle/run/jobs/partial_run/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/no-state/out.test.toml b/acceptance/bundle/run/no-state/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/no-state/out.test.toml +++ b/acceptance/bundle/run/no-state/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/refresh-flags/out.test.toml b/acceptance/bundle/run/refresh-flags/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/refresh-flags/out.test.toml +++ b/acceptance/bundle/run/refresh-flags/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/basic/out.test.toml b/acceptance/bundle/run/scripts/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/basic/out.test.toml +++ b/acceptance/bundle/run/scripts/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/basic/output.txt b/acceptance/bundle/run/scripts/basic/output.txt index 1ec8fd3be7a..870d02863c7 100644 --- a/acceptance/bundle/run/scripts/basic/output.txt +++ b/acceptance/bundle/run/scripts/basic/output.txt @@ -4,8 +4,8 @@ hello >>> [CLI] bundle run me { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } >>> [CLI] bundle run foo arg1 arg2 diff --git a/acceptance/bundle/run/scripts/cwd/out.test.toml b/acceptance/bundle/run/scripts/cwd/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/cwd/out.test.toml +++ b/acceptance/bundle/run/scripts/cwd/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/out.test.toml b/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/out.test.toml +++ b/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/output.txt b/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/output.txt index 2c0238ec30c..c64ea6b2ea0 100644 --- a/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/output.txt +++ b/acceptance/bundle/run/scripts/databricks-cli/profile-is-passed/from_flag/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle run me --profile myprofile { - "id":"[USERID]", - "userName":"[USERNAME]" + "id": "[USERID]", + "userName": "[USERNAME]" } diff --git a/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/default/out.test.toml b/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/default/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/default/out.test.toml +++ b/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/default/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/from_flag/out.test.toml b/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/from_flag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/from_flag/out.test.toml +++ b/acceptance/bundle/run/scripts/databricks-cli/target-is-passed/from_flag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/exit_code/out.test.toml b/acceptance/bundle/run/scripts/exit_code/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/exit_code/out.test.toml +++ b/acceptance/bundle/run/scripts/exit_code/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/io/out.test.toml b/acceptance/bundle/run/scripts/io/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/io/out.test.toml +++ b/acceptance/bundle/run/scripts/io/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/no-auth/out.test.toml b/acceptance/bundle/run/scripts/no-auth/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/no-auth/out.test.toml +++ b/acceptance/bundle/run/scripts/no-auth/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/no-interpolation/out.test.toml b/acceptance/bundle/run/scripts/no-interpolation/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/no-interpolation/out.test.toml +++ b/acceptance/bundle/run/scripts/no-interpolation/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/no_content/out.test.toml b/acceptance/bundle/run/scripts/no_content/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/no_content/out.test.toml +++ b/acceptance/bundle/run/scripts/no_content/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/shell/envvar/out.test.toml b/acceptance/bundle/run/scripts/shell/envvar/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/shell/envvar/out.test.toml +++ b/acceptance/bundle/run/scripts/shell/envvar/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/shell/math/out.test.toml b/acceptance/bundle/run/scripts/shell/math/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/shell/math/out.test.toml +++ b/acceptance/bundle/run/scripts/shell/math/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_name_root/out.test.toml b/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_name_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_name_root/out.test.toml +++ b/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_name_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_subconfigurations/out.test.toml b/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_subconfigurations/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_subconfigurations/out.test.toml +++ b/acceptance/bundle/run/scripts/unique_keys/duplicate_resource_and_script_subconfigurations/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/scripts/unique_keys/duplicate_script_names_in_subconfiguration/out.test.toml b/acceptance/bundle/run/scripts/unique_keys/duplicate_script_names_in_subconfiguration/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/scripts/unique_keys/duplicate_script_names_in_subconfiguration/out.test.toml +++ b/acceptance/bundle/run/scripts/unique_keys/duplicate_script_names_in_subconfiguration/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run/state-wiped/out.test.toml b/acceptance/bundle/run/state-wiped/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run/state-wiped/out.test.toml +++ b/acceptance/bundle/run/state-wiped/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/allowed/regular_user/out.test.toml b/acceptance/bundle/run_as/allowed/regular_user/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/allowed/regular_user/out.test.toml +++ b/acceptance/bundle/run_as/allowed/regular_user/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/allowed/service_principal/out.test.toml b/acceptance/bundle/run_as/allowed/service_principal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/allowed/service_principal/out.test.toml +++ b/acceptance/bundle/run_as/allowed/service_principal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/dashboard_embed/databricks.yml b/acceptance/bundle/run_as/dashboard_embed/databricks.yml new file mode 100644 index 00000000000..3908c50b64a --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: "dashboard_embed" + +run_as: + service_principal_name: "sp-name" + +variables: + embed: + default: true + +resources: + dashboards: + my_dashboard: + display_name: "Dashboard with embed" + embed_credentials: ${var.embed} + warehouse_id: "1234567890" diff --git a/acceptance/bundle/run_as/dashboard_embed/out.test.toml b/acceptance/bundle/run_as/dashboard_embed/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/dashboard_embed/output.txt b/acceptance/bundle/run_as/dashboard_embed/output.txt new file mode 100644 index 00000000000..5f316f521f6 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed/output.txt @@ -0,0 +1,25 @@ + +>>> errcode [CLI] bundle validate --var embed=false +Name: dashboard_embed +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/dashboard_embed/default + +Validation OK! + +>>> errcode [CLI] bundle validate --var embed=true +Error: dashboards with embed_credentials set to true do not support a setting a run_as user that is different from the owner. +Current identity: [USERNAME]. Run as identity: sp-name. +See https://docs.databricks.com/dev-tools/bundles/run-as.html to learn more about the run_as property. + in databricks.yml:14:7 + +Name: dashboard_embed +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/dashboard_embed/default + +Found 1 error + +Exit code: 1 diff --git a/acceptance/bundle/run_as/dashboard_embed/script b/acceptance/bundle/run_as/dashboard_embed/script new file mode 100644 index 00000000000..9ea6f8d51c6 --- /dev/null +++ b/acceptance/bundle/run_as/dashboard_embed/script @@ -0,0 +1,2 @@ +trace errcode $CLI bundle validate --var embed=false +trace errcode $CLI bundle validate --var embed=true diff --git a/acceptance/bundle/run_as/empty_override/out.test.toml b/acceptance/bundle/run_as/empty_override/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_override/out.test.toml +++ b/acceptance/bundle/run_as/empty_override/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/empty_run_as/out.test.toml b/acceptance/bundle/run_as/empty_run_as/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_run_as/out.test.toml +++ b/acceptance/bundle/run_as/empty_run_as/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/empty_run_as_dict/out.test.toml b/acceptance/bundle/run_as/empty_run_as_dict/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_run_as_dict/out.test.toml +++ b/acceptance/bundle/run_as/empty_run_as_dict/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/empty_sp/out.test.toml b/acceptance/bundle/run_as/empty_sp/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_sp/out.test.toml +++ b/acceptance/bundle/run_as/empty_sp/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/empty_user/out.test.toml b/acceptance/bundle/run_as/empty_user/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_user/out.test.toml +++ b/acceptance/bundle/run_as/empty_user/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/empty_user_and_sp/out.test.toml b/acceptance/bundle/run_as/empty_user_and_sp/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/empty_user_and_sp/out.test.toml +++ b/acceptance/bundle/run_as/empty_user_and_sp/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/invalid_both_sp_and_user/out.test.toml b/acceptance/bundle/run_as/invalid_both_sp_and_user/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/invalid_both_sp_and_user/out.test.toml +++ b/acceptance/bundle/run_as/invalid_both_sp_and_user/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/job_default/.gitignore b/acceptance/bundle/run_as/job_default/.gitignore new file mode 100644 index 00000000000..ad75d16418e --- /dev/null +++ b/acceptance/bundle/run_as/job_default/.gitignore @@ -0,0 +1 @@ +out.requests.txt diff --git a/acceptance/bundle/run_as/job_default/databricks.yml.tmpl b/acceptance/bundle/run_as/job_default/databricks.yml.tmpl new file mode 100644 index 00000000000..31a49d96ef9 --- /dev/null +++ b/acceptance/bundle/run_as/job_default/databricks.yml.tmpl @@ -0,0 +1,16 @@ +bundle: + name: "run_as_job_default_$UNIQUE_NAME" + +resources: + jobs: + job_with_run_as: + tasks: + - task_key: "task_one" + notebook_task: + notebook_path: "./test.py" + new_cluster: + spark_version: $DEFAULT_SPARK_VERSION + node_type_id: $NODE_TYPE_ID + num_workers: 1 + run_as: + user_name: deco-test-user@databricks.com diff --git a/acceptance/bundle/run_as/job_default/out.test.toml b/acceptance/bundle/run_as/job_default/out.test.toml new file mode 100644 index 00000000000..00d03354554 --- /dev/null +++ b/acceptance/bundle/run_as/job_default/out.test.toml @@ -0,0 +1,3 @@ +Local = false +Cloud = true +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/run_as/job_default/output.txt b/acceptance/bundle/run_as/job_default/output.txt new file mode 100644 index 00000000000..137a49cbd82 --- /dev/null +++ b/acceptance/bundle/run_as/job_default/output.txt @@ -0,0 +1,108 @@ + +=== Deploy with run_as +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //jobs +{ + "method": "POST", + "path": "/api/2.2/jobs/create", + "body": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/state/metadata.json" + }, + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Untitled", + "queue": { + "enabled": true + }, + "run_as": { + "user_name": "deco-test-user@databricks.com" + }, + "tasks": [ + { + "new_cluster": { + "node_type_id": "[NODE_TYPE_ID]", + "num_workers": 1, + "spark_version": "13.3.x-snapshot-scala2.12" + }, + "notebook_task": { + "notebook_path": "/Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/files/test" + }, + "task_key": "task_one" + } + ] + } +} + +>>> [CLI] jobs get [NUMID] +{ + "user_name": "deco-test-user@databricks.com" +} + +=== Remove run_as and redeploy +>>> [CLI] bundle plan +update jobs.job_with_run_as + +Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> print_requests.py //jobs +{ + "method": "POST", + "path": "/api/2.2/jobs/reset", + "body": { + "job_id": [NUMID], + "new_settings": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/state/metadata.json" + }, + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "Untitled", + "queue": { + "enabled": true + }, + "tasks": [ + { + "new_cluster": { + "node_type_id": "[NODE_TYPE_ID]", + "num_workers": 1, + "spark_version": "13.3.x-snapshot-scala2.12" + }, + "notebook_task": { + "notebook_path": "/Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default/files/test" + }, + "task_key": "task_one" + } + ] + } + } +} + +>>> [CLI] jobs get [NUMID] +{ + "user_name": "deco-test-user@databricks.com" +} + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.job_with_run_as + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/run_as_job_default_[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/run_as/job_default/script b/acceptance/bundle/run_as/job_default/script new file mode 100644 index 00000000000..3e80f98838e --- /dev/null +++ b/acceptance/bundle/run_as/job_default/script @@ -0,0 +1,22 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +title "Deploy with run_as" +trace $CLI bundle deploy +trace print_requests.py //jobs | contains.py "!GET" "POST" +JOB_ID=$($CLI bundle summary -o json | jq -r '.resources.jobs.job_with_run_as.id') +trace $CLI jobs get $JOB_ID | jq -r '.settings.run_as' + +update_file.py databricks.yml "run_as: + user_name: deco-test-user@databricks.com" '' + +title "Remove run_as and redeploy" +trace $CLI bundle plan +trace $CLI bundle deploy +trace print_requests.py //jobs | contains.py "!GET" "POST" +trace $CLI jobs get $JOB_ID | jq -r '.settings.run_as' diff --git a/acceptance/bundle/run_as/job_default/test.py b/acceptance/bundle/run_as/job_default/test.py new file mode 100644 index 00000000000..1645e04b1de --- /dev/null +++ b/acceptance/bundle/run_as/job_default/test.py @@ -0,0 +1 @@ +# Databricks notebook source diff --git a/acceptance/bundle/run_as/job_default/test.toml b/acceptance/bundle/run_as/job_default/test.toml new file mode 100644 index 00000000000..5d119a2dd7b --- /dev/null +++ b/acceptance/bundle/run_as/job_default/test.toml @@ -0,0 +1,9 @@ +Badness = "run_as is still set even though it's not in bundle and not in reset request" +Ignore = [".databricks/.gitignore"] + +Local = false +Cloud = true +RecordRequests = true + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/run_as/model_serving_different/out.test.toml b/acceptance/bundle/run_as/model_serving_different/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/model_serving_different/out.test.toml +++ b/acceptance/bundle/run_as/model_serving_different/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/model_serving_matching/out.test.toml b/acceptance/bundle/run_as/model_serving_matching/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/model_serving_matching/out.test.toml +++ b/acceptance/bundle/run_as/model_serving_matching/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/out.test.toml b/acceptance/bundle/run_as/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/out.test.toml +++ b/acceptance/bundle/run_as/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/pipelines/regular_user/out.test.toml b/acceptance/bundle/run_as/pipelines/regular_user/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/pipelines/regular_user/out.test.toml +++ b/acceptance/bundle/run_as/pipelines/regular_user/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/pipelines/service_principal/out.test.toml b/acceptance/bundle/run_as/pipelines/service_principal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/pipelines/service_principal/out.test.toml +++ b/acceptance/bundle/run_as/pipelines/service_principal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/pipelines_legacy/out.test.toml b/acceptance/bundle/run_as/pipelines_legacy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/run_as/pipelines_legacy/out.test.toml +++ b/acceptance/bundle/run_as/pipelines_legacy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/run_as/pipelines_legacy/output.txt b/acceptance/bundle/run_as/pipelines_legacy/output.txt index 654d5eab112..cfd58c5e866 100644 --- a/acceptance/bundle/run_as/pipelines_legacy/output.txt +++ b/acceptance/bundle/run_as/pipelines_legacy/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle validate -o json -Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC. +Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the pipelines in your DABs project as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC. at experimental.use_legacy_run_as in databricks.yml:8:22 diff --git a/acceptance/bundle/scripts/out.test.toml b/acceptance/bundle/scripts/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/scripts/out.test.toml +++ b/acceptance/bundle/scripts/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/scripts/restricted-execution/out.test.toml b/acceptance/bundle/scripts/restricted-execution/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/scripts/restricted-execution/out.test.toml +++ b/acceptance/bundle/scripts/restricted-execution/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/bad_env/out.test.toml b/acceptance/bundle/state/bad_env/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/bad_env/out.test.toml +++ b/acceptance/bundle/state/bad_env/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/bad_json_local/out.test.toml b/acceptance/bundle/state/bad_json_local/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/bad_json_local/out.test.toml +++ b/acceptance/bundle/state/bad_json_local/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/basic/out.test.toml b/acceptance/bundle/state/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/basic/out.test.toml +++ b/acceptance/bundle/state/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/engine_mismatch/out.test.toml b/acceptance/bundle/state/engine_mismatch/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/engine_mismatch/out.test.toml +++ b/acceptance/bundle/state/engine_mismatch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/force_pull_commands/databricks.yml b/acceptance/bundle/state/force_pull_commands/databricks.yml new file mode 100644 index 00000000000..0d4ab71b68b --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/databricks.yml @@ -0,0 +1,16 @@ +bundle: + name: force-pull-commands + +resources: + jobs: + foo: + name: foo + tasks: + - task_key: task + spark_python_task: + python_file: ./foo.py + environment_key: default + environments: + - environment_key: default + spec: + client: "2" diff --git a/acceptance/bundle/state/force_pull_commands/foo.py b/acceptance/bundle/state/force_pull_commands/foo.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/foo.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/state/force_pull_commands/out.test.toml b/acceptance/bundle/state/force_pull_commands/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/force_pull_commands/output.txt b/acceptance/bundle/state/force_pull_commands/output.txt new file mode 100644 index 00000000000..10560af0965 --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/output.txt @@ -0,0 +1,33 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Use a fake browser that just prints the URL it would have opened +=== bundle summary without --force-pull: no remote state read + +>>> [CLI] bundle summary + +=== bundle summary --force-pull: remote state read + +>>> [CLI] bundle summary --force-pull +{ + "method": "GET", + "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" +} + +=== bundle open without --force-pull: no remote state read + +>>> [CLI] bundle open foo +Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] + +=== bundle open --force-pull: remote state read + +>>> [CLI] bundle open foo --force-pull +Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] +{ + "method": "GET", + "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" +} diff --git a/acceptance/bundle/state/force_pull_commands/script b/acceptance/bundle/state/force_pull_commands/script new file mode 100644 index 00000000000..488a260e2ff --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/script @@ -0,0 +1,29 @@ +trace $CLI bundle deploy > /dev/null +rm -f out.requests.txt + +title "Use a fake browser that just prints the URL it would have opened" +export BROWSER="echo_browser.py" + +# touch out.requests.txt before each print_requests.py call: the commands without +# --force-pull make zero HTTP requests, so the file is never created and +# print_requests.py would otherwise exit with "File not found". + +title "bundle summary without --force-pull: no remote state read\n" +trace $CLI bundle summary > /dev/null +touch out.requests.txt +print_requests.py --get //workspace-files/ + +title "bundle summary --force-pull: remote state read\n" +trace $CLI bundle summary --force-pull > /dev/null +touch out.requests.txt +print_requests.py --get //workspace-files/ + +title "bundle open without --force-pull: no remote state read\n" +trace $CLI bundle open foo > /dev/null +touch out.requests.txt +print_requests.py --get //workspace-files/ + +title "bundle open --force-pull: remote state read\n" +trace $CLI bundle open foo --force-pull > /dev/null +touch out.requests.txt +print_requests.py --get //workspace-files/ diff --git a/acceptance/bundle/state/force_pull_commands/test.toml b/acceptance/bundle/state/force_pull_commands/test.toml new file mode 100644 index 00000000000..f5306da0126 --- /dev/null +++ b/acceptance/bundle/state/force_pull_commands/test.toml @@ -0,0 +1,12 @@ +Local = true +Cloud = false +RecordRequests = true + +Ignore = [".databricks"] + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] + +[[Repls]] +Old = '(resources\.json|terraform\.tfstate)' +New = 'STATE_FILENAME' diff --git a/acceptance/bundle/state/future_version/out.test.toml b/acceptance/bundle/state/future_version/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/state/future_version/out.test.toml +++ b/acceptance/bundle/state/future_version/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/state/lineage_different/out.test.toml b/acceptance/bundle/state/lineage_different/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/lineage_different/out.test.toml +++ b/acceptance/bundle/state/lineage_different/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/permission_level_migration/out.test.toml b/acceptance/bundle/state/permission_level_migration/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/state/permission_level_migration/out.test.toml +++ b/acceptance/bundle/state/permission_level_migration/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/state/same_serial/out.test.toml b/acceptance/bundle/state/same_serial/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/same_serial/out.test.toml +++ b/acceptance/bundle/state/same_serial/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/state_present/out.test.toml b/acceptance/bundle/state/state_present/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/state/state_present/out.test.toml +++ b/acceptance/bundle/state/state_present/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/state/state_present/output.txt b/acceptance/bundle/state/state_present/output.txt index 224c543d3bf..706b54a67a0 100644 --- a/acceptance/bundle/state/state_present/output.txt +++ b/acceptance/bundle/state/state_present/output.txt @@ -8,7 +8,7 @@ Updating deployment state... Deployment complete! >>> print_requests.py //api/2.1/unity-catalog/schemas -"databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" +"databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... @@ -17,7 +17,7 @@ Updating deployment state... Deployment complete! >>> print_requests.py --get //api/2.1/unity-catalog/schemas -"databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" +"databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" === Adding resources.json with lower serial does not change anything >>> DATABRICKS_BUNDLE_ENGINE=direct [CLI] bundle plan diff --git a/acceptance/bundle/summary/missing-libraries-file-path/out.test.toml b/acceptance/bundle/summary/missing-libraries-file-path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/summary/missing-libraries-file-path/out.test.toml +++ b/acceptance/bundle/summary/missing-libraries-file-path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/summary/modified_status/out.test.toml b/acceptance/bundle/summary/modified_status/out.test.toml index 53563c84ffe..7f4e2c0ca80 100644 --- a/acceptance/bundle/summary/modified_status/out.test.toml +++ b/acceptance/bundle/summary/modified_status/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - VARIANT = ["empty_resources.yml", "no_resources.yml"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.VARIANT = ["empty_resources.yml", "no_resources.yml"] diff --git a/acceptance/bundle/summary/show-full-config/out.test.toml b/acceptance/bundle/summary/show-full-config/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/summary/show-full-config/out.test.toml +++ b/acceptance/bundle/summary/show-full-config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/sync/dryrun/out.test.toml b/acceptance/bundle/sync/dryrun/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/sync/dryrun/out.test.toml +++ b/acceptance/bundle/sync/dryrun/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/sync/out.test.toml b/acceptance/bundle/sync/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/sync/out.test.toml +++ b/acceptance/bundle/sync/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/syncroot/dotdot-git/out.test.toml b/acceptance/bundle/syncroot/dotdot-git/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/syncroot/dotdot-git/out.test.toml +++ b/acceptance/bundle/syncroot/dotdot-git/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/syncroot/dotdot-nogit/out.test.toml b/acceptance/bundle/syncroot/dotdot-nogit/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/syncroot/dotdot-nogit/out.test.toml +++ b/acceptance/bundle/syncroot/dotdot-nogit/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-app-lifecycle-started/databricks.yml b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/databricks.yml new file mode 100644 index 00000000000..73279b89f9b --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/databricks.yml @@ -0,0 +1,10 @@ +bundle: + name: test-bundle + +resources: + apps: + myapp: + name: my-app + source_code_path: . + lifecycle: + started: true diff --git a/acceptance/bundle/telemetry/deploy-app-lifecycle-started/out.test.toml b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/telemetry/deploy-app-lifecycle-started/output.txt b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/output.txt new file mode 100644 index 00000000000..3c47531c6fa --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/output.txt @@ -0,0 +1,57 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +✓ Deployment succeeded +Updating deployment state... +Deployment complete! + +>>> cat out.requests.txt +{ + "bool_values": [ + { + "key": "local.cache.attempt", + "value": true + }, + { + "key": "local.cache.miss", + "value": true + }, + { + "key": "experimental.use_legacy_run_as", + "value": false + }, + { + "key": "run_as_set", + "value": false + }, + { + "key": "presets_name_prefix_is_set", + "value": false + }, + { + "key": "python_wheel_wrapper_is_set", + "value": false + }, + { + "key": "skip_artifact_cleanup", + "value": false + }, + { + "key": "has_serverless_compute", + "value": false + }, + { + "key": "has_classic_job_compute", + "value": false + }, + { + "key": "has_classic_interactive_compute", + "value": false + }, + { + "key": "app_lifecycle_started", + "value": true + } + ] +} diff --git a/acceptance/bundle/telemetry/deploy-app-lifecycle-started/script b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/script new file mode 100644 index 00000000000..67a3ba6299e --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/script @@ -0,0 +1,5 @@ +trace $CLI bundle deploy + +trace cat out.requests.txt | jq 'select(has("path") and .path == "/telemetry-ext") | .body.protoLogs[] | fromjson | .entry.databricks_cli_log.bundle_deploy_event.experimental | {bool_values}' + +rm out.requests.txt diff --git a/acceptance/bundle/telemetry/deploy-app-lifecycle-started/test.toml b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/test.toml new file mode 100644 index 00000000000..f32a7530744 --- /dev/null +++ b/acceptance/bundle/telemetry/deploy-app-lifecycle-started/test.toml @@ -0,0 +1,2 @@ +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/telemetry/deploy-artifact-path-type/out.test.toml b/acceptance/bundle/telemetry/deploy-artifact-path-type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-artifact-path-type/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-artifact-path-type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-artifacts-variables/out.test.toml b/acceptance/bundle/telemetry/deploy-artifacts-variables/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-artifacts-variables/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-artifacts-variables/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-compute-type/out.test.toml b/acceptance/bundle/telemetry/deploy-compute-type/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-compute-type/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-compute-type/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-config-file-count/out.test.toml b/acceptance/bundle/telemetry/deploy-config-file-count/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-config-file-count/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-config-file-count/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-error-message/out.test.toml b/acceptance/bundle/telemetry/deploy-error-message/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-error-message/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-error-message/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-error/out.test.toml b/acceptance/bundle/telemetry/deploy-error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-error/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-experimental/out.test.toml b/acceptance/bundle/telemetry/deploy-experimental/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-experimental/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-experimental/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-experimental/output.txt b/acceptance/bundle/telemetry/deploy-experimental/output.txt index d96e688b0ac..cf7a2358da7 100644 --- a/acceptance/bundle/telemetry/deploy-experimental/output.txt +++ b/acceptance/bundle/telemetry/deploy-experimental/output.txt @@ -1,6 +1,6 @@ >>> [CLI] bundle deploy -Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC. +Warning: You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the pipelines in your DABs project as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC. at experimental.use_legacy_run_as in databricks.yml:5:22 diff --git a/acceptance/bundle/telemetry/deploy-mode/out.test.toml b/acceptance/bundle/telemetry/deploy-mode/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-mode/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-mode/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-name-prefix/custom/out.test.toml b/acceptance/bundle/telemetry/deploy-name-prefix/custom/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-name-prefix/custom/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-name-prefix/custom/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-name-prefix/mode-development/out.test.toml b/acceptance/bundle/telemetry/deploy-name-prefix/mode-development/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-name-prefix/mode-development/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-name-prefix/mode-development/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-no-uuid/out.test.toml b/acceptance/bundle/telemetry/deploy-no-uuid/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-no-uuid/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-no-uuid/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-run-as/out.test.toml b/acceptance/bundle/telemetry/deploy-run-as/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-run-as/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-run-as/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-target-count/out.test.toml b/acceptance/bundle/telemetry/deploy-target-count/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-target-count/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-target-count/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-variable-count/out.test.toml b/acceptance/bundle/telemetry/deploy-variable-count/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-variable-count/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-variable-count/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy-whl-artifacts/out.test.toml b/acceptance/bundle/telemetry/deploy-whl-artifacts/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy-whl-artifacts/out.test.toml +++ b/acceptance/bundle/telemetry/deploy-whl-artifacts/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/telemetry/deploy/out.test.toml b/acceptance/bundle/telemetry/deploy/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/telemetry/deploy/out.test.toml +++ b/acceptance/bundle/telemetry/deploy/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/helper_upper_lower/out.test.toml b/acceptance/bundle/templates-machinery/helper_upper_lower/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates-machinery/helper_upper_lower/out.test.toml +++ b/acceptance/bundle/templates-machinery/helper_upper_lower/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/helper_username/out.test.toml b/acceptance/bundle/templates-machinery/helper_username/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates-machinery/helper_username/out.test.toml +++ b/acceptance/bundle/templates-machinery/helper_username/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/helpers-error/out.test.toml b/acceptance/bundle/templates-machinery/helpers-error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates-machinery/helpers-error/out.test.toml +++ b/acceptance/bundle/templates-machinery/helpers-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/number-precision/databricks_template_schema.json b/acceptance/bundle/templates-machinery/number-precision/databricks_template_schema.json new file mode 100644 index 00000000000..9a1bfb55a5c --- /dev/null +++ b/acceptance/bundle/templates-machinery/number-precision/databricks_template_schema.json @@ -0,0 +1,9 @@ +{ + "properties": { + "n": { + "type": "number", + "description": "A number variable", + "default": 1.1 + } + } +} diff --git a/acceptance/bundle/templates-machinery/number-precision/out.test.toml b/acceptance/bundle/templates-machinery/number-precision/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/templates-machinery/number-precision/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/number-precision/output.txt b/acceptance/bundle/templates-machinery/number-precision/output.txt new file mode 100644 index 00000000000..7b07f8a5b53 --- /dev/null +++ b/acceptance/bundle/templates-machinery/number-precision/output.txt @@ -0,0 +1,6 @@ + +>>> [CLI] bundle init . +✨ Successfully initialized template + +>>> cat number.txt +n: 1.1 diff --git a/acceptance/bundle/templates-machinery/number-precision/script b/acceptance/bundle/templates-machinery/number-precision/script new file mode 100644 index 00000000000..e15c9e54dcd --- /dev/null +++ b/acceptance/bundle/templates-machinery/number-precision/script @@ -0,0 +1,4 @@ +trace $CLI bundle init . + +trace cat number.txt +rm number.txt diff --git a/acceptance/bundle/templates-machinery/number-precision/template/number.txt.tmpl b/acceptance/bundle/templates-machinery/number-precision/template/number.txt.tmpl new file mode 100644 index 00000000000..cb1344deb34 --- /dev/null +++ b/acceptance/bundle/templates-machinery/number-precision/template/number.txt.tmpl @@ -0,0 +1 @@ +n: {{ .n }} diff --git a/acceptance/bundle/templates-machinery/wrong-path/out.test.toml b/acceptance/bundle/templates-machinery/wrong-path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates-machinery/wrong-path/out.test.toml +++ b/acceptance/bundle/templates-machinery/wrong-path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates-machinery/wrong-url/out.test.toml b/acceptance/bundle/templates-machinery/wrong-url/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates-machinery/wrong-url/out.test.toml +++ b/acceptance/bundle/templates-machinery/wrong-url/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/dbt-sql/out.test.toml b/acceptance/bundle/templates/dbt-sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/dbt-sql/out.test.toml +++ b/acceptance/bundle/templates/dbt-sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/dbt-sql/output/my_dbt_sql/README.md b/acceptance/bundle/templates/dbt-sql/output/my_dbt_sql/README.md index 6fd15788a5f..00a91e430c4 100644 --- a/acceptance/bundle/templates/dbt-sql/output/my_dbt_sql/README.md +++ b/acceptance/bundle/templates/dbt-sql/output/my_dbt_sql/README.md @@ -102,7 +102,7 @@ on CI/CD setup. ## Manually deploying to Databricks with Declarative Automation Bundles Declarative Automation Bundles can be used to deploy to Databricks and to execute -dbt commands as a job using Databricks Workflows. See +dbt commands as a job using Databricks Jobs. See https://docs.databricks.com/dev-tools/bundles/index.html to learn more. Use the Databricks CLI to deploy a development copy of this project to a workspace: @@ -117,7 +117,7 @@ is optional here.) This deploys everything that's defined for this project. For example, the default template would deploy a job called `[dev yourname] my_dbt_sql_job` to your workspace. -You can find that job by opening your workpace and clicking on **Workflows**. +You can find that job by opening your workpace and clicking on **Jobs & Pipelines**. You can also deploy to your production target directly from the command-line. The warehouse, catalog, and schema for that target are configured in `dbt_profiles/profiles.yml`. diff --git a/acceptance/bundle/templates/default-minimal/out.test.toml b/acceptance/bundle/templates/default-minimal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-minimal/out.test.toml +++ b/acceptance/bundle/templates/default-minimal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-python/azure-government/out.test.toml b/acceptance/bundle/templates/default-python/azure-government/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-python/azure-government/out.test.toml +++ b/acceptance/bundle/templates/default-python/azure-government/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-python/classic/out.test.toml b/acceptance/bundle/templates/default-python/classic/out.test.toml index 3c8bb15e9f3..2f44fc0b7cc 100644 --- a/acceptance/bundle/templates/default-python/classic/out.test.toml +++ b/acceptance/bundle/templates/default-python/classic/out.test.toml @@ -1,7 +1,5 @@ Local = true Cloud = false Phase = 1 - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/templates/default-python/combinations/classic/out.test.toml b/acceptance/bundle/templates/default-python/combinations/classic/out.test.toml index 3d911317b67..9f9b4934ffe 100644 --- a/acceptance/bundle/templates/default-python/combinations/classic/out.test.toml +++ b/acceptance/bundle/templates/default-python/combinations/classic/out.test.toml @@ -1,9 +1,7 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - DLT = ["yes", "no"] - NBOOK = ["yes", "no"] - PY = ["yes", "no"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DLT = ["yes", "no"] +EnvMatrix.NBOOK = ["yes", "no"] +EnvMatrix.PY = ["yes", "no"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/templates/default-python/combinations/serverless/out.test.toml b/acceptance/bundle/templates/default-python/combinations/serverless/out.test.toml index 3d911317b67..9f9b4934ffe 100644 --- a/acceptance/bundle/templates/default-python/combinations/serverless/out.test.toml +++ b/acceptance/bundle/templates/default-python/combinations/serverless/out.test.toml @@ -1,9 +1,7 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - DLT = ["yes", "no"] - NBOOK = ["yes", "no"] - PY = ["yes", "no"] - READPLAN = ["", "1"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DLT = ["yes", "no"] +EnvMatrix.NBOOK = ["yes", "no"] +EnvMatrix.PY = ["yes", "no"] +EnvMatrix.READPLAN = ["", "1"] diff --git a/acceptance/bundle/templates/default-python/fail-missing-uv/out.test.toml b/acceptance/bundle/templates/default-python/fail-missing-uv/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-python/fail-missing-uv/out.test.toml +++ b/acceptance/bundle/templates/default-python/fail-missing-uv/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-python/integration_classic/out.test.toml b/acceptance/bundle/templates/default-python/integration_classic/out.test.toml index 816e788f467..50677b5f636 100644 --- a/acceptance/bundle/templates/default-python/integration_classic/out.test.toml +++ b/acceptance/bundle/templates/default-python/integration_classic/out.test.toml @@ -1,6 +1,10 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - UV_PYTHON = ["3.9", "3.10", "3.11", "3.12", "3.13"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.UV_PYTHON = [ + "3.9", + "3.10", + "3.11", + "3.12", + "3.13" +] diff --git a/acceptance/bundle/templates/default-python/no-uc/out.test.toml b/acceptance/bundle/templates/default-python/no-uc/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-python/no-uc/out.test.toml +++ b/acceptance/bundle/templates/default-python/no-uc/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-python/serverless-customcatalog/out.test.toml b/acceptance/bundle/templates/default-python/serverless-customcatalog/out.test.toml index 4cfe03e9f9d..be193812ec2 100644 --- a/acceptance/bundle/templates/default-python/serverless-customcatalog/out.test.toml +++ b/acceptance/bundle/templates/default-python/serverless-customcatalog/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false Phase = 1 - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-python/serverless/out.test.toml b/acceptance/bundle/templates/default-python/serverless/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-python/serverless/out.test.toml +++ b/acceptance/bundle/templates/default-python/serverless/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-scala/out.test.toml b/acceptance/bundle/templates/default-scala/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-scala/out.test.toml +++ b/acceptance/bundle/templates/default-scala/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-scala/output/my_default_scala/README.md b/acceptance/bundle/templates/default-scala/output/my_default_scala/README.md index 9bc393514c2..1e5f08854e5 100644 --- a/acceptance/bundle/templates/default-scala/output/my_default_scala/README.md +++ b/acceptance/bundle/templates/default-scala/output/my_default_scala/README.md @@ -21,7 +21,7 @@ The 'my_default_scala' project was generated by using the default-scala template This deploys everything that's defined for this project. For example, the default template would deploy a job called `[dev yourname] my_default_scala_job` to your workspace. - You can find that job by opening your workspace and clicking on **Workflows**. + You can find that job by opening your workspace and clicking on **Jobs & Pipelines**. 4. Similarly, to deploy a production copy, type: ``` diff --git a/acceptance/bundle/templates/default-sql/out.test.toml b/acceptance/bundle/templates/default-sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/default-sql/out.test.toml +++ b/acceptance/bundle/templates/default-sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/default-sql/output/my_default_sql/README.md b/acceptance/bundle/templates/default-sql/output/my_default_sql/README.md index 9d915327dbe..551aae1ccf9 100644 --- a/acceptance/bundle/templates/default-sql/output/my_default_sql/README.md +++ b/acceptance/bundle/templates/default-sql/output/my_default_sql/README.md @@ -21,7 +21,7 @@ The 'my_default_sql' project was generated by using the default-sql template. This deploys everything that's defined for this project. For example, the default template would deploy a job called `[dev yourname] my_default_sql_job` to your workspace. - You can find that job by opening your workpace and clicking on **Workflows**. + You can find that job by opening your workpace and clicking on **Jobs & Pipelines**. 4. Similarly, to deploy a production copy, type: ``` diff --git a/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_daily.sql b/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_daily.sql index ea7b80b54f6..27bf1eed460 100644 --- a/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_daily.sql +++ b/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_daily.sql @@ -1,4 +1,4 @@ --- This query is executed using Databricks Workflows (see resources/my_default_sql_sql.job.yml) +-- This query is executed using Databricks Jobs (see resources/my_default_sql_sql.job.yml) USE CATALOG {{catalog}}; USE IDENTIFIER({{schema}}); diff --git a/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_raw.sql b/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_raw.sql index 79b1354cf4a..d0d1afa6604 100644 --- a/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_raw.sql +++ b/acceptance/bundle/templates/default-sql/output/my_default_sql/src/orders_raw.sql @@ -1,4 +1,4 @@ --- This query is executed using Databricks Workflows (see resources/my_default_sql_sql.job.yml) +-- This query is executed using Databricks Jobs (see resources/my_default_sql_sql.job.yml) -- -- The streaming table below ingests all JSON files in /databricks-datasets/retail-org/sales_orders/ -- See also https://docs.databricks.com/sql/language-manual/sql-ref-syntax-ddl-create-streaming-table.html diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/input.json b/acceptance/bundle/templates/experimental-jobs-as-code/input.json deleted file mode 100644 index 5c5fcfc3850..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/input.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "project_name": "my_jobs_as_code", - "include_notebook": "yes", - "include_python": "yes", - "include_dlt": "yes" -} diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/out.test.toml b/acceptance/bundle/templates/experimental-jobs-as-code/out.test.toml deleted file mode 100644 index d560f1de043..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/out.test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output.txt b/acceptance/bundle/templates/experimental-jobs-as-code/output.txt deleted file mode 100644 index 089a5c53a40..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output.txt +++ /dev/null @@ -1,118 +0,0 @@ - ->>> [CLI] bundle init experimental-jobs-as-code --config-file ./input.json --output-dir output - -Welcome to (EXPERIMENTAL) "Jobs as code" template for Declarative Automation Bundles! -Workspace to use (auto-detected, edit in 'my_jobs_as_code/databricks.yml'): [DATABRICKS_URL] - -✨ Your new project has been created in the 'my_jobs_as_code' directory! - -Please refer to the README.md file for "getting started" instructions. -See also the documentation at https://docs.databricks.com/dev-tools/bundles/index.html. - ->>> [CLI] bundle validate -t dev --output json -Warning: Ignoring Databricks CLI version constraint for development build. Required: >= 0.248.0, current: [DEV_VERSION] - -{ - "jobs": { - "my_jobs_as_code_job": { - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_jobs_as_code/dev/state/metadata.json" - }, - "edit_mode": "UI_LOCKED", - "format": "MULTI_TASK", - "job_clusters": [ - { - "job_cluster_key": "job_cluster", - "new_cluster": { - "autoscale": { - "max_workers": 4, - "min_workers": 1 - }, - "data_security_mode": "SINGLE_USER", - "node_type_id": "[NODE_TYPE_ID]", - "spark_version": "15.4.x-scala2.12" - } - } - ], - "max_concurrent_runs": 4, - "name": "[dev [USERNAME]] my_jobs_as_code_job", - "queue": { - "enabled": true - }, - "tags": { - "dev": "[USERNAME]" - }, - "tasks": [ - { - "depends_on": [ - { - "task_key": "notebook_task" - } - ], - "job_cluster_key": "job_cluster", - "libraries": [ - { - "whl": "dist/*.whl" - } - ], - "python_wheel_task": { - "entry_point": "main", - "package_name": "my_jobs_as_code" - }, - "task_key": "main_task" - }, - { - "job_cluster_key": "job_cluster", - "notebook_task": { - "notebook_path": "/Workspace/Users/[USERNAME]/.bundle/my_jobs_as_code/dev/files/src/notebook" - }, - "task_key": "notebook_task" - } - ], - "trigger": { - "pause_status": "PAUSED", - "periodic": { - "interval": 1, - "unit": "DAYS" - } - } - } - }, - "pipelines": { - "my_jobs_as_code_pipeline": { - "catalog": "catalog_name", - "channel": "CURRENT", - "configuration": { - "bundle.sourcePath": "/Workspace/Users/[USERNAME]/.bundle/my_jobs_as_code/dev/files/src" - }, - "deployment": { - "kind": "BUNDLE", - "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_jobs_as_code/dev/state/metadata.json" - }, - "development": true, - "edition": "ADVANCED", - "libraries": [ - { - "notebook": { - "path": "/Workspace/Users/[USERNAME]/.bundle/my_jobs_as_code/dev/files/src/dlt_pipeline" - } - } - ], - "name": "[dev [USERNAME]] my_jobs_as_code_pipeline", - "tags": { - "dev": "[USERNAME]" - }, - "target": "my_jobs_as_code_dev" - } - } -} - ->>> unzip -Z1 dist/my_jobs_as_code-0.0.1-py3-none-any.whl -my_jobs_as_code/__init__.py -my_jobs_as_code/main.py -my_jobs_as_code-0.0.1.dist-info/METADATA -my_jobs_as_code-0.0.1.dist-info/WHEEL -my_jobs_as_code-0.0.1.dist-info/entry_points.txt -my_jobs_as_code-0.0.1.dist-info/top_level.txt -my_jobs_as_code-0.0.1.dist-info/RECORD diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/README.md b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/README.md deleted file mode 100644 index 6bfac07da05..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# my_jobs_as_code - -The 'my_jobs_as_code' project was generated by using the "Jobs as code" template. - -## Prerequisites - -1. Install Databricks CLI 0.238 or later. - See [Install or update the Databricks CLI](https://docs.databricks.com/en/dev-tools/cli/install.html). - -2. Install uv. See [Installing uv](https://docs.astral.sh/uv/getting-started/installation/). - We use uv to create a virtual environment and install the required dependencies. - -3. Authenticate to your Databricks workspace if you have not done so already: - ``` - $ databricks configure - ``` - -4. Optionally, install developer tools such as the Databricks extension for Visual Studio Code from - https://docs.databricks.com/dev-tools/vscode-ext.html. Or read the "getting started" documentation for - **Databricks Connect** for instructions on running the included Python code from a different IDE. - -5. For documentation on the Declarative Automation Bundles format used - for this project, and for CI/CD configuration, see - https://docs.databricks.com/dev-tools/bundles/index.html. - -## Deploy and run jobs - -1. Create a new virtual environment and install the required dependencies: - ``` - $ uv sync - ``` - -2. To deploy the bundle to the development target: - ``` - $ databricks bundle deploy --target dev - ``` - - *(Note that "dev" is the default target, so the `--target` parameter is optional here.)* - - This deploys everything that's defined for this project. - For example, the default template would deploy a job called - `[dev yourname] my_jobs_as_code_job` to your workspace. - You can find that job by opening your workspace and clicking on **Workflows**. - -3. Similarly, to deploy a production copy, type: - ``` - $ databricks bundle deploy --target prod - ``` - - Note that the default job from the template has a schedule that runs every day - (defined in resources/my_jobs_as_code_job.py). The schedule - is paused when deploying in development mode (see [Databricks Asset Bundle deployment modes]( - https://docs.databricks.com/dev-tools/bundles/deployment-modes.html)). - -4. To run a job: - ``` - $ databricks bundle run - ``` diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml deleted file mode 100644 index b910ecd9131..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml +++ /dev/null @@ -1,48 +0,0 @@ -# This is a Databricks asset bundle definition for my_jobs_as_code. -# See https://docs.databricks.com/dev-tools/bundles/index.html for documentation. -bundle: - name: my_jobs_as_code - uuid: [UUID] - databricks_cli_version: ">= 0.248.0" - -python: - # Activate virtual environment before loading resources defined in Python. - # If disabled, defaults to using the Python interpreter available in the current shell. - venv_path: .venv - # Functions called to load resources defined in Python. See resources/__init__.py - resources: - - "resources:load_resources" - -artifacts: - default: - type: whl - path: . - # We use timestamp as Local version identifier (https://peps.python.org/pep-0440/#local-version-identifiers.) - # to ensure that changes to wheel package are picked up when used on all-purpose clusters - build: LOCAL_VERSION=$(date +%Y%m%d.%H%M%S) uv build - -include: - - resources/*.yml - -targets: - dev: - # The default target uses 'mode: development' to create a development copy. - # - Deployed resources get prefixed with '[dev my_user_name]' - # - Any job schedules and triggers are paused by default. - # See also https://docs.databricks.com/dev-tools/bundles/deployment-modes.html. - mode: development - default: true - workspace: - host: [DATABRICKS_URL] - - prod: - mode: production - workspace: - host: [DATABRICKS_URL] - # We explicitly specify /Workspace/Users/[USERNAME] to make sure we only have a single copy. - root_path: /Workspace/Users/[USERNAME]/.bundle/${bundle.name}/${bundle.target} - permissions: - - user_name: [USERNAME] - level: CAN_MANAGE - run_as: - user_name: [USERNAME] diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/fixtures/.gitkeep b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/fixtures/.gitkeep deleted file mode 100644 index fa25d2745e4..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/fixtures/.gitkeep +++ /dev/null @@ -1,22 +0,0 @@ -# Fixtures - -This folder is reserved for fixtures, such as CSV files. - -Below is an example of how to load fixtures as a data frame: - -``` -import pandas as pd -import os - -def get_absolute_path(*relative_parts): - if 'dbutils' in globals(): - base_dir = os.path.dirname(dbutils.notebook.entry_point.getDbutils().notebook().getContext().notebookPath().get()) # type: ignore - path = os.path.normpath(os.path.join(base_dir, *relative_parts)) - return path if path.startswith("/Workspace") else "/Workspace" + path - else: - return os.path.join(*relative_parts) - -csv_file = get_absolute_path("..", "fixtures", "mycsv.csv") -df = pd.read_csv(csv_file) -display(df) -``` diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/out.gitignore b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/out.gitignore deleted file mode 100644 index 0dab7f4995f..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/out.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.databricks/ -build/ -dist/ -__pycache__/ -*.egg-info -.venv/ -scratch/** -!scratch/README.md diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/pyproject.toml b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/pyproject.toml deleted file mode 100644 index 4478dace35b..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/pyproject.toml +++ /dev/null @@ -1,49 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" - -[project] -name = "my_jobs_as_code" -requires-python = ">=3.10" -description = "wheel file based on my_jobs_as_code" - -# Dependencies in case the output wheel file is used as a library dependency. -# For defining dependencies, when this package is used in Databricks, see: -# https://docs.databricks.com/dev-tools/bundles/library-dependencies.html -# -# Example: -# dependencies = [ -# "requests==x.y.z", -# ] -dependencies = [ -] - -# see setup.py -dynamic = ["version"] - -[project.entry-points.packages] -main = "my_jobs_as_code.main:main" - -[tool.setuptools.packages.find] -where = ["src"] - -[tool.uv] -## Dependencies for local development -dev-dependencies = [ - "databricks-bundles==x.y.z", - - ## Add code completion support for DLT - # "databricks-dlt", - - ## databricks-connect can be used to run parts of this project locally. - ## See https://docs.databricks.com/dev-tools/databricks-connect.html. - ## - ## Uncomment line below to install a version of db-connect that corresponds to - ## the Databricks Runtime version used for this project. - # "databricks-connect>=15.4,<15.5", -] - -override-dependencies = [ - # pyspark package conflicts with 'databricks-connect' - "pyspark; sys_platform == 'never'", -] diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/__init__.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/__init__.py deleted file mode 100644 index fbcb9dc5f0b..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from databricks.bundles.core import ( - Bundle, - Resources, - load_resources_from_current_package_module, -) - - -def load_resources(bundle: Bundle) -> Resources: - """ - 'load_resources' function is referenced in databricks.yml and is responsible for loading - bundle resources defined in Python code. This function is called by Databricks CLI during - bundle deployment. After deployment, this function is not used. - """ - - # the default implementation loads all Python files in 'resources' directory - return load_resources_from_current_package_module() diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_job.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_job.py deleted file mode 100644 index 2407a954620..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_job.py +++ /dev/null @@ -1,68 +0,0 @@ -from databricks.bundles.jobs import Job - -""" -The main job for my_jobs_as_code. -""" - - -my_jobs_as_code_job = Job.from_dict( - { - "name": "my_jobs_as_code_job", - "trigger": { - # Run this job every day, exactly one day from the last run; see https://docs.databricks.com/api/workspace/jobs/create#trigger - "periodic": { - "interval": 1, - "unit": "DAYS", - }, - }, - # "email_notifications": { - # "on_failure": [ - # "[USERNAME]", - # ], - # }, - "tasks": [ - { - "task_key": "notebook_task", - "job_cluster_key": "job_cluster", - "notebook_task": { - "notebook_path": "src/notebook.ipynb", - }, - }, - { - "task_key": "main_task", - "depends_on": [ - { - "task_key": "notebook_task", - }, - ], - "job_cluster_key": "job_cluster", - "python_wheel_task": { - "package_name": "my_jobs_as_code", - "entry_point": "main", - }, - "libraries": [ - # By default we just include the .whl file generated for the my_jobs_as_code package. - # See https://docs.databricks.com/dev-tools/bundles/library-dependencies.html - # for more information on how to add other libraries. - { - "whl": "dist/*.whl", - }, - ], - }, - ], - "job_clusters": [ - { - "job_cluster_key": "job_cluster", - "new_cluster": { - "spark_version": "15.4.x-scala2.12", - "node_type_id": "[NODE_TYPE_ID]", - "data_security_mode": "SINGLE_USER", - "autoscale": { - "min_workers": 1, - "max_workers": 4, - }, - }, - }, - ], - } -) diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_pipeline.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_pipeline.py deleted file mode 100644 index 9d83e573a90..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/resources/my_jobs_as_code_pipeline.py +++ /dev/null @@ -1,20 +0,0 @@ -from databricks.bundles.pipelines import Pipeline - -my_jobs_as_code_pipeline = Pipeline.from_dict( - { - "name": "my_jobs_as_code_pipeline", - "target": "my_jobs_as_code_${bundle.target}", - ## Specify the 'catalog' field to configure this pipeline to make use of Unity Catalog: - "catalog": "catalog_name", - "libraries": [ - { - "notebook": { - "path": "src/dlt_pipeline.ipynb", - }, - }, - ], - "configuration": { - "bundle.sourcePath": "${workspace.file_path}/src", - }, - } -) diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/scratch/README.md b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/scratch/README.md deleted file mode 100644 index e6cfb81b46f..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/scratch/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# scratch - -This folder is reserved for personal, exploratory notebooks. -By default these are not committed to Git, as 'scratch' is listed in .gitignore. diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/setup.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/setup.py deleted file mode 100644 index ba284ba828f..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/setup.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -setup.py configuration script describing how to build and package this project. - -This file is primarily used by the setuptools library and typically should not -be executed directly. See README.md for how to deploy, test, and run -the my_jobs_as_code project. -""" - -import os - -from setuptools import setup - -local_version = os.getenv("LOCAL_VERSION") -version = "0.0.1" - -setup( - version=f"{version}+{local_version}" if local_version else version, -) diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/dlt_pipeline.ipynb b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/dlt_pipeline.ipynb deleted file mode 100644 index d651c004222..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/dlt_pipeline.ipynb +++ /dev/null @@ -1,90 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "application/vnd.databricks.v1+cell": { - "cellMetadata": {}, - "inputWidgets": {}, - "nuid": "[UUID]", - "showTitle": false, - "title": "" - } - }, - "source": [ - "# DLT pipeline\n", - "\n", - "This Lakeflow Spark Declarative Pipeline definition is executed using a pipeline defined in resources/my_jobs_as_code.pipeline.yml." - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "application/vnd.databricks.v1+cell": { - "cellMetadata": {}, - "inputWidgets": {}, - "nuid": "[UUID]", - "showTitle": false, - "title": "" - } - }, - "outputs": [], - "source": [ - "# Import DLT and src/my_jobs_as_code\n", - "import dlt\n", - "import sys\n", - "\n", - "sys.path.append(spark.conf.get(\"bundle.sourcePath\", \".\"))\n", - "from pyspark.sql.functions import expr\n", - "from my_jobs_as_code import main" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "application/vnd.databricks.v1+cell": { - "cellMetadata": {}, - "inputWidgets": {}, - "nuid": "[UUID]", - "showTitle": false, - "title": "" - } - }, - "outputs": [], - "source": [ - "@dlt.view\n", - "def taxi_raw():\n", - " return main.get_taxis(spark)\n", - "\n", - "\n", - "@dlt.table\n", - "def filtered_taxis():\n", - " return dlt.read(\"taxi_raw\").filter(expr(\"fare_amount < 30\"))" - ] - } - ], - "metadata": { - "application/vnd.databricks.v1+notebook": { - "dashboards": [], - "language": "python", - "notebookMetadata": { - "pythonIndentUnit": 2 - }, - "notebookName": "dlt_pipeline", - "widgets": {} - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/my_jobs_as_code/main.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/my_jobs_as_code/main.py deleted file mode 100644 index 5ae344c7e27..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/my_jobs_as_code/main.py +++ /dev/null @@ -1,25 +0,0 @@ -from pyspark.sql import SparkSession, DataFrame - - -def get_taxis(spark: SparkSession) -> DataFrame: - return spark.read.table("samples.nyctaxi.trips") - - -# Create a new Databricks Connect session. If this fails, -# check that you have configured Databricks Connect correctly. -# See https://docs.databricks.com/dev-tools/databricks-connect.html. -def get_spark() -> SparkSession: - try: - from databricks.connect import DatabricksSession - - return DatabricksSession.builder.getOrCreate() - except ImportError: - return SparkSession.builder.getOrCreate() - - -def main(): - get_taxis(get_spark()).show(5) - - -if __name__ == "__main__": - main() diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/notebook.ipynb b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/notebook.ipynb deleted file mode 100644 index 227c7cc5586..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/src/notebook.ipynb +++ /dev/null @@ -1,75 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "application/vnd.databricks.v1+cell": { - "cellMetadata": {}, - "inputWidgets": {}, - "nuid": "[UUID]", - "showTitle": false, - "title": "" - } - }, - "source": [ - "# Default notebook\n", - "\n", - "This default notebook is executed using Databricks Workflows as defined in resources/my_jobs_as_code.job.yml." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 0, - "metadata": { - "application/vnd.databricks.v1+cell": { - "cellMetadata": { - "byteLimit": 2048000, - "rowLimit": 10000 - }, - "inputWidgets": {}, - "nuid": "[UUID]", - "showTitle": false, - "title": "" - } - }, - "outputs": [], - "source": [ - "from my_jobs_as_code import main\n", - "\n", - "main.get_taxis(spark).show(10)" - ] - } - ], - "metadata": { - "application/vnd.databricks.v1+notebook": { - "dashboards": [], - "language": "python", - "notebookMetadata": { - "pythonIndentUnit": 2 - }, - "notebookName": "notebook", - "widgets": {} - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "name": "python", - "version": "3.11.4" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/tests/main_test.py b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/tests/main_test.py deleted file mode 100644 index 13e100ee2e8..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/tests/main_test.py +++ /dev/null @@ -1,8 +0,0 @@ -from my_jobs_as_code.main import get_taxis, get_spark - -# running tests requires installing databricks-connect, e.g. by uncommenting it in pyproject.toml - - -def test_main(): - taxis = get_taxis(get_spark()) - assert taxis.count() > 5 diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/script b/acceptance/bundle/templates/experimental-jobs-as-code/script deleted file mode 100644 index 31fa7b07425..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/script +++ /dev/null @@ -1,19 +0,0 @@ -trace $CLI bundle init experimental-jobs-as-code --config-file ./input.json --output-dir output - -cd output/my_jobs_as_code - -# with -f we add pre-built wheel, in addition to vendored packages; -# if PyPi package is not yet published, it will be used instead. -# Note: -f overrides UV_FIND_LINKS, so we must pass vendored dir explicitly. -uv -q sync --no-index -f $VENDORED_PY_PACKAGES -f $(dirname $DATABRICKS_BUNDLES_WHEEL) - -trace $CLI bundle validate -t dev --output json | jq ".resources" - -uv build -q --no-index -trace unzip -Z1 dist/my_jobs_as_code-0.0.1-py3-none-any.whl - -rm -fr .venv resources/__pycache__ uv.lock src/my_jobs_as_code.egg-info dist - -# Do not affect this repository's git behaviour #2318 -mv .gitignore out.gitignore -rm .databricks/.gitignore diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/test.toml b/acceptance/bundle/templates/experimental-jobs-as-code/test.toml deleted file mode 100644 index 3b56f132b87..00000000000 --- a/acceptance/bundle/templates/experimental-jobs-as-code/test.toml +++ /dev/null @@ -1,9 +0,0 @@ -Ignore = [ - '.venv', -] -Timeout = '40s' -TimeoutWindows = '120s' - -[[Repls]] -Old = '"databricks-bundles==0.\d+.\d+"' -New = '"databricks-bundles==x.y.z"' diff --git a/acceptance/bundle/templates/lakeflow-pipelines/python/out.test.toml b/acceptance/bundle/templates/lakeflow-pipelines/python/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/lakeflow-pipelines/python/out.test.toml +++ b/acceptance/bundle/templates/lakeflow-pipelines/python/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/lakeflow-pipelines/sql/out.test.toml b/acceptance/bundle/templates/lakeflow-pipelines/sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/lakeflow-pipelines/sql/out.test.toml +++ b/acceptance/bundle/templates/lakeflow-pipelines/sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/pydabs/check-consistency/out.test.toml b/acceptance/bundle/templates/pydabs/check-consistency/out.test.toml index e3bb197e37a..88bd948e0a9 100644 --- a/acceptance/bundle/templates/pydabs/check-consistency/out.test.toml +++ b/acceptance/bundle/templates/pydabs/check-consistency/out.test.toml @@ -1,9 +1,7 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - INCLUDE_JOB = ["yes", "no"] - INCLUDE_PIPELINE = ["yes", "no"] - INCLUDE_PYTHON = ["yes", "no"] - SERVERLESS = ["yes", "no"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.INCLUDE_JOB = ["yes", "no"] +EnvMatrix.INCLUDE_PIPELINE = ["yes", "no"] +EnvMatrix.INCLUDE_PYTHON = ["yes", "no"] +EnvMatrix.SERVERLESS = ["yes", "no"] diff --git a/acceptance/bundle/templates/pydabs/check-formatting/out.test.toml b/acceptance/bundle/templates/pydabs/check-formatting/out.test.toml index e3bb197e37a..88bd948e0a9 100644 --- a/acceptance/bundle/templates/pydabs/check-formatting/out.test.toml +++ b/acceptance/bundle/templates/pydabs/check-formatting/out.test.toml @@ -1,9 +1,7 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - INCLUDE_JOB = ["yes", "no"] - INCLUDE_PIPELINE = ["yes", "no"] - INCLUDE_PYTHON = ["yes", "no"] - SERVERLESS = ["yes", "no"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.INCLUDE_JOB = ["yes", "no"] +EnvMatrix.INCLUDE_PIPELINE = ["yes", "no"] +EnvMatrix.INCLUDE_PYTHON = ["yes", "no"] +EnvMatrix.SERVERLESS = ["yes", "no"] diff --git a/acceptance/bundle/templates/pydabs/deploy-classic/out.test.toml b/acceptance/bundle/templates/pydabs/deploy-classic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/pydabs/deploy-classic/out.test.toml +++ b/acceptance/bundle/templates/pydabs/deploy-classic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/pydabs/init-classic/out.test.toml b/acceptance/bundle/templates/pydabs/init-classic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/pydabs/init-classic/out.test.toml +++ b/acceptance/bundle/templates/pydabs/init-classic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/telemetry/custom-template/out.test.toml b/acceptance/bundle/templates/telemetry/custom-template/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/telemetry/custom-template/out.test.toml +++ b/acceptance/bundle/templates/telemetry/custom-template/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/telemetry/dbt-sql/out.test.toml b/acceptance/bundle/templates/telemetry/dbt-sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/telemetry/dbt-sql/out.test.toml +++ b/acceptance/bundle/templates/telemetry/dbt-sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/telemetry/default-python/out.test.toml b/acceptance/bundle/templates/telemetry/default-python/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/telemetry/default-python/out.test.toml +++ b/acceptance/bundle/templates/telemetry/default-python/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/templates/telemetry/default-sql/out.test.toml b/acceptance/bundle/templates/telemetry/default-sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/templates/telemetry/default-sql/out.test.toml +++ b/acceptance/bundle/templates/telemetry/default-sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/trampoline/warning_message/out.test.toml b/acceptance/bundle/trampoline/warning_message/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/trampoline/warning_message/out.test.toml +++ b/acceptance/bundle/trampoline/warning_message/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/trampoline/warning_message_with_new_spark/out.test.toml b/acceptance/bundle/trampoline/warning_message_with_new_spark/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/trampoline/warning_message_with_new_spark/out.test.toml +++ b/acceptance/bundle/trampoline/warning_message_with_new_spark/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/trampoline/warning_message_with_old_spark/out.test.toml b/acceptance/bundle/trampoline/warning_message_with_old_spark/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/trampoline/warning_message_with_old_spark/out.test.toml +++ b/acceptance/bundle/trampoline/warning_message_with_old_spark/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/undefined_resources/out.test.toml b/acceptance/bundle/undefined_resources/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/undefined_resources/out.test.toml +++ b/acceptance/bundle/undefined_resources/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/upload/internal_server_error/out.test.toml b/acceptance/bundle/upload/internal_server_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/upload/internal_server_error/out.test.toml +++ b/acceptance/bundle/upload/internal_server_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/upload/timeout/out.test.toml b/acceptance/bundle/upload/timeout/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/upload/timeout/out.test.toml +++ b/acceptance/bundle/upload/timeout/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/upload/timeout/output.txt b/acceptance/bundle/upload/timeout/output.txt index 0c7c837994c..c314a247707 100644 --- a/acceptance/bundle/upload/timeout/output.txt +++ b/acceptance/bundle/upload/timeout/output.txt @@ -1,3 +1,3 @@ Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Error: Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Ffiles%2Ffile_to_upload.txt?overwrite=true": request timed out after 1m30s of inactivity +Error: Post "[DATABRICKS_URL]/api/2.0/workspace-files/import-file/Workspace%2FUsers%2F[USERNAME]%2F.bundle%2Ftest-bundle%2Fdefault%2Ffiles%2Ffile_to_upload.txt?overwrite=true": request timed out after 5s of inactivity diff --git a/acceptance/bundle/upload/timeout/test.toml b/acceptance/bundle/upload/timeout/test.toml index 343eac31942..de2d8ce015b 100644 --- a/acceptance/bundle/upload/timeout/test.toml +++ b/acceptance/bundle/upload/timeout/test.toml @@ -1,9 +1,8 @@ -Slow = true -Timeout = "3m" +[Env] +DATABRICKS_BUNDLE_HTTP_TIMEOUT_SECONDS = "5" [[Server]] -# Client single timeout is 90s, retry timeout is 30m, API delay is 2m and test timeout is 3m, so we should see test killed with timeout. -# Badness: actually what happens is CLI aborts after single attempt. -Delay = "2m" +# CLI aborts after a single attempt when the HTTP timeout fires. +Delay = "30s" Pattern = "POST /api/2.0/workspace-files/import-file/Workspace/Users/tester@databricks.com/.bundle/test-bundle/default/files/file_to_upload.txt" Response.StatusCode = 200 diff --git a/acceptance/bundle/user_agent/out.test.toml b/acceptance/bundle/user_agent/out.test.toml index 4cfe03e9f9d..be193812ec2 100644 --- a/acceptance/bundle/user_agent/out.test.toml +++ b/acceptance/bundle/user_agent/out.test.toml @@ -1,6 +1,4 @@ Local = true Cloud = false Phase = 1 - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/user_agent/output.txt b/acceptance/bundle/user_agent/output.txt index 664f8ded448..bf128624271 100644 --- a/acceptance/bundle/user_agent/output.txt +++ b/acceptance/bundle/user_agent/output.txt @@ -37,8 +37,12 @@ OK deploy.terraform /api/2.0/workspace/delete engine/terraform OK deploy.terraform /api/2.0/workspace/delete engine/terraform OK deploy.terraform /api/2.0/workspace/mkdirs engine/terraform MISS deploy.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' -MISS deploy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' -MISS deploy.terraform /api/2.1/unity-catalog/schemas 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS deploy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS deploy.terraform /api/2.1/unity-catalog/schemas 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' MISS destroy.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' @@ -63,9 +67,15 @@ OK destroy.terraform /api/2.0/workspace/get-status engine/terraform OK destroy.terraform /api/2.0/workspace-files/import-file/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deploy.lock engine/terraform OK destroy.terraform /api/2.0/workspace/delete engine/terraform MISS destroy.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' -MISS destroy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' -MISS destroy.terraform /api/2.1/unity-catalog/current-metastore-assignment 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' -MISS destroy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /api/2.1/unity-catalog/current-metastore-assignment 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' MISS plan.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' @@ -76,6 +86,8 @@ MISS plan.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks- MISS plan.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' OK plan.terraform /api/2.0/workspace/get-status engine/terraform MISS plan.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' +MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' MISS plan2.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' @@ -91,7 +103,9 @@ MISS plan2.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks OK plan2.terraform /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json engine/terraform OK plan2.terraform /api/2.0/workspace/get-status engine/terraform MISS plan2.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' -MISS plan2.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS plan2.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS plan2.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS plan2.terraform /.well-known/databricks-config 'databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' MISS run.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' @@ -105,12 +119,10 @@ MISS run.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks- MISS summary.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' -OK summary.direct /api/2.0/preview/scim/v2/Me engine/direct MISS summary.direct /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS summary.terraform /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' MISS summary.terraform /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none auth/pat' -OK summary.terraform /api/2.0/preview/scim/v2/Me engine/terraform MISS summary.terraform /.well-known/databricks-config 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS validate.direct /api/2.0/preview/scim/v2/Me 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat' MISS validate.direct /api/2.0/workspace/get-status 'cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_validate cmd-exec-id/[UUID] interactive/none auth/pat' diff --git a/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json index e0981c7f29c..435b188af3b 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json @@ -308,7 +308,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -317,7 +317,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "POST", @@ -327,3 +327,39 @@ "name": "myschema" } } +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} diff --git a/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json index 82065d0ca49..f8ab210ec72 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json @@ -136,7 +136,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "DELETE", @@ -148,7 +148,7 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -157,9 +157,63 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", "path": "/api/2.1/unity-catalog/schemas/mycatalog.myschema" } +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} diff --git a/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json index dcc358b33c4..11daf62e9ed 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json @@ -55,3 +55,21 @@ "method": "GET", "path": "/.well-known/databricks-config" } +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} diff --git a/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json index e1ecacb19b9..75f4620ef48 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json @@ -76,9 +76,27 @@ { "headers": { "User-Agent": [ - "databricks-tf-provider/1.111.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", "path": "/api/2.1/unity-catalog/schemas/mycatalog.myschema" } +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.113.0 databricks-sdk-go/[SDK_VERSION] go/1.24.0 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" + ] + }, + "method": "GET", + "path": "/.well-known/databricks-config" +} diff --git a/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json b/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json index c3017391f2f..3a3e2db9e9a 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json @@ -33,15 +33,6 @@ "return_export_info": "true" } } -{ - "headers": { - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/direct auth/pat" - ] - }, - "method": "GET", - "path": "/api/2.0/preview/scim/v2/Me" -} { "headers": { "User-Agent": [ diff --git a/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json index bf160a9744d..3a3e2db9e9a 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json @@ -33,15 +33,6 @@ "return_export_info": "true" } } -{ - "headers": { - "User-Agent": [ - "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_summary cmd-exec-id/[UUID] interactive/none engine/terraform auth/pat" - ] - }, - "method": "GET", - "path": "/api/2.0/preview/scim/v2/Me" -} { "headers": { "User-Agent": [ diff --git a/acceptance/bundle/user_agent/simple/out.test.toml b/acceptance/bundle/user_agent/simple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/user_agent/simple/out.test.toml +++ b/acceptance/bundle/user_agent/simple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/catalog_requires_direct_mode/out.test.toml b/acceptance/bundle/validate/catalog_requires_direct_mode/out.test.toml index 90061dedb10..65156e0457c 100644 --- a/acceptance/bundle/validate/catalog_requires_direct_mode/out.test.toml +++ b/acceptance/bundle/validate/catalog_requires_direct_mode/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/bundle/validate/dashboard_defaults/out.test.toml b/acceptance/bundle/validate/dashboard_defaults/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/dashboard_defaults/out.test.toml +++ b/acceptance/bundle/validate/dashboard_defaults/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/dashboard_required_name/out.test.toml b/acceptance/bundle/validate/dashboard_required_name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/dashboard_required_name/out.test.toml +++ b/acceptance/bundle/validate/dashboard_required_name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/dashboard_required_warehouse_id/out.test.toml b/acceptance/bundle/validate/dashboard_required_warehouse_id/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/dashboard_required_warehouse_id/out.test.toml +++ b/acceptance/bundle/validate/dashboard_required_warehouse_id/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/definitions_yaml_anchors/out.test.toml b/acceptance/bundle/validate/definitions_yaml_anchors/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/definitions_yaml_anchors/out.test.toml +++ b/acceptance/bundle/validate/definitions_yaml_anchors/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/duplicate_yaml_merge_key/databricks.yml b/acceptance/bundle/validate/duplicate_yaml_merge_key/databricks.yml new file mode 100644 index 00000000000..630455bc24f --- /dev/null +++ b/acceptance/bundle/validate/duplicate_yaml_merge_key/databricks.yml @@ -0,0 +1,20 @@ +bundle: + name: test-bundle + +definitions: + cluster1: &cluster1 + num_workers: 1 + cluster2: &cluster2 + spark_version: "13.3.x-scala2.12" + +resources: + jobs: + my_job: + name: "test job" + tasks: + - task_key: "main" + new_cluster: + <<: *cluster1 + <<: *cluster2 + notebook_task: + notebook_path: "/notebook" diff --git a/acceptance/bundle/validate/duplicate_yaml_merge_key/out.test.toml b/acceptance/bundle/validate/duplicate_yaml_merge_key/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/validate/duplicate_yaml_merge_key/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/duplicate_yaml_merge_key/output.txt b/acceptance/bundle/validate/duplicate_yaml_merge_key/output.txt new file mode 100644 index 00000000000..420ad818626 --- /dev/null +++ b/acceptance/bundle/validate/duplicate_yaml_merge_key/output.txt @@ -0,0 +1,20 @@ + +>>> [CLI] bundle validate +Error: duplicate YAML merge key ('<<') is not allowed; to merge multiple maps, use a sequence: '<<: [*anchor1, *anchor2]' + in databricks.yml:18:13 + + +Found 1 error + +>>> [CLI] bundle validate +Name: test-bundle +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default + +Validation OK! +{ + "num_workers": 1, + "spark_version": "13.3.x-scala2.12" +} diff --git a/acceptance/bundle/validate/duplicate_yaml_merge_key/script b/acceptance/bundle/validate/duplicate_yaml_merge_key/script new file mode 100644 index 00000000000..472434d5d5f --- /dev/null +++ b/acceptance/bundle/validate/duplicate_yaml_merge_key/script @@ -0,0 +1,8 @@ +musterr trace $CLI bundle validate + +update_file.py databricks.yml " <<: *cluster1 + <<: *cluster2" " <<: [*cluster1, *cluster2]" + +trace $CLI bundle validate + +$CLI bundle validate -o json | jq '.resources.jobs.my_job.tasks[0].new_cluster' diff --git a/acceptance/bundle/validate/empty_resources/empty_def/out.test.toml b/acceptance/bundle/validate/empty_resources/empty_def/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/empty_resources/empty_def/out.test.toml +++ b/acceptance/bundle/validate/empty_resources/empty_def/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/empty_resources/empty_dict/out.test.toml b/acceptance/bundle/validate/empty_resources/empty_dict/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/empty_resources/empty_dict/out.test.toml +++ b/acceptance/bundle/validate/empty_resources/empty_dict/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/empty_resources/null/out.test.toml b/acceptance/bundle/validate/empty_resources/null/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/empty_resources/null/out.test.toml +++ b/acceptance/bundle/validate/empty_resources/null/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/empty_resources/with_grants/out.test.toml b/acceptance/bundle/validate/empty_resources/with_grants/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/empty_resources/with_grants/out.test.toml +++ b/acceptance/bundle/validate/empty_resources/with_grants/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/empty_resources/with_permissions/out.test.toml b/acceptance/bundle/validate/empty_resources/with_permissions/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/empty_resources/with_permissions/out.test.toml +++ b/acceptance/bundle/validate/empty_resources/with_permissions/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/engine-config-valid/out.test.toml b/acceptance/bundle/validate/engine-config-valid/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/engine-config-valid/out.test.toml +++ b/acceptance/bundle/validate/engine-config-valid/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/enum/out.test.toml b/acceptance/bundle/validate/enum/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/enum/out.test.toml +++ b/acceptance/bundle/validate/enum/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/enum_resource_refs/out.test.toml b/acceptance/bundle/validate/enum_resource_refs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/enum_resource_refs/out.test.toml +++ b/acceptance/bundle/validate/enum_resource_refs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/include_locations/out.test.toml b/acceptance/bundle/validate/include_locations/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/include_locations/out.test.toml +++ b/acceptance/bundle/validate/include_locations/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/invalid-engine-bundle/out.test.toml b/acceptance/bundle/validate/invalid-engine-bundle/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/invalid-engine-bundle/out.test.toml +++ b/acceptance/bundle/validate/invalid-engine-bundle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/invalid-engine-target/out.test.toml b/acceptance/bundle/validate/invalid-engine-target/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/invalid-engine-target/out.test.toml +++ b/acceptance/bundle/validate/invalid-engine-target/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/job-references/out.test.toml b/acceptance/bundle/validate/job-references/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/job-references/out.test.toml +++ b/acceptance/bundle/validate/job-references/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/model_serving_both_fields_error/out.test.toml b/acceptance/bundle/validate/model_serving_both_fields_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/model_serving_both_fields_error/out.test.toml +++ b/acceptance/bundle/validate/model_serving_both_fields_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/model_serving_conversion/out.test.toml b/acceptance/bundle/validate/model_serving_conversion/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/model_serving_conversion/out.test.toml +++ b/acceptance/bundle/validate/model_serving_conversion/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/models/missing_name/out.test.toml b/acceptance/bundle/validate/models/missing_name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/models/missing_name/out.test.toml +++ b/acceptance/bundle/validate/models/missing_name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/models/user_id/out.test.toml b/acceptance/bundle/validate/models/user_id/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/models/user_id/out.test.toml +++ b/acceptance/bundle/validate/models/user_id/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/no_dashboard_etag/out.test.toml b/acceptance/bundle/validate/no_dashboard_etag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/no_dashboard_etag/out.test.toml +++ b/acceptance/bundle/validate/no_dashboard_etag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/permissions/out.test.toml b/acceptance/bundle/validate/permissions/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/permissions/out.test.toml +++ b/acceptance/bundle/validate/permissions/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/presets_max_concurrent_runs/out.test.toml b/acceptance/bundle/validate/presets_max_concurrent_runs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/presets_max_concurrent_runs/out.test.toml +++ b/acceptance/bundle/validate/presets_max_concurrent_runs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/presets_name_prefix/out.test.toml b/acceptance/bundle/validate/presets_name_prefix/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/presets_name_prefix/out.test.toml +++ b/acceptance/bundle/validate/presets_name_prefix/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/presets_name_prefix_dev/out.test.toml b/acceptance/bundle/validate/presets_name_prefix_dev/out.test.toml index 54146af5645..e90b6d5d1ba 100644 --- a/acceptance/bundle/validate/presets_name_prefix_dev/out.test.toml +++ b/acceptance/bundle/validate/presets_name_prefix_dev/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/validate/presets_tags/out.test.toml b/acceptance/bundle/validate/presets_tags/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/presets_tags/out.test.toml +++ b/acceptance/bundle/validate/presets_tags/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/required/out.test.toml b/acceptance/bundle/validate/required/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/required/out.test.toml +++ b/acceptance/bundle/validate/required/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/strict/out.test.toml b/acceptance/bundle/validate/strict/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/strict/out.test.toml +++ b/acceptance/bundle/validate/strict/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/sync_patterns/out.test.toml b/acceptance/bundle/validate/sync_patterns/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/sync_patterns/out.test.toml +++ b/acceptance/bundle/validate/sync_patterns/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/var_in_bundle_name/out.test.toml b/acceptance/bundle/validate/var_in_bundle_name/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/var_in_bundle_name/out.test.toml +++ b/acceptance/bundle/validate/var_in_bundle_name/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/validate/volume_defaults/out.test.toml b/acceptance/bundle/validate/volume_defaults/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/validate/volume_defaults/out.test.toml +++ b/acceptance/bundle/validate/volume_defaults/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/arg-repeat/out.test.toml b/acceptance/bundle/variables/arg-repeat/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/arg-repeat/out.test.toml +++ b/acceptance/bundle/variables/arg-repeat/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-cross-ref/out.test.toml b/acceptance/bundle/variables/complex-cross-ref/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-cross-ref/out.test.toml +++ b/acceptance/bundle/variables/complex-cross-ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-cycle-self/out.test.toml b/acceptance/bundle/variables/complex-cycle-self/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-cycle-self/out.test.toml +++ b/acceptance/bundle/variables/complex-cycle-self/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-cycle/out.test.toml b/acceptance/bundle/variables/complex-cycle/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-cycle/out.test.toml +++ b/acceptance/bundle/variables/complex-cycle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-simple/out.test.toml b/acceptance/bundle/variables/complex-simple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-simple/out.test.toml +++ b/acceptance/bundle/variables/complex-simple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-transitive-deep/out.test.toml b/acceptance/bundle/variables/complex-transitive-deep/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-transitive-deep/out.test.toml +++ b/acceptance/bundle/variables/complex-transitive-deep/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-transitive-deeper/out.test.toml b/acceptance/bundle/variables/complex-transitive-deeper/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-transitive-deeper/out.test.toml +++ b/acceptance/bundle/variables/complex-transitive-deeper/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-transitive/out.test.toml b/acceptance/bundle/variables/complex-transitive/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-transitive/out.test.toml +++ b/acceptance/bundle/variables/complex-transitive/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-with-var-reference/out.test.toml b/acceptance/bundle/variables/complex-with-var-reference/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-with-var-reference/out.test.toml +++ b/acceptance/bundle/variables/complex-with-var-reference/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex-within-complex/out.test.toml b/acceptance/bundle/variables/complex-within-complex/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex-within-complex/out.test.toml +++ b/acceptance/bundle/variables/complex-within-complex/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex/out.test.toml b/acceptance/bundle/variables/complex/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex/out.test.toml +++ b/acceptance/bundle/variables/complex/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/complex_multiple_files/out.test.toml b/acceptance/bundle/variables/complex_multiple_files/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/complex_multiple_files/out.test.toml +++ b/acceptance/bundle/variables/complex_multiple_files/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/cycle/out.test.toml b/acceptance/bundle/variables/cycle/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/cycle/out.test.toml +++ b/acceptance/bundle/variables/cycle/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/double_underscore/out.test.toml b/acceptance/bundle/variables/double_underscore/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/double_underscore/out.test.toml +++ b/acceptance/bundle/variables/double_underscore/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/empty/out.test.toml b/acceptance/bundle/variables/empty/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/empty/out.test.toml +++ b/acceptance/bundle/variables/empty/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/env_overrides/out.test.toml b/acceptance/bundle/variables/env_overrides/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/env_overrides/out.test.toml +++ b/acceptance/bundle/variables/env_overrides/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/file-defaults/out.test.toml b/acceptance/bundle/variables/file-defaults/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/file-defaults/out.test.toml +++ b/acceptance/bundle/variables/file-defaults/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/git-branch/out.test.toml b/acceptance/bundle/variables/git-branch/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/git-branch/out.test.toml +++ b/acceptance/bundle/variables/git-branch/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/host/out.test.toml b/acceptance/bundle/variables/host/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/host/out.test.toml +++ b/acceptance/bundle/variables/host/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/int/out.test.toml b/acceptance/bundle/variables/int/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/int/out.test.toml +++ b/acceptance/bundle/variables/int/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/issue_2436/out.test.toml b/acceptance/bundle/variables/issue_2436/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/issue_2436/out.test.toml +++ b/acceptance/bundle/variables/issue_2436/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/issue_3039_lookup_with_ref/out.test.toml b/acceptance/bundle/variables/issue_3039_lookup_with_ref/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/issue_3039_lookup_with_ref/out.test.toml +++ b/acceptance/bundle/variables/issue_3039_lookup_with_ref/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/lookup/out.test.toml b/acceptance/bundle/variables/lookup/out.test.toml index 01ed6822af8..bbc7fcfd1bd 100644 --- a/acceptance/bundle/variables/lookup/out.test.toml +++ b/acceptance/bundle/variables/lookup/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/prepend-workspace-var/out.test.toml b/acceptance/bundle/variables/prepend-workspace-var/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/prepend-workspace-var/out.test.toml +++ b/acceptance/bundle/variables/prepend-workspace-var/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-builtin/out.test.toml b/acceptance/bundle/variables/resolve-builtin/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-builtin/out.test.toml +++ b/acceptance/bundle/variables/resolve-builtin/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-empty/out.test.toml b/acceptance/bundle/variables/resolve-empty/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-empty/out.test.toml +++ b/acceptance/bundle/variables/resolve-empty/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-field-within-complex/out.test.toml b/acceptance/bundle/variables/resolve-field-within-complex/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-field-within-complex/out.test.toml +++ b/acceptance/bundle/variables/resolve-field-within-complex/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-nonstrings/out.test.toml b/acceptance/bundle/variables/resolve-nonstrings/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-nonstrings/out.test.toml +++ b/acceptance/bundle/variables/resolve-nonstrings/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-resources-fields/out.test.toml b/acceptance/bundle/variables/resolve-resources-fields/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-resources-fields/out.test.toml +++ b/acceptance/bundle/variables/resolve-resources-fields/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/resolve-vars-in-root-path/out.test.toml b/acceptance/bundle/variables/resolve-vars-in-root-path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/resolve-vars-in-root-path/out.test.toml +++ b/acceptance/bundle/variables/resolve-vars-in-root-path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/vanilla/out.test.toml b/acceptance/bundle/variables/vanilla/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/vanilla/out.test.toml +++ b/acceptance/bundle/variables/vanilla/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/var_in_var/out.test.toml b/acceptance/bundle/variables/var_in_var/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/var_in_var/out.test.toml +++ b/acceptance/bundle/variables/var_in_var/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/variable_in_resource_key/databricks.yml b/acceptance/bundle/variables/variable_in_resource_key/databricks.yml new file mode 100644 index 00000000000..f6570e1b04a --- /dev/null +++ b/acceptance/bundle/variables/variable_in_resource_key/databricks.yml @@ -0,0 +1,21 @@ +bundle: + name: variable-in-resource-key + +variables: + env: + description: The target environment + default: dev + +resources: + jobs: + ${var.env}_job: + name: my-job + +targets: + dev: + default: true + resources: + jobs: + ${var.env}_job_2: + name: my-job + ${var.env}: my-job-description diff --git a/acceptance/bundle/variables/variable_in_resource_key/out.test.toml b/acceptance/bundle/variables/variable_in_resource_key/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/bundle/variables/variable_in_resource_key/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/variable_in_resource_key/output.txt b/acceptance/bundle/variables/variable_in_resource_key/output.txt new file mode 100644 index 00000000000..6fc17dc855d --- /dev/null +++ b/acceptance/bundle/variables/variable_in_resource_key/output.txt @@ -0,0 +1,14 @@ +Warning: unknown field: ${var.env} + at targets.dev.resources.jobs.${var.env}_job_2 + in databricks.yml:21:11 + +Error: resource key "${var.env}_job" must not contain variable references + at resources.jobs.${var.env}_job + in databricks.yml:12:7 + +Error: resource key "${var.env}_job_2" must not contain variable references + at targets.dev.resources.jobs.${var.env}_job_2 + in databricks.yml:20:11 + + +Exit code: 1 diff --git a/acceptance/bundle/variables/variable_in_resource_key/script b/acceptance/bundle/variables/variable_in_resource_key/script new file mode 100644 index 00000000000..b260e836a71 --- /dev/null +++ b/acceptance/bundle/variables/variable_in_resource_key/script @@ -0,0 +1 @@ +$CLI bundle plan diff --git a/acceptance/bundle/variables/variable_overrides_in_target/out.test.toml b/acceptance/bundle/variables/variable_overrides_in_target/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/variable_overrides_in_target/out.test.toml +++ b/acceptance/bundle/variables/variable_overrides_in_target/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/variables/without_definition/out.test.toml b/acceptance/bundle/variables/without_definition/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/variables/without_definition/out.test.toml +++ b/acceptance/bundle/variables/without_definition/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/volume_path/invalid_file/out.test.toml b/acceptance/bundle/volume_path/invalid_file/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/volume_path/invalid_file/out.test.toml +++ b/acceptance/bundle/volume_path/invalid_file/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/volume_path/invalid_resource/out.test.toml b/acceptance/bundle/volume_path/invalid_resource/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/volume_path/invalid_resource/out.test.toml +++ b/acceptance/bundle/volume_path/invalid_resource/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/volume_path/invalid_root/out.test.toml b/acceptance/bundle/volume_path/invalid_root/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/volume_path/invalid_root/out.test.toml +++ b/acceptance/bundle/volume_path/invalid_root/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/volume_path/invalid_state/out.test.toml b/acceptance/bundle/volume_path/invalid_state/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/volume_path/invalid_state/out.test.toml +++ b/acceptance/bundle/volume_path/invalid_state/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/volume_path/valid/out.test.toml b/acceptance/bundle/volume_path/valid/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/bundle/volume_path/valid/out.test.toml +++ b/acceptance/bundle/volume_path/valid/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cache/clear/out.test.toml b/acceptance/cache/clear/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cache/clear/out.test.toml +++ b/acceptance/cache/clear/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cache/clear/output.txt b/acceptance/cache/clear/output.txt index bba37b6ccb0..2d46b4c9f23 100644 --- a/acceptance/cache/clear/output.txt +++ b/acceptance/cache/clear/output.txt @@ -1,19 +1,25 @@ === First call in a session is expected to be a cache miss: [DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] -[DEBUG_TIMESTAMP] Debug: [Local Cache] failed to stat cache file: (redacted) +[DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing +[DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result +[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] [DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing [DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result === Second call in a session is expected to be a cache hit [DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] [DEBUG_TIMESTAMP] Debug: [Local Cache] cache hit +[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] +[DEBUG_TIMESTAMP] Debug: [Local Cache] cache hit >>> [CLI] cache clear Cache cleared successfully from [TEST_TMP_DIR]/.cache === First call after a clear is expected to be a cache miss: [DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] -[DEBUG_TIMESTAMP] Debug: [Local Cache] failed to stat cache file: (redacted) +[DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing +[DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result +[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] [DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing [DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result diff --git a/acceptance/cache/simple/out.test.toml b/acceptance/cache/simple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cache/simple/out.test.toml +++ b/acceptance/cache/simple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cache/simple/output.txt b/acceptance/cache/simple/output.txt index 093900b94b7..2206ffdbc71 100644 --- a/acceptance/cache/simple/output.txt +++ b/acceptance/cache/simple/output.txt @@ -1,13 +1,17 @@ === First call in a session is expected to be a cache miss: [DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] -[DEBUG_TIMESTAMP] Debug: [Local Cache] failed to stat cache file: (redacted) +[DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing +[DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result +[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] [DEBUG_TIMESTAMP] Debug: [Local Cache] cache miss, computing [DEBUG_TIMESTAMP] Debug: [Local Cache] computed and stored result === Second call in a session is expected to be a cache hit [DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] [DEBUG_TIMESTAMP] Debug: [Local Cache] cache hit +[DEBUG_TIMESTAMP] Debug: [Local Cache] using cache key: [SHA256_HASH] +[DEBUG_TIMESTAMP] Debug: [Local Cache] cache hit === Bundle deploy should send telemetry values diff --git a/acceptance/cmd/account/account-help/out.test.toml b/acceptance/cmd/account/account-help/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/account/account-help/out.test.toml +++ b/acceptance/cmd/account/account-help/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/account-flag/out.test.toml b/acceptance/cmd/api/account-flag/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/account-flag/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/account-flag/output.txt b/acceptance/cmd/api/account-flag/output.txt new file mode 100644 index 00000000000..c165bf2af88 --- /dev/null +++ b/acceptance/cmd/api/account-flag/output.txt @@ -0,0 +1,15 @@ +{} + +>>> print_requests.py --get //api/2.0/clusters/list +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/clusters/list" +} diff --git a/acceptance/cmd/api/account-flag/script b/acceptance/cmd/api/account-flag/script new file mode 100644 index 00000000000..de2d4de92b7 --- /dev/null +++ b/acceptance/cmd/api/account-flag/script @@ -0,0 +1,2 @@ +MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --account +trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Org-Id" diff --git a/acceptance/cmd/api/account-path/out.test.toml b/acceptance/cmd/api/account-path/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/account-path/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/account-path/output.txt b/acceptance/cmd/api/account-path/output.txt new file mode 100644 index 00000000000..1afd36c01d3 --- /dev/null +++ b/acceptance/cmd/api/account-path/output.txt @@ -0,0 +1,15 @@ +{} + +>>> print_requests.py --get //api/2.0/accounts/abc-123/network-policies +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/accounts/abc-123/network-policies" +} diff --git a/acceptance/cmd/api/account-path/script b/acceptance/cmd/api/account-path/script new file mode 100644 index 00000000000..6cc97637695 --- /dev/null +++ b/acceptance/cmd/api/account-path/script @@ -0,0 +1,2 @@ +MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/accounts/abc-123/network-policies +trace print_requests.py --get //api/2.0/accounts/abc-123/network-policies | contains.py "!X-Databricks-Org-Id" diff --git a/acceptance/cmd/api/test.toml b/acceptance/cmd/api/test.toml new file mode 100644 index 00000000000..11d83c3f486 --- /dev/null +++ b/acceptance/cmd/api/test.toml @@ -0,0 +1,40 @@ +RecordRequests = true +IncludeRequestHeaders = ["Authorization", "User-Agent", "X-Databricks-Org-Id"] + +# Normalize OS-dependent and CI-only User-Agent segments so the recorded +# requests are stable across local macOS/Linux runs and GitHub Actions. +[[Repls]] +Old = '(linux|darwin|windows)' +New = '[OS]' + +[[Repls]] +Old = " cicd/[A-Za-z0-9.-]+" +New = "" + +[[Repls]] +Old = " upstream/[A-Za-z0-9.-]+" +New = "" + +[[Repls]] +Old = " upstream-version/[A-Za-z0-9.-]+" +New = "" + +# Common stubs for paths used across variants. Each returns an empty JSON +# object; the variants assert on the *recorded request* (out.requests.txt), +# not on the response body, so any well-formed JSON is fine. + +[[Server]] +Pattern = "GET /api/2.0/clusters/list" +Response.Body = '{}' + +[[Server]] +Pattern = "GET /api/2.0/accounts/abc-123/network-policies" +Response.Body = '{}' + +[[Server]] +Pattern = "GET /api/2.0/accounts/abc-123/oauth2/published-app-integrations" +Response.Body = '{}' + +[[Server]] +Pattern = "GET /api/2.0/preview/accounts/access-control/rule-sets" +Response.Body = '{}' diff --git a/acceptance/cmd/api/workspace-id-flag/out.test.toml b/acceptance/cmd/api/workspace-id-flag/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-flag/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/workspace-id-flag/output.txt b/acceptance/cmd/api/workspace-id-flag/output.txt new file mode 100644 index 00000000000..5ff264fa554 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-flag/output.txt @@ -0,0 +1,18 @@ +{} + +>>> print_requests.py --get //api/2.0/clusters/list +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ], + "X-Databricks-Org-Id": [ + "999" + ] + }, + "method": "GET", + "path": "/api/2.0/clusters/list" +} diff --git a/acceptance/cmd/api/workspace-id-flag/script b/acceptance/cmd/api/workspace-id-flag/script new file mode 100644 index 00000000000..83664ba8662 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-flag/script @@ -0,0 +1,2 @@ +MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --workspace-id 999 +trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id" "999" diff --git a/acceptance/cmd/api/workspace-id-from-query/out.test.toml b/acceptance/cmd/api/workspace-id-from-query/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-from-query/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/workspace-id-from-query/output.txt b/acceptance/cmd/api/workspace-id-from-query/output.txt new file mode 100644 index 00000000000..7a72f8de473 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-from-query/output.txt @@ -0,0 +1,21 @@ +{} + +>>> print_requests.py --get //api/2.0/clusters/list +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ], + "X-Databricks-Org-Id": [ + "999" + ] + }, + "method": "GET", + "path": "/api/2.0/clusters/list", + "q": { + "o": "999" + } +} diff --git a/acceptance/cmd/api/workspace-id-from-query/script b/acceptance/cmd/api/workspace-id-from-query/script new file mode 100644 index 00000000000..fd3fa0e151b --- /dev/null +++ b/acceptance/cmd/api/workspace-id-from-query/script @@ -0,0 +1,2 @@ +MSYS_NO_PATHCONV=1 $CLI api get "/api/2.0/clusters/list?o=999" +trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id" "999" diff --git a/acceptance/cmd/api/workspace-id-none/out.test.toml b/acceptance/cmd/api/workspace-id-none/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-none/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/workspace-id-none/output.txt b/acceptance/cmd/api/workspace-id-none/output.txt new file mode 100644 index 00000000000..c165bf2af88 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-none/output.txt @@ -0,0 +1,15 @@ +{} + +>>> print_requests.py --get //api/2.0/clusters/list +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/clusters/list" +} diff --git a/acceptance/cmd/api/workspace-id-none/script b/acceptance/cmd/api/workspace-id-none/script new file mode 100644 index 00000000000..4ecd00c6768 --- /dev/null +++ b/acceptance/cmd/api/workspace-id-none/script @@ -0,0 +1,13 @@ +# Profile with workspace_id = none overrides the host-metadata back-fill. +# The CLI must strip the sentinel before the header decision; the recorded +# request should not carry the routing identifier. +sethome "./home" +cat > "./home/.databrickscfg" <>> print_requests.py --get //api/2.0/clusters/list +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" + ] + }, + "method": "GET", + "path": "/api/2.0/clusters/list" +} diff --git a/acceptance/cmd/api/workspace-path/script b/acceptance/cmd/api/workspace-path/script new file mode 100644 index 00000000000..4e5bb35c4be --- /dev/null +++ b/acceptance/cmd/api/workspace-path/script @@ -0,0 +1,2 @@ +MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list +trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id" diff --git a/acceptance/cmd/api/workspace-proxy-regression/out.test.toml b/acceptance/cmd/api/workspace-proxy-regression/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/workspace-proxy-regression/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/workspace-proxy-regression/output.txt b/acceptance/cmd/api/workspace-proxy-regression/output.txt new file mode 100644 index 00000000000..c98486a15e1 --- /dev/null +++ b/acceptance/cmd/api/workspace-proxy-regression/output.txt @@ -0,0 +1,18 @@ +{} + +>>> print_requests.py --get //api/2.0/preview/accounts/access-control/rule-sets +{ + "headers": { + "Authorization": [ + "Bearer [DATABRICKS_TOKEN]" + ], + "User-Agent": [ + "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/accounts/access-control/rule-sets" +} diff --git a/acceptance/cmd/api/workspace-proxy-regression/script b/acceptance/cmd/api/workspace-proxy-regression/script new file mode 100644 index 00000000000..39ab661f4f1 --- /dev/null +++ b/acceptance/cmd/api/workspace-proxy-regression/script @@ -0,0 +1,5 @@ +# Workspace-routed proxy under accounts/. The deny-list must keep this from +# being misclassified as account-scope, so the routing identifier should be +# present on the recorded request. +MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/preview/accounts/access-control/rule-sets +trace print_requests.py --get //api/2.0/preview/accounts/access-control/rule-sets | contains.py "X-Databricks-Org-Id" diff --git a/acceptance/cmd/auth/describe/default-profile/out.test.toml b/acceptance/cmd/auth/describe/default-profile/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/auth/describe/default-profile/out.test.toml +++ b/acceptance/cmd/auth/describe/default-profile/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml b/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/describe/u2m-json-output/output.txt b/acceptance/cmd/auth/describe/u2m-json-output/output.txt new file mode 100644 index 00000000000..7e2ac070cbc --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/output.txt @@ -0,0 +1,8 @@ + +>>> [CLI] auth describe --profile u2m-profile --output json +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +{ + "mode": "plaintext", + "location": "~/.databricks/token-cache.json", + "source": "default" +} diff --git a/acceptance/cmd/auth/describe/u2m-json-output/script b/acceptance/cmd/auth/describe/u2m-json-output/script new file mode 100644 index 00000000000..668d2374496 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/script @@ -0,0 +1,16 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" <>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from auth_storage in [__settings__] section of home/.databrickscfg) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-config/script b/acceptance/cmd/auth/describe/u2m-plaintext-config/script new file mode 100644 index 00000000000..1cf6d3267d5 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-config/script @@ -0,0 +1,17 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" <>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from default) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-default/script b/acceptance/cmd/auth/describe/u2m-plaintext-default/script new file mode 100644 index 00000000000..d0b1ce40020 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-default/script @@ -0,0 +1,14 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" <>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from DATABRICKS_AUTH_STORAGE environment variable) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-env/script b/acceptance/cmd/auth/describe/u2m-plaintext-env/script new file mode 100644 index 00000000000..21bfdf231f1 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-env/script @@ -0,0 +1,15 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE + +export DATABRICKS_AUTH_STORAGE=plaintext + +cat > "./home/.databrickscfg" <>> [CLI] auth profiles --output json +{ + "profiles": [ + { + "name": "spog-account", + "host": "[DATABRICKS_URL]", + "account_id": "spog-acct-123", + "workspace_id": "none", + "cloud": "aws", + "auth_type": "pat", + "valid": true + } + ] +} diff --git a/acceptance/cmd/auth/profiles/spog-account/script b/acceptance/cmd/auth/profiles/spog-account/script new file mode 100644 index 00000000000..64285ad0ec4 --- /dev/null +++ b/acceptance/cmd/auth/profiles/spog-account/script @@ -0,0 +1,15 @@ +sethome "./home" + +# Create a SPOG account profile: non-accounts.* host with account_id, no workspace_id. +# Before the fix, this was misclassified as WorkspaceConfig and validated with +# CurrentUser.Me, which fails on account-scoped SPOG hosts. +cat > "./home/.databrickscfg" <>> [CLI] auth logout --profile dev --auto-approve +Logged out of profile "dev". Use --delete to also remove it from the config file. + +=== Token cache keys after logout (should be empty) +[] diff --git a/acceptance/cmd/auth/storage-modes/env-overrides-config/script b/acceptance/cmd/auth/storage-modes/env-overrides-config/script new file mode 100644 index 00000000000..a698ab96b2e --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/env-overrides-config/script @@ -0,0 +1,35 @@ +export DATABRICKS_AUTH_STORAGE=plaintext + +cat > "./home/.databrickscfg" < "./home/.databricks/token-cache.json" <>> [CLI] auth token --profile nonexistent +Error: auth_storage: unknown storage mode "bogus" (want plaintext or secure) + +Exit code: 1 diff --git a/acceptance/cmd/auth/storage-modes/invalid-config/script b/acceptance/cmd/auth/storage-modes/invalid-config/script new file mode 100644 index 00000000000..c609a71b641 --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/invalid-config/script @@ -0,0 +1,8 @@ +cat > "./home/.databrickscfg" <>> [CLI] auth token --profile nonexistent +Error: DATABRICKS_AUTH_STORAGE: unknown storage mode "bogus" (want plaintext or secure) + +Exit code: 1 diff --git a/acceptance/cmd/auth/storage-modes/invalid-env/script b/acceptance/cmd/auth/storage-modes/invalid-env/script new file mode 100644 index 00000000000..8b00e9d54da --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/invalid-env/script @@ -0,0 +1,6 @@ +export DATABRICKS_AUTH_STORAGE=bogus + +# Any auth command that resolves the storage mode must surface the error. +# auth token is the smallest reproducer because it doesn't perform any +# network I/O before resolving the mode. +trace $CLI auth token --profile nonexistent diff --git a/acceptance/cmd/auth/storage-modes/plaintext-env-default/out.test.toml b/acceptance/cmd/auth/storage-modes/plaintext-env-default/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/plaintext-env-default/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/storage-modes/plaintext-env-default/output.txt b/acceptance/cmd/auth/storage-modes/plaintext-env-default/output.txt new file mode 100644 index 00000000000..8994c41ebbb --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/plaintext-env-default/output.txt @@ -0,0 +1,11 @@ + +=== Token cache keys before logout +[ + "dev" +] + +>>> [CLI] auth logout --profile dev --auto-approve +Logged out of profile "dev". Use --delete to also remove it from the config file. + +=== Token cache keys after logout (should be empty) +[] diff --git a/acceptance/cmd/auth/storage-modes/plaintext-env-default/script b/acceptance/cmd/auth/storage-modes/plaintext-env-default/script new file mode 100644 index 00000000000..75051cb20f7 --- /dev/null +++ b/acceptance/cmd/auth/storage-modes/plaintext-env-default/script @@ -0,0 +1,29 @@ +export DATABRICKS_AUTH_STORAGE=plaintext + +cat > "./home/.databrickscfg" < "./home/.databricks/token-cache.json" <>> [CLI] apps create --json @input.json { + "active_deployment": { + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-name", + "status": { + "message": "Deployment succeeded", + "state": "SUCCEEDED" + } + }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "description":"My app description.", - "id":"1000", - "name":"test-name", + "default_source_code_path": "/Workspace/Users/[USERNAME]/test-name", + "description": "My app description.", + "id": "1000", + "name": "test-name", "resources": [ { - "description":"API key for external service.", - "name":"api-key", + "description": "API key for external service.", + "name": "api-key", "secret": { - "key":"my-key", - "permission":"READ", - "scope":"my-scope" + "key": "my-key", + "permission": "READ", + "scope": "my-scope" } } ], - "service_principal_client_id":"[UUID]", - "service_principal_id":[NUMID], - "service_principal_name":"app-test-name", - "url":"test-name-123.cloud.databricksapps.com" + "service_principal_client_id": "[UUID]", + "service_principal_id": [NUMID], + "service_principal_name": "app-test-name", + "url": "test-name-123.cloud.databricksapps.com" } === Apps update with correct input >>> [CLI] apps update test-name --json @input.json { + "active_deployment": { + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-name", + "status": { + "message": "Deployment succeeded", + "state": "SUCCEEDED" + } + }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "description":"My app description.", - "id":"1001", - "name":"test-name", + "default_source_code_path": "/Workspace/Users/[USERNAME]/test-name", + "description": "My app description.", + "id": "1001", + "name": "test-name", "resources": [ { - "description":"API key for external service.", - "name":"api-key", + "description": "API key for external service.", + "name": "api-key", "secret": { - "key":"my-key", - "permission":"READ", - "scope":"my-scope" + "key": "my-key", + "permission": "READ", + "scope": "my-scope" } } ], - "service_principal_client_id":"[UUID]", - "service_principal_id":[NUMID], - "service_principal_name":"app-test-name", - "url":"test-name-123.cloud.databricksapps.com" + "service_principal_client_id": "[UUID]", + "service_principal_id": [NUMID], + "service_principal_name": "app-test-name", + "url": "test-name-123.cloud.databricksapps.com" } === Apps update with missing parameter diff --git a/acceptance/cmd/workspace/apps/run-local-node/out.test.toml b/acceptance/cmd/workspace/apps/run-local-node/out.test.toml index 8db07a290b0..3ef9121aba6 100644 --- a/acceptance/cmd/workspace/apps/run-local-node/out.test.toml +++ b/acceptance/cmd/workspace/apps/run-local-node/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/cmd/workspace/apps/run-local/out.test.toml b/acceptance/cmd/workspace/apps/run-local/out.test.toml index f9eb74f070b..a013748b1ff 100644 --- a/acceptance/cmd/workspace/apps/run-local/out.test.toml +++ b/acceptance/cmd/workspace/apps/run-local/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform"] diff --git a/acceptance/cmd/workspace/create-scope/out.test.toml b/acceptance/cmd/workspace/create-scope/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/create-scope/out.test.toml +++ b/acceptance/cmd/workspace/create-scope/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/database/update-database-instance/out.test.toml b/acceptance/cmd/workspace/database/update-database-instance/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/database/update-database-instance/out.test.toml +++ b/acceptance/cmd/workspace/database/update-database-instance/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/database/update-database-instance/output.txt b/acceptance/cmd/workspace/database/update-database-instance/output.txt index c634d3fffe7..463b128eaf4 100644 --- a/acceptance/cmd/workspace/database/update-database-instance/output.txt +++ b/acceptance/cmd/workspace/database/update-database-instance/output.txt @@ -27,6 +27,6 @@ Exit code: 1 >>> [CLI] database update-database-instance test-db * --json {"stopped": true} { - "name":"test-db", - "stopped":true + "name": "test-db", + "stopped": true } diff --git a/acceptance/cmd/workspace/export-dir-file-size-limit/out.test.toml b/acceptance/cmd/workspace/export-dir-file-size-limit/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/export-dir-file-size-limit/out.test.toml +++ b/acceptance/cmd/workspace/export-dir-file-size-limit/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/export-dir-skip-experiments/out.test.toml b/acceptance/cmd/workspace/export-dir-skip-experiments/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/export-dir-skip-experiments/out.test.toml +++ b/acceptance/cmd/workspace/export-dir-skip-experiments/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/queries/out.test.toml b/acceptance/cmd/workspace/queries/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/queries/out.test.toml +++ b/acceptance/cmd/workspace/queries/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/query-history/out.test.toml b/acceptance/cmd/workspace/query-history/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/query-history/out.test.toml +++ b/acceptance/cmd/workspace/query-history/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/workspace/secrets/out.test.toml b/acceptance/cmd/workspace/secrets/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/cmd/workspace/secrets/out.test.toml +++ b/acceptance/cmd/workspace/secrets/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/dbr_test.go b/acceptance/dbr_test.go index 3fef59f65d1..2fe498698a1 100644 --- a/acceptance/dbr_test.go +++ b/acceptance/dbr_test.go @@ -85,7 +85,7 @@ func setupDbrTestDir(ctx context.Context, t *testing.T, uniqueID string) (*datab // API path (without /Workspace prefix) for workspace API calls. apiPath := path.Join("/Users", currentUser.UserName, "dbr-acceptance-test", uniqueID) - err = w.Workspace.MkdirsByPath(ctx, apiPath) + err = w.Workspace.MkdirsByPath(ctx, apiPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. require.NoError(t, err) // Note: We do not cleanup test directories created here. They are kept around @@ -185,7 +185,7 @@ func runDbrTests(ctx context.Context, t *testing.T, w *databricks.WorkspaceClien // Create debug logs directory debugLogsDir := path.Join("/Users", currentUser.UserName, "dbr_acceptance_tests") - err = w.Workspace.MkdirsByPath(ctx, debugLogsDir) + err = w.Workspace.MkdirsByPath(ctx, debugLogsDir) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. require.NoError(t, err) // Create an empty debug log file so we can get its URL before the job runs. @@ -204,7 +204,7 @@ func runDbrTests(ctx context.Context, t *testing.T, w *databricks.WorkspaceClien require.NoError(t, err) // Get the file's object ID for the URL - debugLogStatus, err := w.Workspace.GetStatusByPath(ctx, debugLogPath) + debugLogStatus, err := w.Workspace.GetStatusByPath(ctx, debugLogPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. require.NoError(t, err) // Build cloud test parameters (Cloud=true tests, run with CLOUD_ENV set) diff --git a/acceptance/experimental/open/out.test.toml b/acceptance/experimental/open/out.test.toml new file mode 100644 index 00000000000..d6187dcb046 --- /dev/null +++ b/acceptance/experimental/open/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/experimental/open/output.txt b/acceptance/experimental/open/output.txt new file mode 100644 index 00000000000..a83e0676fe8 --- /dev/null +++ b/acceptance/experimental/open/output.txt @@ -0,0 +1,32 @@ + +=== print URL for a job +>>> [CLI] experimental open --url jobs 123 +[DATABRICKS_URL]/jobs/123?o=[NUMID] + +=== print URL for a notebook +>>> [CLI] experimental open --url notebooks 12345 +[DATABRICKS_URL]/?o=[NUMID]#notebook/12345 + +=== unknown resource type +>>> [CLI] experimental open --url unknown 123 +Error: unknown resource type "unknown", must be one of: alerts, apps, clusters, dashboards, experiments, jobs, model_serving_endpoints, models, notebooks, pipelines, queries, registered_models, warehouses + +Exit code: 1 + +=== test auto-completion handler +>>> [CLI] __complete experimental open , +alerts +apps +clusters +dashboards +experiments +jobs +model_serving_endpoints +models +notebooks +pipelines +queries +registered_models +warehouses +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp diff --git a/acceptance/experimental/open/script b/acceptance/experimental/open/script new file mode 100644 index 00000000000..820175db8de --- /dev/null +++ b/acceptance/experimental/open/script @@ -0,0 +1,11 @@ +title "print URL for a job" +trace $CLI experimental open --url jobs 123 + +title "print URL for a notebook" +trace $CLI experimental open --url notebooks 12345 + +title "unknown resource type" +errcode trace $CLI experimental open --url unknown 123 + +title "test auto-completion handler" +trace $CLI __complete experimental open , diff --git a/acceptance/experimental/open/test.toml b/acceptance/experimental/open/test.toml new file mode 100644 index 00000000000..e83f5fafb97 --- /dev/null +++ b/acceptance/experimental/open/test.toml @@ -0,0 +1,3 @@ +# No bundle engine needed for this command. +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/help/out.test.toml b/acceptance/help/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/help/out.test.toml +++ b/acceptance/help/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/help/output.txt b/acceptance/help/output.txt index dd59847c64e..d9f379f5bbf 100644 --- a/acceptance/help/output.txt +++ b/acceptance/help/output.txt @@ -8,7 +8,7 @@ Databricks Workspace git-credentials Registers personal access token for Databricks to do operations on behalf of the user. repos The Repos API allows users to manage their git repos. secrets The Secrets API allows you to manage secrets, secret scopes, and access permissions. - workspace The Workspace API allows you to list, import, export, and delete notebooks and folders. + workspace The Workspace API allows you to list, import, export, and delete workspace objects such as notebooks, files, folders, and dashboards. Compute cluster-policies You can use cluster policies to control users' ability to configure clusters based on a set of rules. diff --git a/acceptance/install_terraform.py b/acceptance/install_terraform.py index 05a231021f9..c725a77988b 100755 --- a/acceptance/install_terraform.py +++ b/acceptance/install_terraform.py @@ -41,7 +41,7 @@ def retrieve(url, path): def read_version(path): for line in path.open(): - if "ProviderVersion" in line: + if line.startswith("const ProviderVersion"): # Expecting 'const ProviderVersion = "1.64.1"' items = line.strip().split() assert len(items) >= 3, items diff --git a/acceptance/internal/cmd_server.go b/acceptance/internal/cmd_server.go index 3fdbb8902b2..05072da6902 100644 --- a/acceptance/internal/cmd_server.go +++ b/acceptance/internal/cmd_server.go @@ -49,10 +49,10 @@ func chdir(t *testing.T, cwd string) func() { require.NotEmpty(t, cwd) prevDir, err := os.Getwd() require.NoError(t, err) - err = os.Chdir(cwd) + err = os.Chdir(cwd) //nolint:usetesting // must restore before function ends, not at test cleanup require.NoError(t, err) return func() { - _ = os.Chdir(prevDir) + _ = os.Chdir(prevDir) //nolint:usetesting // see above } } @@ -62,7 +62,7 @@ func configureEnv(t *testing.T, env map[string]string) func() { // Set current process's environment to match the input. os.Clearenv() for key, val := range env { - os.Setenv(key, val) + os.Setenv(key, val) //nolint:usetesting // custom restore needed; t.Setenv can't clearenv+restore all } // Function callback to use with defer to restore original environment. @@ -70,7 +70,7 @@ func configureEnv(t *testing.T, env map[string]string) func() { os.Clearenv() for _, kv := range oldEnv { kvs := strings.SplitN(kv, "=", 2) - os.Setenv(kvs[0], kvs[1]) + os.Setenv(kvs[0], kvs[1]) //nolint:usetesting // see above } } } diff --git a/acceptance/internal/config.go b/acceptance/internal/config.go index e14bddae68a..06ac61c39b8 100644 --- a/acceptance/internal/config.go +++ b/acceptance/internal/config.go @@ -1,12 +1,14 @@ package internal import ( + "errors" "hash/fnv" + "io/fs" + "maps" "os" "path/filepath" "reflect" "slices" - "sort" "strings" "testing" "time" @@ -43,9 +45,6 @@ type TestConfig struct { // If true, run this test when running locally with a testserver Local *bool - // If true, this test will not be run in -short mode (which is default for make test / PR) - Slow *bool - // If true, run this test when running with cloud env configured Cloud *bool @@ -181,7 +180,7 @@ func FindConfigs(t *testing.T, dir string) []string { dir = filepath.Dir(dir) - if err == nil || os.IsNotExist(err) { + if err == nil || errors.Is(err, fs.ErrNotExist) { continue } @@ -346,11 +345,7 @@ func ExpandEnvMatrix(matrix, exclude map[string][]string, extraVars []string) [] return result } - keys := make([]string, 0, len(filteredMatrix)) - for key := range filteredMatrix { - keys = append(keys, key) - } - sort.Strings(keys) + keys := slices.Sorted(maps.Keys(filteredMatrix)) // Build an expansion of all combinations. // At each step we look at a given key and append each possible value to each diff --git a/acceptance/internal/materialized_config.go b/acceptance/internal/materialized_config.go index a1f30c841c1..d849d74f2dd 100644 --- a/acceptance/internal/materialized_config.go +++ b/acceptance/internal/materialized_config.go @@ -1,56 +1,78 @@ package internal import ( - "bytes" - - "github.com/BurntSushi/toml" + "encoding/json" + "fmt" + "maps" + "slices" + "strings" ) const MaterializedConfigFile = "out.test.toml" -type MaterializedConfig struct { - GOOS map[string]bool `toml:"GOOS,omitempty"` - CloudEnvs map[string]bool `toml:"CloudEnvs,omitempty"` - Local *bool `toml:"Local,omitempty"` - Cloud *bool `toml:"Cloud,omitempty"` - CloudSlow *bool `toml:"CloudSlow,omitempty"` - RequiresUnityCatalog *bool `toml:"RequiresUnityCatalog,omitempty"` - RequiresCluster *bool `toml:"RequiresCluster,omitempty"` - RequiresWarehouse *bool `toml:"RequiresWarehouse,omitempty"` - RunsOnDbr *bool `toml:"RunsOnDbr,omitempty"` - Phase *int `toml:"Phase,omitempty"` - EnvMatrix map[string][]string `toml:"EnvMatrix,omitempty"` -} - // GenerateMaterializedConfig creates a TOML representation of the configuration fields -// that determine where and how a test is executed -func GenerateMaterializedConfig(config TestConfig) (string, error) { - var phase *int +// that determine where and how a test is executed. +func GenerateMaterializedConfig(config *TestConfig) string { + var buf strings.Builder + + writeBool(&buf, "Local", config.Local) + writeBool(&buf, "Cloud", config.Cloud) + writeBool(&buf, "CloudSlow", config.CloudSlow) + writeBool(&buf, "RequiresUnityCatalog", config.RequiresUnityCatalog) + writeBool(&buf, "RequiresCluster", config.RequiresCluster) + writeBool(&buf, "RequiresWarehouse", config.RequiresWarehouse) + writeBool(&buf, "RunsOnDbr", config.RunsOnDbr) if config.Phase != 0 { - phase = &config.Phase + fmt.Fprintf(&buf, "Phase = %d\n", config.Phase) } - materialized := MaterializedConfig{ - GOOS: config.GOOS, - CloudEnvs: config.CloudEnvs, - Local: config.Local, - Cloud: config.Cloud, - CloudSlow: config.CloudSlow, - RequiresUnityCatalog: config.RequiresUnityCatalog, - RequiresCluster: config.RequiresCluster, - RequiresWarehouse: config.RequiresWarehouse, - RunsOnDbr: config.RunsOnDbr, - Phase: phase, - EnvMatrix: config.EnvMatrix, + for _, k := range slices.Sorted(maps.Keys(config.GOOS)) { + fmt.Fprintf(&buf, "GOOS.%s = %v\n", k, config.GOOS[k]) + } + for _, k := range slices.Sorted(maps.Keys(config.CloudEnvs)) { + fmt.Fprintf(&buf, "CloudEnvs.%s = %v\n", k, config.CloudEnvs[k]) + } + for _, k := range slices.Sorted(maps.Keys(config.EnvMatrix)) { + writeTomlStringArray(&buf, "EnvMatrix."+k, config.EnvMatrix[k]) } - var buf bytes.Buffer - encoder := toml.NewEncoder(&buf) - err := encoder.Encode(materialized) - if err != nil { - return "", err + return buf.String() +} + +func writeBool(buf *strings.Builder, key string, v *bool) { + if v != nil { + fmt.Fprintf(buf, "%s = %v\n", key, *v) + } +} + +// writeTomlStringArray writes a TOML string array. Arrays with more than 3 elements +// use one element per line for readability. +func writeTomlStringArray(buf *strings.Builder, key string, vals []string) { + if len(vals) > 3 { + fmt.Fprintf(buf, "%s = [\n", key) + for i, v := range vals { + if i < len(vals)-1 { + fmt.Fprintf(buf, " %s,\n", tomlQuote(v)) + } else { + fmt.Fprintf(buf, " %s\n", tomlQuote(v)) + } + } + buf.WriteString("]\n") + return + } + fmt.Fprintf(buf, "%s = [", key) + for i, v := range vals { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(tomlQuote(v)) } + buf.WriteString("]\n") +} - // Add newline at the end of the TOML - return buf.String(), nil +// tomlQuote returns a TOML basic string literal for s using JSON encoding, +// whose escape sequences (\", \\, \n, \r, \t, \uXXXX) are all valid in TOML. +func tomlQuote(s string) string { + b, _ := json.Marshal(s) + return string(b) } diff --git a/acceptance/internal/prepare_server.go b/acceptance/internal/prepare_server.go index 2a4d02f8c41..8f18d1c61bc 100644 --- a/acceptance/internal/prepare_server.go +++ b/acceptance/internal/prepare_server.go @@ -44,7 +44,7 @@ func StartDefaultServer(t *testing.T, logRequests bool) { // This approach ensures test reliability across platforms. // // See debugging journey in https://github.com/databricks/cli/pull/3575. - homeDir, err := os.MkdirTemp("", "acceptance-home-dir") + homeDir, err := os.MkdirTemp("", "acceptance-home-dir") //nolint:usetesting // t.TempDir() fails on Windows; see PR #3575 require.NoError(t, err) t.Cleanup(func() { err := os.RemoveAll(homeDir) @@ -123,7 +123,7 @@ func PrepareServerAndClient(t *testing.T, config TestConfig, logRequests bool, o } // For the purposes of replacements, use testUser for local runs. - // Note, users might have overriden /api/2.0/preview/scim/v2/Me but that should not affect the replacement: + // Note, users might have overridden /api/2.0/preview/scim/v2/Me but that should not affect the replacement: return cfg, testUser } @@ -188,8 +188,8 @@ func startLocalServer(t *testing.T, killCountersMu := &sync.Mutex{} for ind := range stubs { - // We want later stubs takes precedence, because then leaf configs take precedence over parent directory configs - // In gorilla/mux earlier handlers take precedence, so we need to reverse the order + // Later stubs take precedence over earlier ones (leaf configs override parent configs). + // The first handler registered for a given pattern wins, so we reverse the order. stub := stubs[len(stubs)-1-ind] require.NotEmpty(t, stub.Pattern) items := strings.Split(stub.Pattern, " ") @@ -226,7 +226,8 @@ func startLocalServer(t *testing.T, }) } - // The earliest handlers take precedence, add default handlers last + // The first handler registered for a given pattern wins, so default + // handlers registered last serve as fallbacks. testserver.AddDefaultHandlers(s) return s.URL } diff --git a/acceptance/panic/out.test.toml b/acceptance/panic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/panic/out.test.toml +++ b/acceptance/panic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/databricks-cli-help/out.test.toml b/acceptance/pipelines/databricks-cli-help/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/databricks-cli-help/out.test.toml +++ b/acceptance/pipelines/databricks-cli-help/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/databricks-cli-help/output.txt b/acceptance/pipelines/databricks-cli-help/output.txt index b5d7e4a4835..9043af0f127 100644 --- a/acceptance/pipelines/databricks-cli-help/output.txt +++ b/acceptance/pipelines/databricks-cli-help/output.txt @@ -31,6 +31,7 @@ Available Commands stop Stop a pipeline Management Commands + apply-environment Apply the latest environment to the pipeline. clone Clone a pipeline. create Create a pipeline. delete Delete a pipeline. diff --git a/acceptance/pipelines/deploy/auto-approve/out.test.toml b/acceptance/pipelines/deploy/auto-approve/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/auto-approve/out.test.toml +++ b/acceptance/pipelines/deploy/auto-approve/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/create-pipeline/out.test.toml b/acceptance/pipelines/deploy/create-pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/create-pipeline/out.test.toml +++ b/acceptance/pipelines/deploy/create-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/fail-on-active-runs/out.test.toml b/acceptance/pipelines/deploy/fail-on-active-runs/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/fail-on-active-runs/out.test.toml +++ b/acceptance/pipelines/deploy/fail-on-active-runs/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/force-lock/out.test.toml b/acceptance/pipelines/deploy/force-lock/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/force-lock/out.test.toml +++ b/acceptance/pipelines/deploy/force-lock/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/oss-spark-error/out.test.toml b/acceptance/pipelines/deploy/oss-spark-error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/oss-spark-error/out.test.toml +++ b/acceptance/pipelines/deploy/oss-spark-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/render-diagnostics-warning/out.test.toml b/acceptance/pipelines/deploy/render-diagnostics-warning/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/render-diagnostics-warning/out.test.toml +++ b/acceptance/pipelines/deploy/render-diagnostics-warning/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/deploy/var-flag/out.test.toml b/acceptance/pipelines/deploy/var-flag/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/deploy/var-flag/out.test.toml +++ b/acceptance/pipelines/deploy/var-flag/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/destroy/auto-approve/out.test.toml b/acceptance/pipelines/destroy/auto-approve/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/destroy/auto-approve/out.test.toml +++ b/acceptance/pipelines/destroy/auto-approve/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/destroy/destroy-pipeline/out.test.toml b/acceptance/pipelines/destroy/destroy-pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/destroy/destroy-pipeline/out.test.toml +++ b/acceptance/pipelines/destroy/destroy-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/destroy/force-lock/out.test.toml b/acceptance/pipelines/destroy/force-lock/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/destroy/force-lock/out.test.toml +++ b/acceptance/pipelines/destroy/force-lock/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/dry-run/dry-run-pipeline/out.test.toml b/acceptance/pipelines/dry-run/dry-run-pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/dry-run/dry-run-pipeline/out.test.toml +++ b/acceptance/pipelines/dry-run/dry-run-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/dry-run/no-wait/out.test.toml b/acceptance/pipelines/dry-run/no-wait/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/dry-run/no-wait/out.test.toml +++ b/acceptance/pipelines/dry-run/no-wait/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/dry-run/restart/out.test.toml b/acceptance/pipelines/dry-run/restart/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/dry-run/restart/out.test.toml +++ b/acceptance/pipelines/dry-run/restart/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/e2e/out.test.toml b/acceptance/pipelines/e2e/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/e2e/out.test.toml +++ b/acceptance/pipelines/e2e/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/e2e/output.txt b/acceptance/pipelines/e2e/output.txt index a964b394dad..65b27ec3506 100644 --- a/acceptance/pipelines/e2e/output.txt +++ b/acceptance/pipelines/e2e/output.txt @@ -51,28 +51,28 @@ View your pipeline lakeflow_project_etl_2 here: [DATABRICKS_URL]/pipelines/[UUID === Assert the second pipeline is created >>> [CLI] pipelines get [UUID] { - "creator_user_name":"[USERNAME]", - "effective_publishing_mode":"DEFAULT_PUBLISHING_MODE", - "last_modified":[UNIX_TIME_MILLIS], - "name":"[dev [USERNAME]] lakeflow_project_etl_2", - "pipeline_id":"[UUID]", - "run_as_user_name":"[USERNAME]", + "creator_user_name": "[USERNAME]", + "effective_publishing_mode": "DEFAULT_PUBLISHING_MODE", + "last_modified": [UNIX_TIME_MILLIS], + "name": "[dev [USERNAME]] lakeflow_project_etl_2", + "pipeline_id": "[UUID]", + "run_as_user_name": "[USERNAME]", "spec": { - "channel":"CURRENT", + "channel": "CURRENT", "deployment": { - "kind":"BUNDLE", - "metadata_file_path":"/Workspace/Users/[USERNAME]/.bundle/lakeflow_project/dev/state/metadata.json" + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/lakeflow_project/dev/state/metadata.json" }, - "development":true, - "edition":"ADVANCED", - "id":"[UUID]", - "name":"[dev [USERNAME]] lakeflow_project_etl_2", - "storage":"dbfs:/pipelines/[UUID]", + "development": true, + "edition": "ADVANCED", + "id": "[UUID]", + "name": "[dev [USERNAME]] lakeflow_project_etl_2", + "storage": "dbfs:/pipelines/[UUID]", "tags": { - "dev":"[USERNAME]" + "dev": "[USERNAME]" } }, - "state":"IDLE" + "state": "IDLE" } >>> [CLI] pipelines run lakeflow_project_etl_2 diff --git a/acceptance/pipelines/generate/bad-path/out.test.toml b/acceptance/pipelines/generate/bad-path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/generate/bad-path/out.test.toml +++ b/acceptance/pipelines/generate/bad-path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/generate/discover-spark-pipeline-yml/out.test.toml b/acceptance/pipelines/generate/discover-spark-pipeline-yml/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/generate/discover-spark-pipeline-yml/out.test.toml +++ b/acceptance/pipelines/generate/discover-spark-pipeline-yml/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/generate/fail-overwrite/out.test.toml b/acceptance/pipelines/generate/fail-overwrite/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/generate/fail-overwrite/out.test.toml +++ b/acceptance/pipelines/generate/fail-overwrite/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/generate/simple/out.test.toml b/acceptance/pipelines/generate/simple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/generate/simple/out.test.toml +++ b/acceptance/pipelines/generate/simple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/generate/unknown-attribute/out.test.toml b/acceptance/pipelines/generate/unknown-attribute/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/generate/unknown-attribute/out.test.toml +++ b/acceptance/pipelines/generate/unknown-attribute/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/init/error-cases/out.test.toml b/acceptance/pipelines/init/error-cases/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/init/error-cases/out.test.toml +++ b/acceptance/pipelines/init/error-cases/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/init/python/out.test.toml b/acceptance/pipelines/init/python/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/init/python/out.test.toml +++ b/acceptance/pipelines/init/python/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/init/sql/out.test.toml b/acceptance/pipelines/init/sql/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/init/sql/out.test.toml +++ b/acceptance/pipelines/init/sql/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/open/completion/out.test.toml b/acceptance/pipelines/open/completion/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/open/completion/out.test.toml +++ b/acceptance/pipelines/open/completion/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/open/open-after-deployment/out.test.toml b/acceptance/pipelines/open/open-after-deployment/out.test.toml index 216969a7619..519954aedc9 100644 --- a/acceptance/pipelines/open/open-after-deployment/out.test.toml +++ b/acceptance/pipelines/open/open-after-deployment/out.test.toml @@ -1,9 +1,5 @@ Local = true Cloud = false - -[GOOS] - linux = false - windows = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +GOOS.linux = false +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/completion/out.test.toml b/acceptance/pipelines/run/completion/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/completion/out.test.toml +++ b/acceptance/pipelines/run/completion/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/no-wait/out.test.toml b/acceptance/pipelines/run/no-wait/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/no-wait/out.test.toml +++ b/acceptance/pipelines/run/no-wait/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/refresh-flags/out.test.toml b/acceptance/pipelines/run/refresh-flags/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/refresh-flags/out.test.toml +++ b/acceptance/pipelines/run/refresh-flags/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/restart/out.test.toml b/acceptance/pipelines/run/restart/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/restart/out.test.toml +++ b/acceptance/pipelines/run/restart/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/run-info/out.test.toml b/acceptance/pipelines/run/run-info/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/run-info/out.test.toml +++ b/acceptance/pipelines/run/run-info/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/run/run-pipeline/out.test.toml b/acceptance/pipelines/run/run-pipeline/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/run/run-pipeline/out.test.toml +++ b/acceptance/pipelines/run/run-pipeline/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/pipelines/stop/out.test.toml b/acceptance/pipelines/stop/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/pipelines/stop/out.test.toml +++ b/acceptance/pipelines/stop/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/IsServicePrincipal/out.test.toml b/acceptance/selftest/IsServicePrincipal/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/IsServicePrincipal/out.test.toml +++ b/acceptance/selftest/IsServicePrincipal/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/IsServicePrincipal/output.txt b/acceptance/selftest/IsServicePrincipal/output.txt index 07438ae85e9..bcf74fce5bf 100644 --- a/acceptance/selftest/IsServicePrincipal/output.txt +++ b/acceptance/selftest/IsServicePrincipal/output.txt @@ -1,6 +1,6 @@ >>> [CLI] current-user me { - "id":"[USERID]", - "userName":"Xaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee" + "id": "[USERID]", + "userName": "Xaaaaaaa-bbbb-4ccc-dddd-eeeeeeeeeeee" } diff --git a/acceptance/selftest/acc_repls/out.test.toml b/acceptance/selftest/acc_repls/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/acc_repls/out.test.toml +++ b/acceptance/selftest/acc_repls/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/add_repl/out.test.toml b/acceptance/selftest/add_repl/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/add_repl/out.test.toml +++ b/acceptance/selftest/add_repl/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/basic/out.test.toml b/acceptance/selftest/basic/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/basic/out.test.toml +++ b/acceptance/selftest/basic/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/benchmark/out.test.toml b/acceptance/selftest/benchmark/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/benchmark/out.test.toml +++ b/acceptance/selftest/benchmark/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/contains/out.test.toml b/acceptance/selftest/contains/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/contains/out.test.toml +++ b/acceptance/selftest/contains/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/diff/out.test.toml b/acceptance/selftest/diff/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/diff/out.test.toml +++ b/acceptance/selftest/diff/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/envmatrix/inner/out.test.toml b/acceptance/selftest/envmatrix/inner/out.test.toml index 21b27ccedba..a07b8b5da49 100644 --- a/acceptance/selftest/envmatrix/inner/out.test.toml +++ b/acceptance/selftest/envmatrix/inner/out.test.toml @@ -1,9 +1,7 @@ Local = true Cloud = false - -[EnvMatrix] - B_REGULAR_VAR = ["hello"] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - FIRST = ["one111", "two222"] - SECOND = ["variantA", "variantB"] - THIRD = ["three $FIRST"] +EnvMatrix.B_REGULAR_VAR = ["hello"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.FIRST = ["one111", "two222"] +EnvMatrix.SECOND = ["variantA", "variantB"] +EnvMatrix.THIRD = ["three $FIRST"] diff --git a/acceptance/selftest/envmatrix/out.test.toml b/acceptance/selftest/envmatrix/out.test.toml index 820b3f48eba..b017ee6bdaf 100644 --- a/acceptance/selftest/envmatrix/out.test.toml +++ b/acceptance/selftest/envmatrix/out.test.toml @@ -1,7 +1,5 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - FIRST = ["overriden-in-inner"] - THIRD = ["three $FIRST"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.FIRST = ["overriden-in-inner"] +EnvMatrix.THIRD = ["three $FIRST"] diff --git a/acceptance/selftest/envmatrix_empty/out.test.toml b/acceptance/selftest/envmatrix_empty/out.test.toml index d3e35285f1c..d6187dcb046 100644 --- a/acceptance/selftest/envmatrix_empty/out.test.toml +++ b/acceptance/selftest/envmatrix_empty/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = [] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/selftest/envmatrix_mixed/out.test.toml b/acceptance/selftest/envmatrix_mixed/out.test.toml index 0f241320834..7dbbeba2e82 100644 --- a/acceptance/selftest/envmatrix_mixed/out.test.toml +++ b/acceptance/selftest/envmatrix_mixed/out.test.toml @@ -1,7 +1,5 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = [] - FIRST = ["alpha", "beta"] - SECOND = [] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] +EnvMatrix.FIRST = ["alpha", "beta"] +EnvMatrix.SECOND = [] diff --git a/acceptance/selftest/envoutput/out.test.toml b/acceptance/selftest/envoutput/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/envoutput/out.test.toml +++ b/acceptance/selftest/envoutput/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/gen_config/out.test.toml b/acceptance/selftest/gen_config/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/gen_config/out.test.toml +++ b/acceptance/selftest/gen_config/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/inject_error/out.test.toml b/acceptance/selftest/inject_error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/inject_error/out.test.toml +++ b/acceptance/selftest/inject_error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/kill_caller/currentuser/out.test.toml b/acceptance/selftest/kill_caller/currentuser/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/kill_caller/currentuser/out.test.toml +++ b/acceptance/selftest/kill_caller/currentuser/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/kill_caller/multi_pattern/out.test.toml b/acceptance/selftest/kill_caller/multi_pattern/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/kill_caller/multi_pattern/out.test.toml +++ b/acceptance/selftest/kill_caller/multi_pattern/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/kill_caller/multi_pattern/output.txt b/acceptance/selftest/kill_caller/multi_pattern/output.txt index 2c080b2c349..9b41f23ec4d 100644 --- a/acceptance/selftest/kill_caller/multi_pattern/output.txt +++ b/acceptance/selftest/kill_caller/multi_pattern/output.txt @@ -13,8 +13,8 @@ Me attempt 2 done >>> [CLI] current-user me { - "id":"123", - "userName":"test@example.com" + "id": "123", + "userName": "test@example.com" } Me attempt 3 done - success! diff --git a/acceptance/selftest/kill_caller/multiple/out.test.toml b/acceptance/selftest/kill_caller/multiple/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/kill_caller/multiple/out.test.toml +++ b/acceptance/selftest/kill_caller/multiple/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/kill_caller/multiple/output.txt b/acceptance/selftest/kill_caller/multiple/output.txt index 538672bf865..27b034cfcb1 100644 --- a/acceptance/selftest/kill_caller/multiple/output.txt +++ b/acceptance/selftest/kill_caller/multiple/output.txt @@ -19,7 +19,7 @@ Attempt 3 done >>> [CLI] current-user me { - "id":"123", - "userName":"test@example.com" + "id": "123", + "userName": "test@example.com" } Attempt 4 done - success! diff --git a/acceptance/selftest/kill_caller/workspace/out.test.toml b/acceptance/selftest/kill_caller/workspace/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/kill_caller/workspace/out.test.toml +++ b/acceptance/selftest/kill_caller/workspace/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/log/out.test.toml b/acceptance/selftest/log/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/log/out.test.toml +++ b/acceptance/selftest/log/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/basic/out.test.toml b/acceptance/selftest/record_cloud/basic/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/selftest/record_cloud/basic/out.test.toml +++ b/acceptance/selftest/record_cloud/basic/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/error/out.test.toml b/acceptance/selftest/record_cloud/error/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/selftest/record_cloud/error/out.test.toml +++ b/acceptance/selftest/record_cloud/error/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/pipeline-crud/out.test.toml b/acceptance/selftest/record_cloud/pipeline-crud/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/selftest/record_cloud/pipeline-crud/out.test.toml +++ b/acceptance/selftest/record_cloud/pipeline-crud/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/pipeline-crud/output.txt b/acceptance/selftest/record_cloud/pipeline-crud/output.txt index c62fb4a463c..97ac1421a4b 100644 --- a/acceptance/selftest/record_cloud/pipeline-crud/output.txt +++ b/acceptance/selftest/record_cloud/pipeline-crud/output.txt @@ -27,10 +27,6 @@ "test-pipeline-1" >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/pipelines/[UUID]" @@ -40,10 +36,6 @@ >>> [CLI] pipelines update [UUID] --json @pipeline2.json >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "PUT", "path": "/api/2.0/pipelines/[UUID]", @@ -65,10 +57,6 @@ "test-pipeline-2" >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/pipelines/[UUID]" @@ -78,10 +66,6 @@ >>> [CLI] pipelines delete [UUID] >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "DELETE", "path": "/api/2.0/pipelines/[UUID]" @@ -94,10 +78,6 @@ Error: The specified pipeline [UUID] was not found. Exit code: 1 >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/pipelines/[UUID]" diff --git a/acceptance/selftest/record_cloud/volume-io/out.test.toml b/acceptance/selftest/record_cloud/volume-io/out.test.toml index 7190c9b30bf..2d812727e32 100644 --- a/acceptance/selftest/record_cloud/volume-io/out.test.toml +++ b/acceptance/selftest/record_cloud/volume-io/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = true RequiresUnityCatalog = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/volume-io/output.txt b/acceptance/selftest/record_cloud/volume-io/output.txt index bcce9a7f896..b06ba039261 100644 --- a/acceptance/selftest/record_cloud/volume-io/output.txt +++ b/acceptance/selftest/record_cloud/volume-io/output.txt @@ -26,10 +26,6 @@ } >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/schemas/main.schema-[UNIQUE_NAME]" @@ -42,10 +38,6 @@ } >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.1/unity-catalog/volumes", @@ -64,10 +56,6 @@ } >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/volumes/main.schema-[UNIQUE_NAME].volume-[UNIQUE_NAME]" @@ -77,10 +65,6 @@ ./hello.txt -> dbfs:/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]/hello.txt >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "HEAD", "path": "/api/2.0/fs/directories/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]" @@ -102,10 +86,6 @@ hello.txt >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/fs/directories/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]" @@ -114,10 +94,6 @@ hello.txt >>> [CLI] fs cat dbfs:/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]/hello.txt hello, world >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/fs/files/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]/hello.txt" @@ -126,10 +102,6 @@ hello, world >>> [CLI] fs rm dbfs:/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]/hello.txt >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "HEAD", "path": "/api/2.0/fs/directories/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]/hello.txt" @@ -146,10 +118,6 @@ hello, world >>> [CLI] fs ls dbfs:/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME] >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/fs/directories/Volumes/main/schema-[UNIQUE_NAME]/volume-[UNIQUE_NAME]" @@ -158,10 +126,6 @@ hello, world >>> [CLI] volumes delete main.schema-[UNIQUE_NAME].volume-[UNIQUE_NAME] >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "DELETE", "path": "/api/2.1/unity-catalog/volumes/main.schema-[UNIQUE_NAME].volume-[UNIQUE_NAME]" @@ -173,10 +137,6 @@ Error: Volume 'main.schema-[UNIQUE_NAME].volume-[UNIQUE_NAME]' does not exist. Exit code: 1 >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/volumes/main.schema-[UNIQUE_NAME].volume-[UNIQUE_NAME]" @@ -185,10 +145,6 @@ Exit code: 1 >>> [CLI] schemas delete main.schema-[UNIQUE_NAME] >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "DELETE", "path": "/api/2.1/unity-catalog/schemas/main.schema-[UNIQUE_NAME]" @@ -200,10 +156,6 @@ Error: Schema 'main.schema-[UNIQUE_NAME]' does not exist. Exit code: 1 >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.1/unity-catalog/schemas/main.schema-[UNIQUE_NAME]" diff --git a/acceptance/selftest/record_cloud/workspace-file-io/out.test.toml b/acceptance/selftest/record_cloud/workspace-file-io/out.test.toml index f474b1b917a..650836edeb3 100644 --- a/acceptance/selftest/record_cloud/workspace-file-io/out.test.toml +++ b/acceptance/selftest/record_cloud/workspace-file-io/out.test.toml @@ -1,5 +1,3 @@ Local = false Cloud = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/record_cloud/workspace-file-io/output.txt b/acceptance/selftest/record_cloud/workspace-file-io/output.txt index d08a332e6a1..7f515ba1511 100644 --- a/acceptance/selftest/record_cloud/workspace-file-io/output.txt +++ b/acceptance/selftest/record_cloud/workspace-file-io/output.txt @@ -11,10 +11,6 @@ "method": "GET", "path": "/api/2.0/preview/scim/v2/Me" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/workspace/mkdirs", @@ -27,10 +23,6 @@ >>> [CLI] workspace import /Users/[USERNAME]/[UNIQUE_NAME]/hello.txt --format AUTO --file ./hello.txt >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/workspace/import", @@ -49,10 +41,6 @@ } >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/list", @@ -69,10 +57,6 @@ } >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -88,10 +72,6 @@ hello, world >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/export", @@ -105,10 +85,6 @@ hello, world >>> [CLI] workspace delete /Users/[USERNAME]/[UNIQUE_NAME]/hello.txt >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/workspace/delete", @@ -124,10 +100,6 @@ Error: Path (/Users/[USERNAME]/[UNIQUE_NAME]/hello.txt) doesn't exist. Exit code: 1 >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -144,10 +116,6 @@ ID Type Language Path >>> [CLI] workspace delete /Users/[USERNAME]/[UNIQUE_NAME] >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/list", @@ -155,10 +123,6 @@ ID Type Language Path "path": "/Users/[USERNAME]/[UNIQUE_NAME]" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/workspace/delete", @@ -174,10 +138,6 @@ Error: Path (/Users/[USERNAME]/[UNIQUE_NAME]) doesn't exist. Exit code: 1 >>> print_requests -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/list", diff --git a/acceptance/selftest/server/out.test.toml b/acceptance/selftest/server/out.test.toml index bbcae619d6c..d4e9799c700 100644 --- a/acceptance/selftest/server/out.test.toml +++ b/acceptance/selftest/server/out.test.toml @@ -1,7 +1,5 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] - VARIABLE_A = ["one", "two"] - VARIABLE_B = ["HELLO", "WORLD"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.VARIABLE_A = ["one", "two"] +EnvMatrix.VARIABLE_B = ["HELLO", "WORLD"] diff --git a/acceptance/selftest/skip/out.test.toml b/acceptance/selftest/skip/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/skip/out.test.toml +++ b/acceptance/selftest/skip/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/slow/out.test.toml b/acceptance/selftest/slow/out.test.toml deleted file mode 100644 index d560f1de043..00000000000 --- a/acceptance/selftest/slow/out.test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/slow/output.txt b/acceptance/selftest/slow/output.txt deleted file mode 100644 index ff94be68ef9..00000000000 --- a/acceptance/selftest/slow/output.txt +++ /dev/null @@ -1 +0,0 @@ -Skipped in -short mode diff --git a/acceptance/selftest/slow/script b/acceptance/selftest/slow/script deleted file mode 100644 index 4cc8286b568..00000000000 --- a/acceptance/selftest/slow/script +++ /dev/null @@ -1 +0,0 @@ -echo "Skipped in -short mode" diff --git a/acceptance/selftest/slow/test.toml b/acceptance/selftest/slow/test.toml deleted file mode 100644 index 465ea35c7cd..00000000000 --- a/acceptance/selftest/slow/test.toml +++ /dev/null @@ -1 +0,0 @@ -Slow = true diff --git a/acceptance/selftest/subset_ancestor_engine/child/out.test.toml b/acceptance/selftest/subset_ancestor_engine/child/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/subset_ancestor_engine/child/out.test.toml +++ b/acceptance/selftest/subset_ancestor_engine/child/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/testlog/out.test.toml b/acceptance/selftest/testlog/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/testlog/out.test.toml +++ b/acceptance/selftest/testlog/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/timeout/out.test.toml b/acceptance/selftest/timeout/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/timeout/out.test.toml +++ b/acceptance/selftest/timeout/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/timestamp/out.test.toml b/acceptance/selftest/timestamp/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/timestamp/out.test.toml +++ b/acceptance/selftest/timestamp/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/trap/out.test.toml b/acceptance/selftest/trap/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/selftest/trap/out.test.toml +++ b/acceptance/selftest/trap/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/ssh/connect-serverless-gpu/out.test.toml b/acceptance/ssh/connect-serverless-gpu/out.test.toml index b57de8531dd..ab8691ddb9b 100644 --- a/acceptance/ssh/connect-serverless-gpu/out.test.toml +++ b/acceptance/ssh/connect-serverless-gpu/out.test.toml @@ -1,9 +1,5 @@ Local = false Cloud = false RequiresUnityCatalog = true - -[CloudEnvs] - gcp = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/ssh/connection/out.test.toml b/acceptance/ssh/connection/out.test.toml index 76965d3ae0e..06079f65341 100644 --- a/acceptance/ssh/connection/out.test.toml +++ b/acceptance/ssh/connection/out.test.toml @@ -1,6 +1,4 @@ Local = false Cloud = false RequiresCluster = true - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/telemetry/failure/out.requests.txt b/acceptance/telemetry/failure/out.requests.txt index 19177f4de19..808b5e97243 100644 --- a/acceptance/telemetry/failure/out.requests.txt +++ b/acceptance/telemetry/failure/out.requests.txt @@ -6,6 +6,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", @@ -23,6 +26,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", @@ -40,6 +46,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", diff --git a/acceptance/telemetry/failure/out.test.toml b/acceptance/telemetry/failure/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/telemetry/failure/out.test.toml +++ b/acceptance/telemetry/failure/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/telemetry/failure/output.txt b/acceptance/telemetry/failure/output.txt index af0c34a13ea..2086a88444a 100644 --- a/acceptance/telemetry/failure/output.txt +++ b/acceptance/telemetry/failure/output.txt @@ -1,12 +1,15 @@ >>> [CLI] selftest send-telemetry --debug HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Debug: [Local Cache] using cache key: [SHA256_HASH] pid=PID +HH:MM:SS Debug: [Local Cache] cache miss, computing pid=PID HH:MM:SS Debug: GET /.well-known/databricks-config < HTTP/1.1 200 OK < { < "oidc_endpoint": "[DATABRICKS_URL]/oidc", < "workspace_id": "[NUMID]" < } pid=PID sdk=true +HH:MM:SS Debug: [Local Cache] computed and stored result pid=PID HH:MM:SS Debug: Resolved workspace_id from host metadata: "[NUMID]" pid=PID sdk=true HH:MM:SS Debug: Resolved cloud from hostname: "AWS" pid=PID sdk=true HH:MM:SS Debug: Resolved discovery_url from host metadata: "[DATABRICKS_URL]/oidc/.well-known/oauth-authorization-server" pid=PID sdk=true diff --git a/acceptance/telemetry/partial-success/out.requests.txt b/acceptance/telemetry/partial-success/out.requests.txt index 19177f4de19..808b5e97243 100644 --- a/acceptance/telemetry/partial-success/out.requests.txt +++ b/acceptance/telemetry/partial-success/out.requests.txt @@ -6,6 +6,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", @@ -23,6 +26,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", @@ -40,6 +46,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", diff --git a/acceptance/telemetry/partial-success/out.test.toml b/acceptance/telemetry/partial-success/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/telemetry/partial-success/out.test.toml +++ b/acceptance/telemetry/partial-success/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/telemetry/partial-success/output.txt b/acceptance/telemetry/partial-success/output.txt index 113dc11b664..c641e6bc0d0 100644 --- a/acceptance/telemetry/partial-success/output.txt +++ b/acceptance/telemetry/partial-success/output.txt @@ -1,12 +1,15 @@ >>> [CLI] selftest send-telemetry --debug HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Debug: [Local Cache] using cache key: [SHA256_HASH] pid=PID +HH:MM:SS Debug: [Local Cache] cache miss, computing pid=PID HH:MM:SS Debug: GET /.well-known/databricks-config < HTTP/1.1 200 OK < { < "oidc_endpoint": "[DATABRICKS_URL]/oidc", < "workspace_id": "[NUMID]" < } pid=PID sdk=true +HH:MM:SS Debug: [Local Cache] computed and stored result pid=PID HH:MM:SS Debug: Resolved workspace_id from host metadata: "[NUMID]" pid=PID sdk=true HH:MM:SS Debug: Resolved cloud from hostname: "AWS" pid=PID sdk=true HH:MM:SS Debug: Resolved discovery_url from host metadata: "[DATABRICKS_URL]/oidc/.well-known/oauth-authorization-server" pid=PID sdk=true diff --git a/acceptance/telemetry/skipped/out.test.toml b/acceptance/telemetry/skipped/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/telemetry/skipped/out.test.toml +++ b/acceptance/telemetry/skipped/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/telemetry/skipped/output.txt b/acceptance/telemetry/skipped/output.txt index e85ce380a42..9e784a8eb0b 100644 --- a/acceptance/telemetry/skipped/output.txt +++ b/acceptance/telemetry/skipped/output.txt @@ -1,12 +1,15 @@ >>> [CLI] selftest send-telemetry --debug HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Debug: [Local Cache] using cache key: [SHA256_HASH] pid=PID +HH:MM:SS Debug: [Local Cache] cache miss, computing pid=PID HH:MM:SS Debug: GET /.well-known/databricks-config < HTTP/1.1 200 OK < { < "oidc_endpoint": "[DATABRICKS_URL]/oidc", < "workspace_id": "[NUMID]" < } pid=PID sdk=true +HH:MM:SS Debug: [Local Cache] computed and stored result pid=PID HH:MM:SS Debug: Resolved workspace_id from host metadata: "[NUMID]" pid=PID sdk=true HH:MM:SS Debug: Resolved cloud from hostname: "AWS" pid=PID sdk=true HH:MM:SS Debug: Resolved discovery_url from host metadata: "[DATABRICKS_URL]/oidc/.well-known/oauth-authorization-server" pid=PID sdk=true diff --git a/acceptance/telemetry/success/out.requests.txt b/acceptance/telemetry/success/out.requests.txt index 22e7a92d720..f73cd0a891e 100644 --- a/acceptance/telemetry/success/out.requests.txt +++ b/acceptance/telemetry/success/out.requests.txt @@ -14,6 +14,9 @@ ], "User-Agent": [ "cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/selftest_send-telemetry cmd-exec-id/[CMD-EXEC-ID] interactive/none auth/pat" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", diff --git a/acceptance/telemetry/success/out.test.toml b/acceptance/telemetry/success/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/telemetry/success/out.test.toml +++ b/acceptance/telemetry/success/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/telemetry/success/output.txt b/acceptance/telemetry/success/output.txt index f3b410f765b..96f72c97270 100644 --- a/acceptance/telemetry/success/output.txt +++ b/acceptance/telemetry/success/output.txt @@ -1,12 +1,15 @@ >>> [CLI] selftest send-telemetry --debug HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Debug: [Local Cache] using cache key: [SHA256_HASH] pid=PID +HH:MM:SS Debug: [Local Cache] cache miss, computing pid=PID HH:MM:SS Debug: GET /.well-known/databricks-config < HTTP/1.1 200 OK < { < "oidc_endpoint": "[DATABRICKS_URL]/oidc", < "workspace_id": "[NUMID]" < } pid=PID sdk=true +HH:MM:SS Debug: [Local Cache] computed and stored result pid=PID HH:MM:SS Debug: Resolved workspace_id from host metadata: "[NUMID]" pid=PID sdk=true HH:MM:SS Debug: Resolved cloud from hostname: "AWS" pid=PID sdk=true HH:MM:SS Debug: Resolved discovery_url from host metadata: "[DATABRICKS_URL]/oidc/.well-known/oauth-authorization-server" pid=PID sdk=true diff --git a/acceptance/telemetry/test.toml b/acceptance/telemetry/test.toml index 574ffd3ce14..cce01f8ccd2 100644 --- a/acceptance/telemetry/test.toml +++ b/acceptance/telemetry/test.toml @@ -1,4 +1,4 @@ -IncludeRequestHeaders = ["Authorization"] +IncludeRequestHeaders = ["Authorization", "X-Databricks-Org-Id"] RecordRequests = true Local = true @@ -36,3 +36,11 @@ New = "pid=PID" [[Repls]] Old = "\\([0-9]+ more bytes\\)" New = "(N more bytes)" + +# Host metadata cache keys vary per-test because the mock server URL changes. +# Normalize them so the golden output is stable across runs. +# Order=1 so it runs before the parent's `\d{14,}` → `[NUMID]` replacement. +[[Repls]] +Old = '[a-f0-9]{64}' +New = "[SHA256_HASH]" +Order = 1 diff --git a/acceptance/telemetry/timeout/out.requests.txt b/acceptance/telemetry/timeout/out.requests.txt index 9fe44bf9ba8..1241a933a9a 100644 --- a/acceptance/telemetry/timeout/out.requests.txt +++ b/acceptance/telemetry/timeout/out.requests.txt @@ -6,6 +6,9 @@ "headers": { "Authorization": [ "Bearer [DATABRICKS_TOKEN]" + ], + "X-Databricks-Org-Id": [ + "[NUMID]" ] }, "method": "POST", diff --git a/acceptance/telemetry/timeout/out.test.toml b/acceptance/telemetry/timeout/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/telemetry/timeout/out.test.toml +++ b/acceptance/telemetry/timeout/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/telemetry/timeout/output.txt b/acceptance/telemetry/timeout/output.txt index a124cc72b66..21f79ef7bed 100644 --- a/acceptance/telemetry/timeout/output.txt +++ b/acceptance/telemetry/timeout/output.txt @@ -1,12 +1,15 @@ >>> [CLI] selftest send-telemetry --debug HH:MM:SS Info: start pid=PID version=[DEV_VERSION] args="[CLI], selftest, send-telemetry, --debug" +HH:MM:SS Debug: [Local Cache] using cache key: [SHA256_HASH] pid=PID +HH:MM:SS Debug: [Local Cache] cache miss, computing pid=PID HH:MM:SS Debug: GET /.well-known/databricks-config < HTTP/1.1 200 OK < { < "oidc_endpoint": "[DATABRICKS_URL]/oidc", < "workspace_id": "[NUMID]" < } pid=PID sdk=true +HH:MM:SS Debug: [Local Cache] computed and stored result pid=PID HH:MM:SS Debug: Resolved workspace_id from host metadata: "[NUMID]" pid=PID sdk=true HH:MM:SS Debug: Resolved cloud from hostname: "AWS" pid=PID sdk=true HH:MM:SS Debug: Resolved discovery_url from host metadata: "[DATABRICKS_URL]/oidc/.well-known/oauth-authorization-server" pid=PID sdk=true diff --git a/acceptance/test.toml b/acceptance/test.toml index 3881887cf4f..a482f492603 100644 --- a/acceptance/test.toml +++ b/acceptance/test.toml @@ -3,8 +3,8 @@ Local = true Cloud = false # default timeouts -Timeout = '30s' -TimeoutWindows = '60s' +Timeout = '60s' +TimeoutWindows = '90s' # Slowest test I saw: # github.com/databricks/cli/acceptance TestAccept/bundle/integration_whl/interactive_single_user 18m8.69s diff --git a/acceptance/workspace/jobs/create-error/out.test.toml b/acceptance/workspace/jobs/create-error/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/jobs/create-error/out.test.toml +++ b/acceptance/workspace/jobs/create-error/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/jobs/create/out.test.toml b/acceptance/workspace/jobs/create/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/jobs/create/out.test.toml +++ b/acceptance/workspace/jobs/create/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/jobs/create/output.txt b/acceptance/workspace/jobs/create/output.txt index 5f80243baa0..7b006aa656e 100644 --- a/acceptance/workspace/jobs/create/output.txt +++ b/acceptance/workspace/jobs/create/output.txt @@ -1,5 +1,5 @@ >>> [CLI] jobs create --json {"name":"abc"} { - "job_id":[NUMID] + "job_id": [NUMID] } diff --git a/acceptance/workspace/lakeview/publish/out.requests.txt b/acceptance/workspace/lakeview/publish/out.requests.txt index 4adba9b64af..e4802babc67 100644 --- a/acceptance/workspace/lakeview/publish/out.requests.txt +++ b/acceptance/workspace/lakeview/publish/out.requests.txt @@ -9,10 +9,6 @@ "path": "/Users/[USERNAME]" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/lakeview/dashboards", @@ -22,10 +18,6 @@ "warehouse_id": "test-warehouse" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/lakeview/dashboards/[DASHBOARD_ID]/published", diff --git a/acceptance/workspace/lakeview/publish/out.test.toml b/acceptance/workspace/lakeview/publish/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/lakeview/publish/out.test.toml +++ b/acceptance/workspace/lakeview/publish/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/lakeview/publish/output.txt b/acceptance/workspace/lakeview/publish/output.txt index 60235c01c43..3a1ebfc8bdf 100644 --- a/acceptance/workspace/lakeview/publish/output.txt +++ b/acceptance/workspace/lakeview/publish/output.txt @@ -5,8 +5,8 @@ >>> [CLI] lakeview publish [DASHBOARD_ID] { - "display_name":"Test Dashboard", - "embed_credentials":false, - "revision_create_time":"[TIMESTAMP]", - "warehouse_id":"test-warehouse" + "display_name": "Test Dashboard", + "embed_credentials": false, + "revision_create_time": "[TIMESTAMP]", + "warehouse_id": "test-warehouse" } diff --git a/acceptance/workspace/repos/create_with_provider/out.requests.txt b/acceptance/workspace/repos/create_with_provider/out.requests.txt index 73219c0a272..430eb33fe1b 100644 --- a/acceptance/workspace/repos/create_with_provider/out.requests.txt +++ b/acceptance/workspace/repos/create_with_provider/out.requests.txt @@ -11,18 +11,10 @@ "url": "https://github.com/databricks/databricks-empty-ide-project.git" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/repos/[NUMID]" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -34,10 +26,6 @@ "method": "GET", "path": "/api/2.0/repos/[NUMID]" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "DELETE", "path": "/api/2.0/repos/[NUMID]" diff --git a/acceptance/workspace/repos/create_with_provider/out.test.toml b/acceptance/workspace/repos/create_with_provider/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/repos/create_with_provider/out.test.toml +++ b/acceptance/workspace/repos/create_with_provider/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/create_with_provider/output.txt b/acceptance/workspace/repos/create_with_provider/output.txt index b6008eba86c..df97be1fcee 100644 --- a/acceptance/workspace/repos/create_with_provider/output.txt +++ b/acceptance/workspace/repos/create_with_provider/output.txt @@ -4,21 +4,21 @@ === Get by id should work >>> [CLI] repos get [NUMID] -o json { - "branch":"main", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "main", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } === Get by path should work >>> [CLI] repos get /Repos/me@databricks.com/test-repo -o json { - "branch":"main", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "main", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } === Delete by id should work diff --git a/acceptance/workspace/repos/create_without_provider/out.test.toml b/acceptance/workspace/repos/create_without_provider/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/repos/create_without_provider/out.test.toml +++ b/acceptance/workspace/repos/create_without_provider/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/create_without_provider/output.txt b/acceptance/workspace/repos/create_without_provider/output.txt index 746919f6efb..4a461ec664e 100644 --- a/acceptance/workspace/repos/create_without_provider/output.txt +++ b/acceptance/workspace/repos/create_without_provider/output.txt @@ -1,9 +1,9 @@ >>> [CLI] repos create https://github.com/databricks/databricks-empty-ide-project.git --path /Repos/me@databricks.com/test-repo { - "branch":"main", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "main", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } diff --git a/acceptance/workspace/repos/delete_by_path/out.requests.txt b/acceptance/workspace/repos/delete_by_path/out.requests.txt index f6857935ae5..9fa6916971a 100644 --- a/acceptance/workspace/repos/delete_by_path/out.requests.txt +++ b/acceptance/workspace/repos/delete_by_path/out.requests.txt @@ -11,10 +11,6 @@ "url": "https://github.com/databricks/databricks-empty-ide-project.git" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -26,10 +22,6 @@ "method": "GET", "path": "/api/2.0/repos/[NUMID]" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -41,10 +33,6 @@ "method": "DELETE", "path": "/api/2.0/repos/[NUMID]" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", diff --git a/acceptance/workspace/repos/delete_by_path/out.test.toml b/acceptance/workspace/repos/delete_by_path/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/repos/delete_by_path/out.test.toml +++ b/acceptance/workspace/repos/delete_by_path/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/delete_by_path/output.txt b/acceptance/workspace/repos/delete_by_path/output.txt index 782dd50d101..3f888863e06 100644 --- a/acceptance/workspace/repos/delete_by_path/output.txt +++ b/acceptance/workspace/repos/delete_by_path/output.txt @@ -4,11 +4,11 @@ >>> [CLI] repos get /Repos/me@databricks.com/test-repo -o json { - "branch":"main", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "main", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } >>> [CLI] repos delete /Repos/me@databricks.com/test-repo diff --git a/acceptance/workspace/repos/get_errors/out.requests.txt b/acceptance/workspace/repos/get_errors/out.requests.txt index 24de0f3dd05..2bfe07b1317 100644 --- a/acceptance/workspace/repos/get_errors/out.requests.txt +++ b/acceptance/workspace/repos/get_errors/out.requests.txt @@ -9,10 +9,6 @@ "path": "/Repos/me@databricks.com/doesnotexist" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "POST", "path": "/api/2.0/workspace/mkdirs", @@ -20,10 +16,6 @@ "path": "/not-a-repo" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", diff --git a/acceptance/workspace/repos/get_errors/out.test.toml b/acceptance/workspace/repos/get_errors/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/repos/get_errors/out.test.toml +++ b/acceptance/workspace/repos/get_errors/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/update/out.requests.txt b/acceptance/workspace/repos/update/out.requests.txt index ca982e372de..fa5a518dad8 100644 --- a/acceptance/workspace/repos/update/out.requests.txt +++ b/acceptance/workspace/repos/update/out.requests.txt @@ -11,10 +11,6 @@ "url": "https://github.com/databricks/databricks-empty-ide-project.git" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "PATCH", "path": "/api/2.0/repos/[NUMID]", @@ -22,18 +18,10 @@ "branch": "update-by-id" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/repos/[NUMID]" } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/workspace/get-status", @@ -48,10 +36,6 @@ "branch": "update-by-path" } } -{ - "method": "GET", - "path": "/.well-known/databricks-config" -} { "method": "GET", "path": "/api/2.0/repos/[NUMID]" diff --git a/acceptance/workspace/repos/update/out.test.toml b/acceptance/workspace/repos/update/out.test.toml index d560f1de043..f784a183258 100644 --- a/acceptance/workspace/repos/update/out.test.toml +++ b/acceptance/workspace/repos/update/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/workspace/repos/update/output.txt b/acceptance/workspace/repos/update/output.txt index c15a955f49b..fb5b97bc3a6 100644 --- a/acceptance/workspace/repos/update/output.txt +++ b/acceptance/workspace/repos/update/output.txt @@ -5,20 +5,20 @@ >>> [CLI] repos get [NUMID] -o json { - "branch":"update-by-id", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "update-by-id", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } >>> [CLI] repos update /Repos/me@databricks.com/test-repo --branch update-by-path >>> [CLI] repos get [NUMID] -o json { - "branch":"update-by-path", - "id":[NUMID], - "path":"/Repos/me@databricks.com/test-repo", - "provider":"gitHub", - "url":"https://github.com/databricks/databricks-empty-ide-project.git" + "branch": "update-by-path", + "id": [NUMID], + "path": "/Repos/me@databricks.com/test-repo", + "provider": "gitHub", + "url": "https://github.com/databricks/databricks-empty-ide-project.git" } diff --git a/bundle/appdeploy/app.go b/bundle/appdeploy/app.go index dd0602f670d..4f590be60ac 100644 --- a/bundle/appdeploy/app.go +++ b/bundle/appdeploy/app.go @@ -20,6 +20,10 @@ func logProgress(ctx context.Context, msg string) { // BuildDeployment constructs an AppDeployment from the app's source code path, inline config and git source. func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource *sdkapps.GitSource) sdkapps.AppDeployment { + // GitRepository is not supported in the Deploy API, only as part of Create, so we need to remove it. + if gitSource != nil { + gitSource.GitRepository = nil + } deployment := sdkapps.AppDeployment{ Mode: sdkapps.AppDeploymentModeSnapshot, SourceCodePath: sourcePath, @@ -49,6 +53,7 @@ func BuildDeployment(sourcePath string, config *resources.AppConfig, gitSource * // WaitForDeploymentToComplete waits for active and pending deployments on an app to finish. func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceClient, app *sdkapps.App) error { if app.ActiveDeployment != nil && + app.ActiveDeployment.Status != nil && app.ActiveDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the active deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.ActiveDeployment.DeploymentId, 20*time.Minute, nil) @@ -59,6 +64,7 @@ func WaitForDeploymentToComplete(ctx context.Context, w *databricks.WorkspaceCli } if app.PendingDeployment != nil && + app.PendingDeployment.Status != nil && app.PendingDeployment.Status.State == sdkapps.AppDeploymentStateInProgress { logProgress(ctx, "Waiting for the pending deployment to complete...") _, err := w.Apps.WaitGetDeploymentAppSucceeded(ctx, app.Name, app.PendingDeployment.DeploymentId, 20*time.Minute, nil) diff --git a/bundle/apps/validate.go b/bundle/apps/validate.go index 6c6403e06f4..45a5b49c672 100644 --- a/bundle/apps/validate.go +++ b/bundle/apps/validate.go @@ -46,7 +46,7 @@ func (v *validate) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Duplicate app source code path", - Detail: fmt.Sprintf("app resource '%s' has the same source code path as app resource '%s', this will lead to the app configuration being overriden by each other", key, usedSourceCodePaths[app.SourceCodePath]), + Detail: fmt.Sprintf("app resource '%s' has the same source code path as app resource '%s', this will lead to the app configuration being overridden by each other", key, usedSourceCodePaths[app.SourceCodePath]), Locations: b.Config.GetLocations(fmt.Sprintf("resources.apps.%s.source_code_path", key)), }) } diff --git a/bundle/artifacts/build.go b/bundle/artifacts/build.go index 6d89b8c07cd..38b42dd1ab6 100644 --- a/bundle/artifacts/build.go +++ b/bundle/artifacts/build.go @@ -3,8 +3,10 @@ package artifacts import ( "context" "fmt" + "maps" "os" "path/filepath" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" @@ -15,7 +17,6 @@ import ( "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/patchwheel" - "github.com/databricks/cli/libs/utils" ) func Build() bundle.Mutator { @@ -37,7 +38,7 @@ func (m *build) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { }) } - for _, artifactName := range utils.SortedKeys(b.Config.Artifacts) { + for _, artifactName := range slices.Sorted(maps.Keys(b.Config.Artifacts)) { a := b.Config.Artifacts[artifactName] if a.BuildCommand != "" { @@ -84,18 +85,21 @@ func doBuild(ctx context.Context, artifactName string, a *config.Artifact) error cmdio.LogString(ctx, fmt.Sprintf("Building %s...", artifactName)) var executor *exec.Executor - var err error if a.Executable != "" { + var err error executor, err = exec.NewCommandExecutorWithExecutable(a.Path, a.Executable) + if err != nil { + return err + } } else { + var err error executor, err = exec.NewCommandExecutor(a.Path) + if err != nil { + return err + } a.Executable = executor.ShellType() } - if err != nil { - return err - } - out, err := executor.Exec(ctx, a.BuildCommand) if err != nil { return fmt.Errorf("build failed %s, error: %v, output: %s", artifactName, err, out) diff --git a/bundle/artifacts/prepare.go b/bundle/artifacts/prepare.go index 84e2eefd0e7..9f8b2e6eaed 100644 --- a/bundle/artifacts/prepare.go +++ b/bundle/artifacts/prepare.go @@ -3,8 +3,10 @@ package artifacts import ( "context" "errors" + "maps" "os" "path/filepath" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" @@ -15,7 +17,6 @@ import ( "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/cli/libs/python" - "github.com/databricks/cli/libs/utils" ) func Prepare() bundle.Mutator { @@ -34,7 +35,7 @@ func (m *prepare) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics return diag.FromErr(err) } - for _, artifactName := range utils.SortedKeys(b.Config.Artifacts) { + for _, artifactName := range slices.Sorted(maps.Keys(b.Config.Artifacts)) { artifact := b.Config.Artifacts[artifactName] if artifact == nil { l := b.Config.GetLocation("artifacts." + artifactName) @@ -116,7 +117,7 @@ func InsertPythonArtifact(ctx context.Context, b *bundle.Bundle) error { _, err := os.Stat(setupPy) if err != nil { log.Infof(ctx, "No Python wheel project found at bundle root folder") - return nil + return nil //nolint:nilerr // setup.py not found means no wheel project to detect } log.Infof(ctx, "Found Python wheel project at %s", b.BundleRootPath) diff --git a/bundle/bundle.go b/bundle/bundle.go index 97824eb8396..e7eef14b907 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -8,7 +8,6 @@ package bundle import ( "context" - "errors" "fmt" "os" "path/filepath" @@ -121,11 +120,8 @@ type Bundle struct { // in the WSFS location containing the bundle state. Metadata metadata.Metadata - // Store a pointer to the workspace client. - // It can be initialized on demand after loading the configuration. - clientOnce sync.Once - client *databricks.WorkspaceClient - clientErr error + // Returns the workspace client, initializing it on first call. + getClient func() (*databricks.WorkspaceClient, error) // Files that are synced to the workspace.file_path Files []fileset.File @@ -225,20 +221,25 @@ func TryLoad(ctx context.Context) *Bundle { return b } -func (b *Bundle) WorkspaceClientE() (*databricks.WorkspaceClient, error) { - b.clientOnce.Do(func() { - var err error - b.client, err = b.Config.Workspace.Client() +func (b *Bundle) initClientOnce(ctx context.Context) { + b.getClient = sync.OnceValues(func() (*databricks.WorkspaceClient, error) { + w, err := b.Config.Workspace.Client(ctx) if err != nil { - b.clientErr = fmt.Errorf("cannot resolve bundle auth configuration: %w", err) + return nil, fmt.Errorf("cannot resolve bundle auth configuration: %w", err) } + return w, nil }) +} - return b.client, b.clientErr +func (b *Bundle) WorkspaceClientE(ctx context.Context) (*databricks.WorkspaceClient, error) { + if b.getClient == nil { + b.initClientOnce(ctx) + } + return b.getClient() } -func (b *Bundle) WorkspaceClient() *databricks.WorkspaceClient { - client, err := b.WorkspaceClientE() +func (b *Bundle) WorkspaceClient(ctx context.Context) *databricks.WorkspaceClient { + client, err := b.WorkspaceClientE(ctx) if err != nil { panic(err) } @@ -249,16 +250,15 @@ func (b *Bundle) WorkspaceClient() *databricks.WorkspaceClient { // SetWorkpaceClient sets the workspace client for this bundle. // This is used to inject a mock client for testing. func (b *Bundle) SetWorkpaceClient(w *databricks.WorkspaceClient) { - b.clientOnce.Do(func() {}) - b.client = w + b.getClient = func() (*databricks.WorkspaceClient, error) { + return w, nil + } } // ClearWorkspaceClient resets the workspace client cache, allowing // WorkspaceClientE() to attempt client creation again on the next call. -func (b *Bundle) ClearWorkspaceClient() { - b.clientOnce = sync.Once{} - b.client = nil - b.clientErr = nil +func (b *Bundle) ClearWorkspaceClient(ctx context.Context) { + b.initClientOnce(ctx) } // LocalStateDir returns directory to use for temporary files for this bundle without creating @@ -346,13 +346,13 @@ func (b *Bundle) GetSyncIncludePatterns(ctx context.Context) ([]string, error) { // // This map can be used to configure authentication for tools that // we call into from this bundle context. -func (b *Bundle) AuthEnv() (map[string]string, error) { - if b.client == nil { - return nil, errors.New("workspace client not initialized yet") +func (b *Bundle) AuthEnv(ctx context.Context) (map[string]string, error) { + w, err := b.WorkspaceClientE(ctx) + if err != nil { + return nil, err } - cfg := b.client.Config - return auth.Env(cfg), nil + return auth.Env(w.Config), nil } // StateFilenameDirect returns (relative remote path, relative local path) for direct engine resource state diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index fa8282f343c..37928cb8801 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -179,25 +179,27 @@ func TestBundleGetResourceConfigJobsPointer(t *testing.T) { } func TestClearWorkspaceClient(t *testing.T) { + ctx := t.Context() + // First attempt: profile "profile-A" doesn't exist → error mentions "profile-A". b := &Bundle{} b.Config.Workspace.Host = "https://nonexistent.example.com" b.Config.Workspace.Profile = "profile-A" - _, err1 := b.WorkspaceClientE() + _, err1 := b.WorkspaceClientE(ctx) require.Error(t, err1) assert.Contains(t, err1.Error(), "profile-A") // Without retry, second call returns the same cached error (same object). - _, err1b := b.WorkspaceClientE() + _, err1b := b.WorkspaceClientE(ctx) assert.Same(t, err1, err1b, "expected same cached error without retry") // After retry, change the profile to "profile-B" and call again. // If retry didn't re-execute, the error would still mention "profile-A". - b.ClearWorkspaceClient() + b.ClearWorkspaceClient(ctx) b.Config.Workspace.Profile = "profile-B" - _, err2 := b.WorkspaceClientE() + _, err2 := b.WorkspaceClientE(ctx) require.Error(t, err2) assert.Contains(t, err2.Error(), "profile-B", "expected re-execution to pick up new profile") assert.NotContains(t, err2.Error(), "profile-A", "stale cached error should not appear") diff --git a/bundle/config/loader/process_include.go b/bundle/config/loader/process_include.go index 12be8bb83fe..3a64297814c 100644 --- a/bundle/config/loader/process_include.go +++ b/bundle/config/loader/process_include.go @@ -1,10 +1,10 @@ package loader import ( + "cmp" "context" "fmt" "slices" - "sort" "strings" "github.com/databricks/cli/bundle" @@ -98,7 +98,7 @@ func validateSingleResourceDefined(configRoot dyn.Value, ext, typ string) diag.D lines = append(lines, fmt.Sprintf(" - %s (%s)\n", r.key, r.typ)) } // Sort the lines to print to make the output deterministic. - sort.Strings(lines) + slices.Sort(lines) // Compact the lines before writing them to the message to remove any duplicate lines. // This is needed because we do not dedup earlier when gathering the resources // and it's valid to define the same resource in both the resources and targets block. @@ -114,11 +114,11 @@ func validateSingleResourceDefined(configRoot dyn.Value, ext, typ string) diag.D paths = append(paths, rr.path) } // Sort the locations and paths to make the output deterministic. - sort.Slice(locations, func(i, j int) bool { - return locations[i].String() < locations[j].String() + slices.SortFunc(locations, func(a, b dyn.Location) int { + return cmp.Compare(a.String(), b.String()) }) - sort.Slice(paths, func(i, j int) bool { - return paths[i].String() < paths[j].String() + slices.SortFunc(paths, func(a, b dyn.Path) int { + return cmp.Compare(a.String(), b.String()) }) return diag.Diagnostics{ diff --git a/bundle/config/mutator/configure_wsfs.go b/bundle/config/mutator/configure_wsfs.go index 1a5b74f39c1..a93fba9e052 100644 --- a/bundle/config/mutator/configure_wsfs.go +++ b/bundle/config/mutator/configure_wsfs.go @@ -51,7 +51,7 @@ func (m *configureWSFS) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno // If so, swap out vfs.Path instance of the sync root with one that // makes all Workspace File System interactions extension aware. p, err := vfs.NewFilerPath(ctx, root, func(path string) (filer.Filer, error) { - return filer.NewReadOnlyWorkspaceFilesExtensionsClient(ctx, b.WorkspaceClient(), path) + return filer.NewReadOnlyWorkspaceFilesExtensionsClient(ctx, b.WorkspaceClient(ctx), path) }) if err != nil { return diag.FromErr(err) diff --git a/bundle/config/mutator/initialize_urls.go b/bundle/config/mutator/initialize_urls.go index 35ff53d0b62..ea69a38ce1c 100644 --- a/bundle/config/mutator/initialize_urls.go +++ b/bundle/config/mutator/initialize_urls.go @@ -25,12 +25,12 @@ func (m *initializeURLs) Name() string { } func (m *initializeURLs) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - workspaceId, err := b.WorkspaceClient().CurrentWorkspaceID(ctx) + workspaceId, err := b.WorkspaceClient(ctx).CurrentWorkspaceID(ctx) if err != nil { return diag.FromErr(err) } orgId := strconv.FormatInt(workspaceId, 10) - host := b.WorkspaceClient().Config.CanonicalHostName() + host := b.WorkspaceClient(ctx).Config.CanonicalHostName() err = initializeForWorkspace(b, orgId, host) if err != nil { return diag.FromErr(err) diff --git a/bundle/config/mutator/load_git_details.go b/bundle/config/mutator/load_git_details.go index e4f52ea4c75..bbb6ff147e8 100644 --- a/bundle/config/mutator/load_git_details.go +++ b/bundle/config/mutator/load_git_details.go @@ -24,7 +24,7 @@ func (m *loadGitDetails) Name() string { func (m *loadGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { var diags diag.Diagnostics - info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot.Native(), b.WorkspaceClient()) + info, err := git.FetchRepositoryInfo(ctx, b.BundleRoot.Native(), b.WorkspaceClient(ctx)) if err != nil { if !errors.Is(err, os.ErrNotExist) { diags = append(diags, diag.WarningFromErr(err)...) diff --git a/bundle/config/mutator/mutator.go b/bundle/config/mutator/mutator.go index d589b5baf3b..a00488a50fa 100644 --- a/bundle/config/mutator/mutator.go +++ b/bundle/config/mutator/mutator.go @@ -28,8 +28,9 @@ func DefaultMutators(ctx context.Context, b *bundle.Bundle) { InitializeVariables(), DefineDefaultTarget(), - // Note: This mutator must run before the target overrides are merged. - // See the mutator for more details. + // Note: These mutators must run before the target overrides are merged. + // See the mutators for more details. + validate.NoVariableReferenceInResourceKey(), validate.UniqueResourceKeys(), ) } diff --git a/bundle/config/mutator/normalize_paths.go b/bundle/config/mutator/normalize_paths.go index 716da0bb0e7..ecab0e812db 100644 --- a/bundle/config/mutator/normalize_paths.go +++ b/bundle/config/mutator/normalize_paths.go @@ -7,6 +7,7 @@ import ( "net/url" pathlib "path" "path/filepath" + "slices" "strings" "github.com/databricks/cli/bundle" @@ -44,10 +45,8 @@ func (a normalizePaths) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnost err := b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { return paths.VisitPaths(v, func(path dyn.Path, kind paths.TranslateMode, v dyn.Value) (dyn.Value, error) { - for _, gitSourcePrefix := range gitSourcePaths { - if path.HasPrefix(gitSourcePrefix) { - return v, nil - } + if slices.ContainsFunc(gitSourcePaths, path.HasPrefix) { + return v, nil } value, ok := v.AsString() diff --git a/bundle/config/mutator/paths/job_paths_visitor.go b/bundle/config/mutator/paths/job_paths_visitor.go index 99799d6a80c..49011e4b1e7 100644 --- a/bundle/config/mutator/paths/job_paths_visitor.go +++ b/bundle/config/mutator/paths/job_paths_visitor.go @@ -36,6 +36,11 @@ func jobTaskRewritePatterns(base dyn.Pattern) []jobRewritePattern { TranslateModeFile, noSkipRewrite, }, + { + base.Append(dyn.Key("alert_task"), dyn.Key("workspace_path")), + TranslateModeFile, + noSkipRewrite, + }, { base.Append(dyn.Key("libraries"), dyn.AnyIndex(), dyn.Key("requirements")), TranslateModeFile, diff --git a/bundle/config/mutator/paths/job_paths_visitor_test.go b/bundle/config/mutator/paths/job_paths_visitor_test.go index 0c074d97982..6e6687c265f 100644 --- a/bundle/config/mutator/paths/job_paths_visitor_test.go +++ b/bundle/config/mutator/paths/job_paths_visitor_test.go @@ -49,6 +49,11 @@ func TestVisitJobPaths(t *testing.T) { {Requirements: "requirements.txt"}, }, } + task7 := jobs.Task{ + AlertTask: &jobs.AlertTask{ + WorkspacePath: "abc", + }, + } job0 := &resources.Job{ JobSettings: jobs.JobSettings{ @@ -60,6 +65,7 @@ func TestVisitJobPaths(t *testing.T) { task4, task5, task6, + task7, }, }, } @@ -79,6 +85,7 @@ func TestVisitJobPaths(t *testing.T) { dyn.MustPathFromString("resources.jobs.job0.tasks[2].dbt_task.project_directory"), dyn.MustPathFromString("resources.jobs.job0.tasks[3].sql_task.file.path"), dyn.MustPathFromString("resources.jobs.job0.tasks[6].libraries[0].requirements"), + dyn.MustPathFromString("resources.jobs.job0.tasks[7].alert_task.workspace_path"), } assert.ElementsMatch(t, expected, actual) @@ -125,10 +132,20 @@ func TestVisitJobPaths_foreach(t *testing.T) { }, }, } + task1 := jobs.Task{ + ForEachTask: &jobs.ForEachTask{ + Task: jobs.Task{ + AlertTask: &jobs.AlertTask{ + WorkspacePath: "abc", + }, + }, + }, + } job0 := &resources.Job{ JobSettings: jobs.JobSettings{ Tasks: []jobs.Task{ task0, + task1, }, }, } @@ -144,6 +161,7 @@ func TestVisitJobPaths_foreach(t *testing.T) { actual := collectVisitedPaths(t, root, VisitJobPaths) expected := []dyn.Path{ dyn.MustPathFromString("resources.jobs.job0.tasks[0].for_each_task.task.notebook_task.notebook_path"), + dyn.MustPathFromString("resources.jobs.job0.tasks[1].for_each_task.task.alert_task.workspace_path"), } assert.ElementsMatch(t, expected, actual) diff --git a/bundle/config/mutator/populate_current_user.go b/bundle/config/mutator/populate_current_user.go index 0088a024516..2a3cc492829 100644 --- a/bundle/config/mutator/populate_current_user.go +++ b/bundle/config/mutator/populate_current_user.go @@ -27,7 +27,7 @@ func (m *populateCurrentUser) Apply(ctx context.Context, b *bundle.Bundle) diag. if b.Config.Workspace.CurrentUser != nil { return nil } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) fingerprint := b.GetUserFingerprint(ctx) me, err := cache.GetOrCompute(ctx, b.Cache, fingerprint, func(ctx context.Context) (*iam.User, error) { diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index c20e172c00f..ed221c00c6b 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -18,8 +18,8 @@ import ( "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/databricks-sdk-go/logger" - "github.com/fatih/color" "github.com/databricks/cli/libs/python" @@ -104,6 +104,7 @@ type runPythonMutatorOpts struct { bundleRootPath string pythonPath string loadLocations bool + authEnv map[string]string } // getOpts adapts deprecated PyDABs and upcoming Python configuration @@ -217,6 +218,15 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno return diag.Errorf("Running Python code is not allowed when DATABRICKS_BUNDLE_RESTRICTED_CODE_EXECUTION is set") } + // Propagate auth env so the Databricks SDK in the Python subprocess uses the + // same credentials as the CLI. In particular this carries DATABRICKS_CONFIG_PROFILE, + // which lets the CLI disambiguate profiles sharing the same host when the SDK + // re-invokes `databricks auth token --host `. + authEnv, err := b.AuthEnv(ctx) + if err != nil { + return diag.FromErr(err) + } + // mutateDiags is used because Mutate returns 'error' instead of 'diag.Diagnostics' var mutateDiags diag.Diagnostics var result applyPythonOutputResult @@ -238,6 +248,7 @@ func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagno bundleRootPath: b.BundleRootPath, pythonPath: pythonPath, loadLocations: opts.loadLocations, + authEnv: authEnv, }) mutateDiags = diags if diags.HasError() { @@ -364,6 +375,7 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op process.WithDir(opts.bundleRootPath), process.WithStderrWriter(stderrWriter), process.WithStdoutWriter(stdoutWriter), + process.WithEnvs(opts.authEnv), ) if processErr != nil { logger.Debugf(ctx, "python mutator process failed: %s", processErr) @@ -386,7 +398,7 @@ func (m *pythonMutator) runPythonMutator(ctx context.Context, root dyn.Value, op diagnostic := diag.Diagnostic{ Severity: diag.Error, Summary: fmt.Sprintf("python mutator process failed: %q, use --debug to enable logging", processErr), - Detail: explainProcessErr(stderrBuf.String()), + Detail: explainProcessErr(ctx, stderrBuf.String()), } return dyn.InvalidValue, diag.Diagnostics{diagnostic} @@ -424,10 +436,10 @@ or activate the environment before running CLI commands: // explainProcessErr provides additional explanation for common errors. // It's meant to be the best effort, and not all errors are covered. // Output should be used only used for error reporting. -func explainProcessErr(stderr string) string { +func explainProcessErr(ctx context.Context, stderr string) string { // implemented in cpython/Lib/runpy.py and portable across Python 3.x, including pypy if strings.Contains(stderr, "Error while finding module specification for 'databricks.bundles.build'") { - summary := color.CyanString("Explanation: ") + "'databricks-bundles' library is not installed in the Python environment.\n" + summary := cmdio.Cyan(ctx, "Explanation: ") + "'databricks-bundles' library is not installed in the Python environment.\n" return stderr + "\n" + summary + "\n" + pythonInstallExplanation } diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index d1efcac71e4..cf81da5f78c 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -3,10 +3,12 @@ package python import ( "context" "fmt" + "maps" "os" "os/exec" "path/filepath" "runtime" + "slices" "testing" "github.com/databricks/cli/libs/dyn/convert" @@ -14,12 +16,11 @@ import ( "github.com/databricks/cli/bundle/env" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" - "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/process" "github.com/stretchr/testify/assert" ) @@ -103,7 +104,7 @@ workspace: { current_user: { userName: test }}`) assert.NoError(t, diags.Error()) - assert.ElementsMatch(t, []string{"job0", "job1"}, maps.Keys(b.Config.Resources.Jobs)) + assert.ElementsMatch(t, []string{"job0", "job1"}, slices.Collect(maps.Keys(b.Config.Resources.Jobs))) if job0, ok := b.Config.Resources.Jobs["job0"]; ok { assert.Equal(t, "job_0", job0.Name) @@ -212,7 +213,7 @@ resources: assert.NoError(t, diag.Error()) - assert.ElementsMatch(t, []string{"job0"}, maps.Keys(b.Config.Resources.Jobs)) + assert.ElementsMatch(t, []string{"job0"}, slices.Collect(maps.Keys(b.Config.Resources.Jobs))) assert.Equal(t, "job_0", b.Config.Resources.Jobs["job0"].Name) assert.Equal(t, "my job", b.Config.Resources.Jobs["job0"].Description) @@ -488,7 +489,7 @@ or activate the environment before running CLI commands: venv_path: .venv ` - out := explainProcessErr(stderr) + out := explainProcessErr(cmdio.MockDiscard(t.Context()), stderr) assert.Equal(t, expected, out) } diff --git a/bundle/config/mutator/resolve_lookup_variables.go b/bundle/config/mutator/resolve_lookup_variables.go index 1074df2099b..e642997eedf 100644 --- a/bundle/config/mutator/resolve_lookup_variables.go +++ b/bundle/config/mutator/resolve_lookup_variables.go @@ -31,7 +31,7 @@ func (m *resolveLookupVariables) Apply(ctx context.Context, b *bundle.Bundle) di } errs.Go(func() error { - id, err := v.Lookup.Resolve(errCtx, b.WorkspaceClient()) + id, err := v.Lookup.Resolve(errCtx, b.WorkspaceClient(errCtx)) if err != nil { return fmt.Errorf("failed to resolve %s, err: %w", v.Lookup, err) } diff --git a/bundle/config/mutator/resolve_variable_references.go b/bundle/config/mutator/resolve_variable_references.go index 9868b4fb95b..113f0576394 100644 --- a/bundle/config/mutator/resolve_variable_references.go +++ b/bundle/config/mutator/resolve_variable_references.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "github.com/databricks/cli/libs/dyn/merge" @@ -227,12 +228,10 @@ func (m *resolveVariableReferences) resolveOnce(b *bundle.Bundle, prefixes []dyn } // Perform resolution only if the path starts with one of the specified prefixes. - for _, prefix := range prefixes { - if path.HasPrefix(prefix) { - value, err := m.lookupFn(normalized, path, b) - hasUpdates = hasUpdates || (err == nil && value.IsValid()) - return value, err - } + if slices.ContainsFunc(prefixes, path.HasPrefix) { + value, err := m.lookupFn(normalized, path, b) + hasUpdates = hasUpdates || (err == nil && value.IsValid()) + return value, err } return dyn.InvalidValue, dynvar.ErrSkipResolution diff --git a/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go index 73ce556868e..fd019479d77 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go @@ -18,7 +18,8 @@ import ( var ( allowedLevels = []string{permissions.CAN_MANAGE, permissions.CAN_VIEW, permissions.CAN_RUN} - levelsMap = map[string](map[string]string){ + // Map of allowed permission levels to the corresponding permission level of specific resources + levelsMap = map[string](map[string]string){ "jobs": { permissions.CAN_MANAGE: "CAN_MANAGE", permissions.CAN_VIEW: "CAN_VIEW", @@ -78,6 +79,11 @@ var ( permissions.CAN_VIEW: "CAN_ATTACH_TO", permissions.CAN_RUN: "CAN_RESTART", }, + "vector_search_endpoints": { + // https://docs.databricks.com/aws/en/security/auth/access-control/#vector-search-endpoint-acls + permissions.CAN_MANAGE: "CAN_MANAGE", + permissions.CAN_VIEW: "CAN_USE", + }, } ) diff --git a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go index ba121130348..e472241f282 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go @@ -34,7 +34,7 @@ func TestApplyBundlePermissions(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Users/foo@bar.com", + RootPath: "/Users/foo@bar.test", }, Permissions: []resources.Permission{ {Level: permissions.CAN_MANAGE, UserName: "TestUser"}, @@ -78,6 +78,10 @@ func TestApplyBundlePermissions(t *testing.T) { "app_1": {}, "app_2": {}, }, + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "vs_1": {}, + "vs_2": {}, + }, }, }, } @@ -138,13 +142,21 @@ func TestApplyBundlePermissions(t *testing.T) { require.Len(t, b.Config.Resources.Apps["app_1"].Permissions, 2) require.Contains(t, b.Config.Resources.Apps["app_1"].Permissions, resources.AppPermission{Level: "CAN_MANAGE", UserName: "TestUser"}) require.Contains(t, b.Config.Resources.Apps["app_1"].Permissions, resources.AppPermission{Level: "CAN_USE", GroupName: "TestGroup"}) + + require.Len(t, b.Config.Resources.VectorSearchEndpoints["vs_1"].Permissions, 2) + require.Contains(t, b.Config.Resources.VectorSearchEndpoints["vs_1"].Permissions, resources.Permission{Level: "CAN_MANAGE", UserName: "TestUser"}) + require.Contains(t, b.Config.Resources.VectorSearchEndpoints["vs_1"].Permissions, resources.Permission{Level: "CAN_USE", GroupName: "TestGroup"}) + + require.Len(t, b.Config.Resources.VectorSearchEndpoints["vs_2"].Permissions, 2) + require.Contains(t, b.Config.Resources.VectorSearchEndpoints["vs_2"].Permissions, resources.Permission{Level: "CAN_MANAGE", UserName: "TestUser"}) + require.Contains(t, b.Config.Resources.VectorSearchEndpoints["vs_2"].Permissions, resources.Permission{Level: "CAN_USE", GroupName: "TestGroup"}) } func TestWarningOnOverlapPermission(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Users/foo@bar.com", + RootPath: "/Users/foo@bar.test", }, Permissions: []resources.Permission{ {Level: permissions.CAN_MANAGE, UserName: "TestUser"}, diff --git a/bundle/config/mutator/resourcemutator/apply_presets.go b/bundle/config/mutator/resourcemutator/apply_presets.go index 8749103b476..83d512ca518 100644 --- a/bundle/config/mutator/resourcemutator/apply_presets.go +++ b/bundle/config/mutator/resourcemutator/apply_presets.go @@ -1,10 +1,10 @@ package resourcemutator import ( + "cmp" "context" "path" "slices" - "sort" "strings" "github.com/databricks/cli/bundle" @@ -290,6 +290,10 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos } } + // Vector Search Endpoints: no prefix. The endpoint name is the primary key + // (it's what GET/UPDATE/DELETE address by), so prefixing it would change + // the resource's identity rather than just its display name. + return diags } @@ -315,8 +319,8 @@ func toTagArray(tags map[string]string) []Tag { for key, value := range tags { tagArray = append(tagArray, Tag{Key: key, Value: value}) } - sort.Slice(tagArray, func(i, j int) bool { - return tagArray[i].Key < tagArray[j].Key + slices.SortFunc(tagArray, func(a, b Tag) int { + return cmp.Compare(a.Key, b.Key) }) return tagArray } diff --git a/bundle/config/mutator/resourcemutator/apply_target_mode.go b/bundle/config/mutator/resourcemutator/apply_target_mode.go index e7ee0324dc7..727a2e5bef0 100644 --- a/bundle/config/mutator/resourcemutator/apply_target_mode.go +++ b/bundle/config/mutator/resourcemutator/apply_target_mode.go @@ -23,7 +23,7 @@ func (m *applyTargetMode) Name() string { } // Mark all resources as being for 'development' purposes, i.e. -// changing their their name, adding tags, and (in the future) +// changing their name, adding tags, and (in the future) // marking them as 'hidden' in the UI. func transformDevelopmentMode(ctx context.Context, b *bundle.Bundle) { if !b.Config.Bundle.Deployment.Lock.IsExplicitlyEnabled() { diff --git a/bundle/config/mutator/resourcemutator/apply_target_mode_test.go b/bundle/config/mutator/resourcemutator/apply_target_mode_test.go index 804552f56a2..fe9c9a1db06 100644 --- a/bundle/config/mutator/resourcemutator/apply_target_mode_test.go +++ b/bundle/config/mutator/resourcemutator/apply_target_mode_test.go @@ -23,6 +23,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/postgres" "github.com/databricks/databricks-sdk-go/service/serving" "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -246,6 +247,14 @@ func mockBundle(mode config.Mode) *bundle.Bundle { }, }, }, + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "vs_endpoint1": { + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "vs_endpoint1", + EndpointType: vectorsearch.EndpointTypeStandard, + }, + }, + }, }, }, SyncRoot: vfs.MustNew("/Users/lennart.kats@databricks.com"), @@ -294,6 +303,9 @@ func TestProcessTargetModeDevelopment(t *testing.T) { // Model serving endpoint 1 assert.Equal(t, "dev_lennart_servingendpoint1", b.Config.Resources.ModelServingEndpoints["servingendpoint1"].Name) + // Vector search endpoint 1: name is the primary key, so it must not be prefixed. + assert.Equal(t, "vs_endpoint1", b.Config.Resources.VectorSearchEndpoints["vs_endpoint1"].Name) + // Registered model 1 assert.Equal(t, "dev_lennart_registeredmodel1", b.Config.Resources.RegisteredModels["registeredmodel1"].Name) @@ -402,17 +414,33 @@ func TestAllResourcesMocked(t *testing.T) { } } -// Make sure that we at rename all non UC resources -func TestAllNonUcResourcesAreRenamed(t *testing.T) { +// TestAppropriateResourcesAreRenamed checks that every resource with a user-facing +// Name field is renamed by dev-mode / presets.name_prefix, except for an +// explicit carve-out list. The carve-out applies to resources whose Name is +// the API primary key / object id (not a display name) — prefixing those +// would change the resource's identity rather than its label. +func TestAppropriateResourcesAreRenamed(t *testing.T) { b := mockBundle(config.Development) - // UC resources should not have a prefix added to their name. Right now - // this list only contains the Volume, Catalog, and ExternalLocation resources since we have yet to remove - // prefixing support for UC schemas and registered models. - ucFields := []reflect.Type{ - reflect.TypeOf(&resources.Catalog{}), - reflect.TypeOf(&resources.ExternalLocation{}), - reflect.TypeOf(&resources.Volume{}), + notRenamedFields := []reflect.Type{ + reflect.TypeFor[*resources.Catalog](), + reflect.TypeFor[*resources.ExternalLocation](), + reflect.TypeFor[*resources.Volume](), + reflect.TypeFor[*resources.VectorSearchEndpoint](), + } + + // Resources whose Name is server-generated or otherwise not a user-facing + // label, so the rename matrix doesn't apply. Reflection still finds a + // Name field on these via embedded SDK types, hence the explicit skip. + notUserNamed := []string{ + "Apps", + "SecretScopes", + "DatabaseInstances", + "DatabaseCatalogs", + "SyncedDatabaseTables", + "PostgresProjects", + "PostgresBranches", + "PostgresEndpoints", } diags := bundle.ApplySeq(t.Context(), b, ApplyTargetMode(), ApplyPresets()) @@ -421,28 +449,24 @@ func TestAllNonUcResourcesAreRenamed(t *testing.T) { resources := reflect.ValueOf(b.Config.Resources) for i := range resources.NumField() { field := resources.Field(i) + if field.Kind() != reflect.Map { + continue + } + resourceType := resources.Type().Field(i).Name + if slices.Contains(notUserNamed, resourceType) { + continue + } + for _, key := range field.MapKeys() { + resource := field.MapIndex(key) + nameField := resource.Elem().FieldByName("Name") + if !nameField.IsValid() || nameField.Kind() != reflect.String { + continue + } - if field.Kind() == reflect.Map { - for _, key := range field.MapKeys() { - resource := field.MapIndex(key) - nameField := resource.Elem().FieldByName("Name") - resourceType := resources.Type().Field(i).Name - - // Skip resources that are not renamed (either because they don't have a user-facing Name field, - // or because their Name is server-generated rather than user-specified) - if resourceType == "Apps" || resourceType == "SecretScopes" || resourceType == "DatabaseInstances" || resourceType == "DatabaseCatalogs" || resourceType == "SyncedDatabaseTables" || resourceType == "PostgresProjects" || resourceType == "PostgresBranches" || resourceType == "PostgresEndpoints" { - continue - } - - if !nameField.IsValid() || nameField.Kind() != reflect.String { - continue - } - - if slices.Contains(ucFields, resource.Type()) { - assert.NotContains(t, nameField.String(), "dev", "process_target_mode should not rename '%s' in '%s'", key, resources.Type().Field(i).Name) - } else { - assert.Contains(t, nameField.String(), "dev", "process_target_mode should rename '%s' in '%s'", key, resources.Type().Field(i).Name) - } + if slices.Contains(notRenamedFields, resource.Type()) { + assert.NotContains(t, nameField.String(), "dev", "process_target_mode should not rename '%s' in '%s'", key, resourceType) + } else { + assert.Contains(t, nameField.String(), "dev", "process_target_mode should rename '%s' in '%s'", key, resourceType) } } } diff --git a/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go b/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go index 92d22333e70..61c2fed2592 100644 --- a/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go +++ b/bundle/config/mutator/resourcemutator/capture_uc_dependencies.go @@ -12,7 +12,7 @@ import ( type captureUCDependencies struct{} -// If a user defines a UC schema in the bundle, they can refer to it in DLT pipelines, +// If a user defines a UC schema in the bundle, they can refer to it in SDP pipelines, // UC Volumes, Registered Models, Quality Monitors, or Model Serving Endpoints using the // `${resources.schemas..name}` syntax. Using this syntax allows TF to capture // the deploy time dependency this resource has on the schema and deploy changes to the @@ -110,7 +110,7 @@ func (m *captureUCDependencies) Apply(ctx context.Context, b *bundle.Bundle) dia if p == nil { continue } - // "schema" and "target" have the same semantics in the DLT API but are mutually + // "schema" and "target" have the same semantics in the SDP API but are mutually // exclusive i.e. only one can be set at a time. p.Schema = resolveSchema(b, p.Catalog, p.Schema) p.Target = resolveSchema(b, p.Catalog, p.Target) diff --git a/bundle/config/mutator/resourcemutator/resource_mutator.go b/bundle/config/mutator/resourcemutator/resource_mutator.go index 9616de202a6..2eb292cfbb0 100644 --- a/bundle/config/mutator/resourcemutator/resource_mutator.go +++ b/bundle/config/mutator/resourcemutator/resource_mutator.go @@ -116,8 +116,8 @@ func applyInitializeMutators(ctx context.Context, b *bundle.Bundle) { DashboardFixups(), // Reads (typed): b.Config.Permissions (validates permission levels) - // Reads (dynamic): resources.{jobs,pipelines,experiments,models,model_serving_endpoints,dashboards,apps}.*.permissions (reads existing permissions) - // Updates (dynamic): resources.{jobs,pipelines,experiments,models,model_serving_endpoints,dashboards,apps}.*.permissions (adds permissions from bundle-level configuration) + // Reads (dynamic): resources.{jobs,pipelines,experiments,models,model_serving_endpoints,dashboards,apps,vector_search_endpoints,...}.*.permissions (reads existing permissions) + // Updates (dynamic): resources.{jobs,pipelines,experiments,models,model_serving_endpoints,dashboards,apps,vector_search_endpoints,...}.*.permissions (adds permissions from bundle-level configuration) // Applies bundle-level permissions to all supported resources ApplyBundlePermissions(), diff --git a/bundle/config/mutator/resourcemutator/run_as.go b/bundle/config/mutator/resourcemutator/run_as.go index 7360048213b..4f5e3ce9036 100644 --- a/bundle/config/mutator/resourcemutator/run_as.go +++ b/bundle/config/mutator/resourcemutator/run_as.go @@ -103,12 +103,17 @@ func validateRunAs(b *bundle.Bundle) diag.Diagnostics { // Dashboards do not support run_as in the API. if len(b.Config.Resources.Dashboards) > 0 { - diags = diags.Extend(reportRunAsNotSupported( - "dashboards", - b.Config.GetLocation("resources.dashboards"), - b.Config.Workspace.CurrentUser.UserName, - identity, - )) + for key, dashboard := range b.Config.Resources.Dashboards { + if !dashboard.EmbedCredentials { + continue + } + diags = diags.Extend(reportRunAsNotSupported( + "dashboards with embed_credentials set to true", + b.Config.GetLocation("resources.dashboards."+key), + b.Config.Workspace.CurrentUser.UserName, + identity, + )) + } } // Apps do not support run_as in the API. @@ -178,7 +183,7 @@ func setRunAsForAlerts(b *bundle.Bundle) { } } -// Legacy behavior of run_as for DLT pipelines. Available under the experimental.use_run_as_legacy flag. +// Legacy behavior of run_as for SDP pipelines. Available under the experimental.use_run_as_legacy flag. // Only available to unblock customers stuck due to breaking changes in https://github.com/databricks/cli/pull/1233 func setPipelineOwnersToRunAsIdentity(b *bundle.Bundle) { runAs := b.Config.RunAs @@ -228,7 +233,7 @@ func (m *setRunAs) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { return diag.Diagnostics{ { Severity: diag.Warning, - Summary: "You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the DLT pipelines in your DAB as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.", + Summary: "You are using the legacy mode of run_as. The support for this mode is experimental and might be removed in a future release of the CLI. In order to run the pipelines in your DABs project as the run_as user this mode changes the owners of the pipelines to the run_as identity, which requires the user deploying the bundle to be a workspace admin, and also a Metastore admin if the pipeline target is in UC.", Paths: []dyn.Path{dyn.MustPathFromString("experimental.use_legacy_run_as")}, Locations: b.Config.GetLocations("experimental.use_legacy_run_as"), }, diff --git a/bundle/config/mutator/resourcemutator/run_as_test.go b/bundle/config/mutator/resourcemutator/run_as_test.go index 9d59615201f..0b7003f5873 100644 --- a/bundle/config/mutator/resourcemutator/run_as_test.go +++ b/bundle/config/mutator/resourcemutator/run_as_test.go @@ -54,6 +54,7 @@ func allResourceTypes(t *testing.T) []string { "secret_scopes", "sql_warehouses", "synced_database_tables", + "vector_search_endpoints", "volumes", }, resourceTypes, @@ -164,6 +165,7 @@ var allowList = []string{ "alerts", "catalogs", "clusters", + "dashboards", "database_catalogs", "database_instances", "external_locations", @@ -179,6 +181,7 @@ var allowList = []string{ "schemas", "secret_scopes", "sql_warehouses", + "vector_search_endpoints", "volumes", } diff --git a/bundle/config/mutator/rewrite_workspace_prefix.go b/bundle/config/mutator/rewrite_workspace_prefix.go index 0ccb3314b95..e66482f8e55 100644 --- a/bundle/config/mutator/rewrite_workspace_prefix.go +++ b/bundle/config/mutator/rewrite_workspace_prefix.go @@ -12,7 +12,7 @@ import ( type rewriteWorkspacePrefix struct{} -// RewriteWorkspacePrefix finds any strings in bundle configration that have +// RewriteWorkspacePrefix finds any strings in bundle configuration that have // workspace prefix plus workspace path variable used and removes workspace prefix from it. func RewriteWorkspacePrefix() bundle.Mutator { return &rewriteWorkspacePrefix{} diff --git a/bundle/config/mutator/select_default_target.go b/bundle/config/mutator/select_default_target.go index 7486cef81f5..ad8132a46aa 100644 --- a/bundle/config/mutator/select_default_target.go +++ b/bundle/config/mutator/select_default_target.go @@ -2,11 +2,12 @@ package mutator import ( "context" + "maps" + "slices" "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "golang.org/x/exp/maps" ) type selectDefaultTarget struct{} @@ -26,7 +27,7 @@ func (m *selectDefaultTarget) Apply(ctx context.Context, b *bundle.Bundle) diag. } // One target means there's only one default. - names := maps.Keys(b.Config.Targets) + names := slices.Collect(maps.Keys(b.Config.Targets)) if len(names) == 1 { bundle.ApplyContext(ctx, b, SelectTarget(names[0])) return nil diff --git a/bundle/config/mutator/select_target.go b/bundle/config/mutator/select_target.go index 9ee0f9541fc..43764a9ee38 100644 --- a/bundle/config/mutator/select_target.go +++ b/bundle/config/mutator/select_target.go @@ -3,11 +3,12 @@ package mutator import ( "context" "fmt" + "maps" + "slices" "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" - "golang.org/x/exp/maps" ) type selectTarget struct { @@ -34,7 +35,7 @@ func (m *selectTarget) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnosti // Get specified target target, ok := b.Config.Targets[m.name] if !ok { - return diag.Errorf("%s: no such target. Available targets: %s", m.name, strings.Join(maps.Keys(b.Config.Targets), ", ")) + return diag.Errorf("%s: no such target. Available targets: %s", m.name, strings.Join(slices.Collect(maps.Keys(b.Config.Targets)), ", ")) } // Merge specified target into root configuration structure. diff --git a/bundle/config/mutator/set_variables_test.go b/bundle/config/mutator/set_variables_test.go index e69bbf34c30..ecc67548017 100644 --- a/bundle/config/mutator/set_variables_test.go +++ b/bundle/config/mutator/set_variables_test.go @@ -122,7 +122,7 @@ func TestSetVariablesMutator(t *testing.T) { Default: defaultValForA, }, "b": { - Description: "resolved from environment vairables", + Description: "resolved from environment variables", Default: defaultValForB, }, "c": { diff --git a/bundle/config/mutator/translate_paths.go b/bundle/config/mutator/translate_paths.go index cd35dfa0421..99dd75dd787 100644 --- a/bundle/config/mutator/translate_paths.go +++ b/bundle/config/mutator/translate_paths.go @@ -47,6 +47,14 @@ func (err ErrIsNotNotebook) Error() string { return fmt.Sprintf("file at %s is not a notebook", err.path) } +// seenKey is the cache key for the seen map in translateContext. +// It includes both the local path and the translation mode to prevent +// cross-mode cache collisions (e.g. artifact vs. workspace path translations). +type seenKey struct { + path string + mode paths.TranslateMode +} + type translatePaths struct{} type translatePathsDashboards struct{} @@ -76,9 +84,9 @@ func (m *translatePathsDashboards) Name() string { type translateContext struct { b *bundle.Bundle - // seen is a map of local paths to their corresponding remote paths. - // If a local path has already been successfully resolved, we do not need to resolve it again. - seen map[string]string + // seen is a map of (local path, translation mode) pairs to their corresponding remote paths. + // If a local path has already been successfully resolved for a given mode, we do not need to resolve it again. + seen map[seenKey]string // remoteRoot is the root path of the remote workspace. // It is equal to ${workspace.file_path} for regular deployments. @@ -135,7 +143,8 @@ func (t *translateContext) rewritePath( // Local path is relative to the directory the resource was defined in. localPath := filepath.Join(dir, input) - if interp, ok := t.seen[localPath]; ok { + key := seenKey{path: localPath, mode: opts.Mode} + if interp, ok := t.seen[key]; ok { return interp, nil } @@ -181,7 +190,7 @@ func (t *translateContext) rewritePath( return "", err } - t.seen[localPath] = interp + t.seen[key] = interp return interp, nil } @@ -337,7 +346,7 @@ func applyTranslations(ctx context.Context, b *bundle.Bundle, t *translateContex func (m *translatePaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { t := &translateContext{ b: b, - seen: make(map[string]string), + seen: make(map[seenKey]string), skipLocalFileValidation: b.SkipLocalFileValidation, } @@ -354,7 +363,7 @@ func (m *translatePaths) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagn func (m *translatePathsDashboards) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { t := &translateContext{ b: b, - seen: make(map[string]string), + seen: make(map[seenKey]string), skipLocalFileValidation: b.SkipLocalFileValidation, } diff --git a/bundle/config/mutator/translate_paths_test.go b/bundle/config/mutator/translate_paths_test.go index 8776459c57a..bc2d1217909 100644 --- a/bundle/config/mutator/translate_paths_test.go +++ b/bundle/config/mutator/translate_paths_test.go @@ -290,6 +290,7 @@ func TestTranslatePathsInSubdirectories(t *testing.T) { touchEmptyFile(t, filepath.Join(dir, "pipeline", "my_python_file.py")) touchEmptyFile(t, filepath.Join(dir, "job", "my_sql_file.sql")) touchEmptyFile(t, filepath.Join(dir, "job", "my_dbt_project", "dbt_project.yml")) + touchEmptyFile(t, filepath.Join(dir, "job", "my_alert.dbalert.json")) b := &bundle.Bundle{ SyncRootPath: dir, @@ -329,6 +330,11 @@ func TestTranslatePathsInSubdirectories(t *testing.T) { ProjectDirectory: "./my_dbt_project", }, }, + { + AlertTask: &jobs.AlertTask{ + WorkspacePath: "./my_alert.dbalert.json", + }, + }, }, }, }, @@ -376,6 +382,11 @@ func TestTranslatePathsInSubdirectories(t *testing.T) { "/bundle/job/my_dbt_project", b.Config.Resources.Jobs["job"].Tasks[3].DbtTask.ProjectDirectory, ) + assert.Equal( + t, + "/bundle/job/my_alert.dbalert.json", + b.Config.Resources.Jobs["job"].Tasks[4].AlertTask.WorkspacePath, + ) assert.Equal( t, @@ -766,10 +777,10 @@ func TestTranslatePathJobEnvironments(t *testing.T) { "./dist/env1.whl", "../dist/env2.whl", "simplejson", - "/Workspace/Users/foo@bar.com/test.whl", + "/Workspace/Users/foo@bar.test/test.whl", "--extra-index-url https://name:token@gitlab.com/api/v4/projects/9876/packages/pypi/simple foobar", "foobar --extra-index-url https://name:token@gitlab.com/api/v4/projects/9876/packages/pypi/simple", - "https://foo@bar.com/packages/pypi/simple", + "https://foo@bar.test/packages/pypi/simple", }, }, }, @@ -789,10 +800,10 @@ func TestTranslatePathJobEnvironments(t *testing.T) { assert.Equal(t, "./job/dist/env1.whl", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[0]) assert.Equal(t, "./dist/env2.whl", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[1]) assert.Equal(t, "simplejson", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[2]) - assert.Equal(t, "/Workspace/Users/foo@bar.com/test.whl", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[3]) + assert.Equal(t, "/Workspace/Users/foo@bar.test/test.whl", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[3]) assert.Equal(t, "--extra-index-url https://name:token@gitlab.com/api/v4/projects/9876/packages/pypi/simple foobar", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[4]) assert.Equal(t, "foobar --extra-index-url https://name:token@gitlab.com/api/v4/projects/9876/packages/pypi/simple", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[5]) - assert.Equal(t, "https://foo@bar.com/packages/pypi/simple", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[6]) + assert.Equal(t, "https://foo@bar.test/packages/pypi/simple", b.Config.Resources.Jobs["job"].Environments[0].Spec.Dependencies[6]) } func TestTranslatePathWithComplexVariables(t *testing.T) { diff --git a/bundle/config/mutator/validate_direct_only_resources.go b/bundle/config/mutator/validate_direct_only_resources.go index 54e2924848a..556b7b815a5 100644 --- a/bundle/config/mutator/validate_direct_only_resources.go +++ b/bundle/config/mutator/validate_direct_only_resources.go @@ -6,44 +6,11 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/engine" + "github.com/databricks/cli/bundle/deploy/terraform" + "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/libs/diag" ) -type directOnlyResource struct { - resourceType string - pluralName string - singularName string - getResources func(*bundle.Bundle) map[string]any -} - -// Resources that are only supported in direct deployment mode -var directOnlyResources = []directOnlyResource{ - { - resourceType: "catalogs", - pluralName: "Catalog", - singularName: "catalog", - getResources: func(b *bundle.Bundle) map[string]any { - result := make(map[string]any) - for k, v := range b.Config.Resources.Catalogs { - result[k] = v - } - return result - }, - }, - { - resourceType: "external_locations", - pluralName: "External Location", - singularName: "external location", - getResources: func(b *bundle.Bundle) map[string]any { - result := make(map[string]any) - for k, v := range b.Config.Resources.ExternalLocations { - result[k] = v - } - return result - }, - }, -} - type validateDirectOnlyResources struct { engine engine.EngineType } @@ -58,26 +25,37 @@ func (m *validateDirectOnlyResources) Name() string { return "ValidateDirectOnlyResources" } +// isDirectOnly reports whether a resource type (by PluralName) is supported only +// by the direct engine — present in dresources.SupportedResources but absent +// from terraform.GroupToTerraformName. +func isDirectOnly(pluralName string) bool { + _, hasDirect := dresources.SupportedResources[pluralName] + _, hasTerraform := terraform.GroupToTerraformName[pluralName] + return hasDirect && !hasTerraform +} + func (m *validateDirectOnlyResources) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { if m.engine.IsDirect() { return nil } var diags diag.Diagnostics - - for _, resource := range directOnlyResources { - resourceMap := resource.getResources(b) - if len(resourceMap) > 0 { - diags = diags.Append(diag.Diagnostic{ - Severity: diag.Error, - Summary: resource.pluralName + " resources are only supported with direct deployment mode", - Detail: fmt.Sprintf("%s resources require direct deployment mode. "+ - "Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use %s resources.\n"+ - "Learn more at https://docs.databricks.com/dev-tools/bundles/direct", - resource.pluralName, resource.singularName), - Locations: b.Config.GetLocations("resources." + resource.resourceType), - }) + for _, group := range b.Config.Resources.AllResources() { + if len(group.Resources) == 0 { + continue + } + if !isDirectOnly(group.Description.PluralName) { + continue } + diags = diags.Append(diag.Diagnostic{ + Severity: diag.Error, + Summary: group.Description.SingularTitle + " resources are only supported with direct deployment mode", + Detail: fmt.Sprintf("%s resources require direct deployment mode. "+ + "Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use %s resources.\n"+ + "Learn more at https://docs.databricks.com/dev-tools/bundles/direct", + group.Description.SingularTitle, group.Description.SingularName), + Locations: b.Config.GetLocations("resources." + group.Description.PluralName), + }) } return diags diff --git a/bundle/config/mutator/validate_direct_only_resources_test.go b/bundle/config/mutator/validate_direct_only_resources_test.go new file mode 100644 index 00000000000..dbc184c752d --- /dev/null +++ b/bundle/config/mutator/validate_direct_only_resources_test.go @@ -0,0 +1,110 @@ +package mutator_test + +import ( + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/engine" + "github.com/databricks/cli/bundle/config/mutator" + "github.com/databricks/cli/bundle/config/resources" + "github.com/stretchr/testify/assert" +) + +func TestValidateDirectOnlyResourcesDirectEngineReturnsNil(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Catalogs: map[string]*resources.Catalog{ + "my_catalog": {}, + }, + }, + }, + } + + diags := bundle.Apply(t.Context(), b, mutator.ValidateDirectOnlyResources(engine.EngineDirect)) + assert.Empty(t, diags) +} + +func TestValidateDirectOnlyResourcesTerraformEngineNoDirectOnlyReturnsNil(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Jobs: map[string]*resources.Job{ + "my_job": {}, + }, + }, + }, + } + + diags := bundle.Apply(t.Context(), b, mutator.ValidateDirectOnlyResources(engine.EngineTerraform)) + assert.Empty(t, diags) +} + +func TestValidateDirectOnlyResourcesTerraformEngineDirectOnlyEmitsError(t *testing.T) { + cases := []struct { + name string + bundle *bundle.Bundle + expectedSummary string + expectedDetail string + }{ + { + name: "catalogs", + bundle: &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + Catalogs: map[string]*resources.Catalog{ + "my_catalog": {}, + }, + }, + }, + }, + expectedSummary: "Catalog resources are only supported with direct deployment mode", + expectedDetail: "Catalog resources require direct deployment mode. " + + "Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use catalog resources.\n" + + "Learn more at https://docs.databricks.com/dev-tools/bundles/direct", + }, + { + name: "external_locations", + bundle: &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + ExternalLocations: map[string]*resources.ExternalLocation{ + "my_location": {}, + }, + }, + }, + }, + expectedSummary: "External Location resources are only supported with direct deployment mode", + expectedDetail: "External Location resources require direct deployment mode. " + + "Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use external_location resources.\n" + + "Learn more at https://docs.databricks.com/dev-tools/bundles/direct", + }, + { + name: "vector_search_endpoints", + bundle: &bundle.Bundle{ + Config: config.Root{ + Resources: config.Resources{ + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "my_endpoint": {}, + }, + }, + }, + }, + expectedSummary: "Vector Search Endpoint resources are only supported with direct deployment mode", + expectedDetail: "Vector Search Endpoint resources require direct deployment mode. " + + "Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use vector_search_endpoint resources.\n" + + "Learn more at https://docs.databricks.com/dev-tools/bundles/direct", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + diags := bundle.Apply(t.Context(), tc.bundle, mutator.ValidateDirectOnlyResources(engine.EngineTerraform)) + if assert.Len(t, diags, 1) { + assert.Equal(t, tc.expectedSummary, diags[0].Summary) + assert.Equal(t, tc.expectedDetail, diags[0].Detail) + } + }) + } +} diff --git a/bundle/config/resources.go b/bundle/config/resources.go index 4131f686956..225ec32165d 100644 --- a/bundle/config/resources.go +++ b/bundle/config/resources.go @@ -35,6 +35,7 @@ type Resources struct { PostgresProjects map[string]*resources.PostgresProject `json:"postgres_projects,omitempty"` PostgresBranches map[string]*resources.PostgresBranch `json:"postgres_branches,omitempty"` PostgresEndpoints map[string]*resources.PostgresEndpoint `json:"postgres_endpoints,omitempty"` + VectorSearchEndpoints map[string]*resources.VectorSearchEndpoint `json:"vector_search_endpoints,omitempty"` } type ConfigResource interface { @@ -111,6 +112,7 @@ func (r *Resources) AllResources() []ResourceGroup { collectResourceMap(descriptions["postgres_projects"], r.PostgresProjects), collectResourceMap(descriptions["postgres_branches"], r.PostgresBranches), collectResourceMap(descriptions["postgres_endpoints"], r.PostgresEndpoints), + collectResourceMap(descriptions["vector_search_endpoints"], r.VectorSearchEndpoints), } } @@ -165,5 +167,6 @@ func SupportedResources() map[string]resources.ResourceDescription { "postgres_projects": (&resources.PostgresProject{}).ResourceDescription(), "postgres_branches": (&resources.PostgresBranch{}).ResourceDescription(), "postgres_endpoints": (&resources.PostgresEndpoint{}).ResourceDescription(), + "vector_search_endpoints": (&resources.VectorSearchEndpoint{}).ResourceDescription(), } } diff --git a/bundle/config/resources/alerts.go b/bundle/config/resources/alerts.go index bfdd64e9001..628302c4e3b 100644 --- a/bundle/config/resources/alerts.go +++ b/bundle/config/resources/alerts.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/sql" @@ -52,8 +53,7 @@ func (a *Alert) InitializeURL(baseURL url.URL) { if a.ID == "" { return } - baseURL.Path = "sql/alerts-v2/" + a.ID - a.URL = baseURL.String() + a.URL = workspaceurls.ResourceURL(baseURL, "alerts", a.ID) } func (a *Alert) GetName() string { diff --git a/bundle/config/resources/apps.go b/bundle/config/resources/apps.go index f17aa4c22b7..75d5cac0fd1 100644 --- a/bundle/config/resources/apps.go +++ b/bundle/config/resources/apps.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/apps" @@ -94,8 +95,7 @@ func (a *App) InitializeURL(baseURL url.URL) { if a.ModifiedStatus == "" || a.ModifiedStatus == ModifiedStatusCreated { return } - baseURL.Path = "apps/" + a.GetName() - a.URL = baseURL.String() + a.URL = workspaceurls.ResourceURL(baseURL, "apps", a.GetName()) } func (a *App) GetName() string { diff --git a/bundle/config/resources/clusters.go b/bundle/config/resources/clusters.go index c549ac4a6b9..5eb8dc57378 100644 --- a/bundle/config/resources/clusters.go +++ b/bundle/config/resources/clusters.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/compute" @@ -47,8 +48,7 @@ func (s *Cluster) InitializeURL(baseURL url.URL) { if s.ID == "" { return } - baseURL.Path = "compute/clusters/" + s.ID - s.URL = baseURL.String() + s.URL = workspaceurls.ResourceURL(baseURL, "clusters", s.ID) } func (s *Cluster) GetName() string { diff --git a/bundle/config/resources/dashboard.go b/bundle/config/resources/dashboard.go index c108ac8abec..61238149371 100644 --- a/bundle/config/resources/dashboard.go +++ b/bundle/config/resources/dashboard.go @@ -2,10 +2,10 @@ package resources import ( "context" - "fmt" "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/dashboards" @@ -114,8 +114,7 @@ func (r *Dashboard) InitializeURL(baseURL url.URL) { return } - baseURL.Path = fmt.Sprintf("dashboardsv3/%s/published", r.ID) - r.URL = baseURL.String() + r.URL = workspaceurls.ResourceURL(baseURL, "dashboards", r.ID) } func (r *Dashboard) GetName() string { diff --git a/bundle/config/resources/dashboard_test.go b/bundle/config/resources/dashboard_test.go index 435392873e4..c010d949157 100644 --- a/bundle/config/resources/dashboard_test.go +++ b/bundle/config/resources/dashboard_test.go @@ -10,8 +10,8 @@ import ( ) func TestDashboardConfigIsSupersetOfSDKDashboard(t *testing.T) { - configType := reflect.TypeOf(DashboardConfig{}) - sdkType := reflect.TypeOf(dashboards.Dashboard{}) + configType := reflect.TypeFor[DashboardConfig]() + sdkType := reflect.TypeFor[dashboards.Dashboard]() // Helper function to extract JSON tag name getJSONTagName := func(tag string) string { diff --git a/bundle/config/resources/job.go b/bundle/config/resources/job.go index b2b7ff15f1f..646750ef859 100644 --- a/bundle/config/resources/job.go +++ b/bundle/config/resources/job.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -54,8 +55,7 @@ func (j *Job) InitializeURL(baseURL url.URL) { if j.ID == "" { return } - baseURL.Path = "jobs/" + j.ID - j.URL = baseURL.String() + j.URL = workspaceurls.ResourceURL(baseURL, "jobs", j.ID) } func (j *Job) GetName() string { diff --git a/bundle/config/resources/mlflow_experiment.go b/bundle/config/resources/mlflow_experiment.go index 0a2a36b840b..c7db059bc8b 100644 --- a/bundle/config/resources/mlflow_experiment.go +++ b/bundle/config/resources/mlflow_experiment.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/ml" @@ -49,8 +50,7 @@ func (s *MlflowExperiment) InitializeURL(baseURL url.URL) { if s.ID == "" { return } - baseURL.Path = "ml/experiments/" + s.ID - s.URL = baseURL.String() + s.URL = workspaceurls.ResourceURL(baseURL, "experiments", s.ID) } func (s *MlflowExperiment) GetName() string { diff --git a/bundle/config/resources/mlflow_model.go b/bundle/config/resources/mlflow_model.go index a867f55a0e0..c153e2d95e9 100644 --- a/bundle/config/resources/mlflow_model.go +++ b/bundle/config/resources/mlflow_model.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/ml" @@ -49,8 +50,7 @@ func (s *MlflowModel) InitializeURL(baseURL url.URL) { if s.ID == "" { return } - baseURL.Path = "ml/models/" + s.ID - s.URL = baseURL.String() + s.URL = workspaceurls.ResourceURL(baseURL, "models", s.ID) } func (s *MlflowModel) GetName() string { diff --git a/bundle/config/resources/model_serving_endpoint.go b/bundle/config/resources/model_serving_endpoint.go index d3e390596d0..5a917c2e268 100644 --- a/bundle/config/resources/model_serving_endpoint.go +++ b/bundle/config/resources/model_serving_endpoint.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/serving" @@ -54,8 +55,7 @@ func (s *ModelServingEndpoint) InitializeURL(baseURL url.URL) { if s.ID == "" { return } - baseURL.Path = "ml/endpoints/" + s.ID - s.URL = baseURL.String() + s.URL = workspaceurls.ResourceURL(baseURL, "model_serving_endpoints", s.ID) } func (s *ModelServingEndpoint) GetName() string { diff --git a/bundle/config/resources/permission_types.go b/bundle/config/resources/permission_types.go index a40b5c8eec9..3029ee40b8c 100644 --- a/bundle/config/resources/permission_types.go +++ b/bundle/config/resources/permission_types.go @@ -22,6 +22,7 @@ func (p Permission) String() string { return PermissionT[iam.PermissionLevel](p).String() } +// If the SDK exposes a resource's permission level, add it here. type ( AppPermission PermissionT[apps.AppPermissionLevel] ClusterPermission PermissionT[compute.ClusterPermissionLevel] diff --git a/bundle/config/resources/pipeline.go b/bundle/config/resources/pipeline.go index e213ff9c008..c442a01a641 100644 --- a/bundle/config/resources/pipeline.go +++ b/bundle/config/resources/pipeline.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -49,8 +50,7 @@ func (p *Pipeline) InitializeURL(baseURL url.URL) { if p.ID == "" { return } - baseURL.Path = "pipelines/" + p.ID - p.URL = baseURL.String() + p.URL = workspaceurls.ResourceURL(baseURL, "pipelines", p.ID) } func (p *Pipeline) GetName() string { diff --git a/bundle/config/resources/registered_model.go b/bundle/config/resources/registered_model.go index c51450bf747..87b0f0748c1 100644 --- a/bundle/config/resources/registered_model.go +++ b/bundle/config/resources/registered_model.go @@ -3,9 +3,9 @@ package resources import ( "context" "net/url" - "strings" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/catalog" @@ -54,8 +54,7 @@ func (s *RegisteredModel) InitializeURL(baseURL url.URL) { if s.ID == "" { return } - baseURL.Path = "explore/data/models/" + strings.ReplaceAll(s.ID, ".", "/") - s.URL = baseURL.String() + s.URL = workspaceurls.ResourceURL(baseURL, "registered_models", s.ID) } func (s *RegisteredModel) GetName() string { diff --git a/bundle/config/resources/secret_scope.go b/bundle/config/resources/secret_scope.go index acefe80bd2c..702ee914f8a 100644 --- a/bundle/config/resources/secret_scope.go +++ b/bundle/config/resources/secret_scope.go @@ -28,7 +28,7 @@ type SecretScopePermission struct { GroupName string `json:"group_name,omitempty"` } -type SecretScope struct { +type SecretScope struct { //nolint:recvcheck // pointer receiver needed for UnmarshalJSON, value for other methods BaseResource // A unique name to identify the secret scope. @@ -67,7 +67,7 @@ func (s SecretScope) Exists(ctx context.Context, w *databricks.WorkspaceClient, // The indirect methods are not semantically ideal for simple existence checks, so we use the list API here scopes, err := w.Secrets.ListScopesAll(ctx) if err != nil { - return false, nil + return false, nil //nolint:nilerr // treat API errors as "scope not found" } for _, scope := range scopes { diff --git a/bundle/config/resources/sql_warehouses.go b/bundle/config/resources/sql_warehouses.go index bed567b8059..016526a80ef 100644 --- a/bundle/config/resources/sql_warehouses.go +++ b/bundle/config/resources/sql_warehouses.go @@ -5,6 +5,7 @@ import ( "net/url" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/sql" @@ -47,8 +48,7 @@ func (sw *SqlWarehouse) InitializeURL(baseURL url.URL) { if sw.ID == "" { return } - baseURL.Path = "sql/warehouses/" + sw.ID - sw.URL = baseURL.String() + sw.URL = workspaceurls.ResourceURL(baseURL, "warehouses", sw.ID) } func (sw *SqlWarehouse) GetName() string { diff --git a/bundle/config/resources/vector_search_endpoint.go b/bundle/config/resources/vector_search_endpoint.go new file mode 100644 index 00000000000..900e91ccc57 --- /dev/null +++ b/bundle/config/resources/vector_search_endpoint.go @@ -0,0 +1,64 @@ +package resources + +import ( + "context" + "net/url" + + "github.com/databricks/cli/libs/log" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/apierr" + "github.com/databricks/databricks-sdk-go/marshal" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" +) + +type VectorSearchEndpoint struct { + BaseResource + vectorsearch.CreateEndpoint + + Permissions []Permission `json:"permissions,omitempty"` +} + +func (e *VectorSearchEndpoint) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, e) +} + +func (e VectorSearchEndpoint) MarshalJSON() ([]byte, error) { + return marshal.Marshal(e) +} + +func (e *VectorSearchEndpoint) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) { + _, err := w.VectorSearchEndpoints.GetEndpoint(ctx, vectorsearch.GetEndpointRequest{EndpointName: name}) + if err != nil { + log.Debugf(ctx, "vector search endpoint %s does not exist: %v", name, err) + if apierr.IsMissing(err) { + return false, nil + } + return false, err + } + return true, nil +} + +func (e *VectorSearchEndpoint) ResourceDescription() ResourceDescription { + return ResourceDescription{ + SingularName: "vector_search_endpoint", + PluralName: "vector_search_endpoints", + SingularTitle: "Vector Search Endpoint", + PluralTitle: "Vector Search Endpoints", + } +} + +func (e *VectorSearchEndpoint) InitializeURL(baseURL url.URL) { + if e.Name == "" { + return + } + baseURL.Path = "compute/vector-search/" + e.Name + e.URL = baseURL.String() +} + +func (e *VectorSearchEndpoint) GetName() string { + return e.Name +} + +func (e *VectorSearchEndpoint) GetURL() string { + return e.URL +} diff --git a/bundle/config/resources_test.go b/bundle/config/resources_test.go index ddc90209e8b..943b279a288 100644 --- a/bundle/config/resources_test.go +++ b/bundle/config/resources_test.go @@ -2,6 +2,7 @@ package config import ( "encoding/json" + "net/url" "reflect" "strings" "testing" @@ -14,6 +15,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/serving" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/workspaceurls" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/databricks/databricks-sdk-go/service/catalog" @@ -21,7 +23,9 @@ import ( "github.com/databricks/databricks-sdk-go/service/ml" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" ) @@ -47,8 +51,7 @@ import ( // a way to directly assert that MarshalJSON and UnmarshalJSON are implemented // at the top level. func TestCustomMarshallerIsImplemented(t *testing.T) { - r := Resources{} - rt := reflect.TypeOf(r) + rt := reflect.TypeFor[Resources]() for i := range rt.NumField() { field := rt.Field(i) @@ -84,7 +87,7 @@ func TestCustomMarshallerIsImplemented(t *testing.T) { func TestResourcesAllResourcesCompleteness(t *testing.T) { r := Resources{} - rt := reflect.TypeOf(r) + rt := reflect.TypeFor[Resources]() // Collect set of includes resource types var types []string @@ -108,7 +111,7 @@ func TestSupportedResources(t *testing.T) { // Please add your resource to the SupportedResources() function in resources.go if you add a new resource. actual := SupportedResources() - typ := reflect.TypeOf(Resources{}) + typ := reflect.TypeFor[Resources]() for i := range typ.NumField() { field := typ.Field(i) jsonTags := strings.Split(field.Tag.Get("json"), ",") @@ -117,6 +120,37 @@ func TestSupportedResources(t *testing.T) { } } +// Bundle resources whose InitializeURL() resolves via workspaceurls. When a +// pattern key or a bundle plural name drifts, ResourceURL returns "" and this +// test fails loudly instead of silently producing empty URLs in bundle summary. +func TestBundleResourcePluralNamesResolveInWorkspaceURLs(t *testing.T) { + withURLs := []string{ + "alerts", + "apps", + "clusters", + "dashboards", + "experiments", + "jobs", + "models", + "model_serving_endpoints", + "pipelines", + "registered_models", + "sql_warehouses", + } + + supported := SupportedResources() + for _, name := range withURLs { + _, ok := supported[name] + require.Truef(t, ok, "%q is not a bundle plural name, update SupportedResources or this test", name) + } + + base := url.URL{Scheme: "https", Host: "example.com"} + for _, name := range withURLs { + got := workspaceurls.ResourceURL(base, name, "test-id") + assert.NotEmptyf(t, got, "workspaceurls.ResourceURL(%q) returned empty; pattern key renamed or alias missing", name) + } +} + func TestResourcesBindSupport(t *testing.T) { supportedResources := &Resources{ Jobs: map[string]*resources.Job{ @@ -239,6 +273,14 @@ func TestResourcesBindSupport(t *testing.T) { }, }, }, + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "my_vector_search_endpoint": { + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "my_vector_search_endpoint", + EndpointType: vectorsearch.EndpointTypeStandard, + }, + }, + }, } unbindableResources := map[string]bool{ "model": true, @@ -270,6 +312,7 @@ func TestResourcesBindSupport(t *testing.T) { m.GetMockPostgresAPI().EXPECT().GetProject(mock.Anything, mock.Anything).Return(nil, nil) m.GetMockPostgresAPI().EXPECT().GetBranch(mock.Anything, mock.Anything).Return(nil, nil) m.GetMockPostgresAPI().EXPECT().GetEndpoint(mock.Anything, mock.Anything).Return(nil, nil) + m.GetMockVectorSearchEndpointsAPI().EXPECT().GetEndpoint(mock.Anything, mock.Anything).Return(nil, nil) allResources := supportedResources.AllResources() for _, group := range allResources { diff --git a/bundle/config/resources_types.go b/bundle/config/resources_types.go index c1b55a7d158..15fac1b93f1 100644 --- a/bundle/config/resources_types.go +++ b/bundle/config/resources_types.go @@ -10,8 +10,7 @@ import ( // "jobs" or "pipelines") to the Go type that represents a single resource instance inside // that group (for example `resources.Job`). var ResourcesTypes = func() map[string]reflect.Type { - var r Resources - rt := reflect.TypeOf(r) + rt := reflect.TypeFor[Resources]() res := make(map[string]reflect.Type, rt.NumField()) for _, field := range reflect.VisibleFields(rt) { diff --git a/bundle/config/resources_types_test.go b/bundle/config/resources_types_test.go index 577f1dd0034..5d2a7298c6f 100644 --- a/bundle/config/resources_types_test.go +++ b/bundle/config/resources_types_test.go @@ -14,9 +14,9 @@ func TestResourcesTypesMap(t *testing.T) { typ, ok := ResourcesTypes["jobs"] assert.True(t, ok, "resources type for 'jobs' not found in ResourcesTypes map") - assert.Equal(t, reflect.TypeOf(resources.Job{}), typ, "resources type for 'jobs' mismatch") + assert.Equal(t, reflect.TypeFor[resources.Job](), typ, "resources type for 'jobs' mismatch") typ, ok = ResourcesTypes["jobs.permissions"] assert.True(t, ok, "resources type for 'jobs.permissions' not found in ResourcesTypes map") - assert.Equal(t, reflect.TypeOf([]resources.JobPermission{}), typ, "resources type for 'jobs.permissions' mismatch") + assert.Equal(t, reflect.TypeFor[[]resources.JobPermission](), typ, "resources type for 'jobs.permissions' mismatch") } diff --git a/bundle/config/root.go b/bundle/config/root.go index a32ca8ea286..6d4697cc1ba 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -3,6 +3,7 @@ package config import ( "bytes" "context" + "errors" "fmt" "os" "reflect" @@ -25,7 +26,7 @@ type Script struct { Content string `json:"content"` } -type Root struct { +type Root struct { //nolint:recvcheck // value receivers for read-only accessors, pointer for mutators value dyn.Value depth int @@ -106,6 +107,14 @@ func LoadFromBytes(path string, raw []byte) (*Root, diag.Diagnostics) { // Load configuration tree from YAML. v, err := yamlloader.LoadYAML(path, bytes.NewBuffer(raw)) if err != nil { + var le *yamlloader.LocationError + if errors.As(err, &le) { + return nil, diag.Diagnostics{{ + Severity: diag.Error, + Summary: le.Summary, + Locations: []dyn.Location{le.Loc}, + }} + } return nil, diag.Errorf("failed to load %s: %v", path, err) } @@ -443,7 +452,7 @@ var allowedVariableDefinitions = []([]string){ {"lookup"}, } -// isFullVariableOverrideDef checks if the given value is a full syntax varaible override. +// isFullVariableOverrideDef checks if the given value is a full syntax variable override. // A full syntax variable override is a map with either 1 of 2 keys. // If it's 2 keys, the keys should be "default" and "type". // If it's 1 key, the key should be one of the following keys: "default", "lookup". @@ -514,7 +523,7 @@ func rewriteShorthands(v dyn.Value) (dyn.Value, error) { // Check if the original definition of variable has a type field. // If it has a type field, it means the shorthand is a value of a complex type. - // Type might not be found if the variable overriden in a separate file + // Type might not be found if the variable overridden in a separate file // and configuration is not merged yet. typeV, err := dyn.GetByPath(v, p.Append(dyn.Key("type"))) if err == nil && typeV.MustString() == "complex" { diff --git a/bundle/config/validate/enum.go b/bundle/config/validate/enum.go index 033f7474ec8..e6266163bc8 100644 --- a/bundle/config/validate/enum.go +++ b/bundle/config/validate/enum.go @@ -1,10 +1,10 @@ package validate import ( + "cmp" "context" "fmt" "slices" - "sort" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/internal/validation/generated" @@ -86,16 +86,14 @@ func (f *enum) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { } // Sort diagnostics to make them deterministic - sort.Slice(diags, func(i, j int) bool { + slices.SortFunc(diags, func(a, b diag.Diagnostic) int { // First sort by summary - if diags[i].Summary != diags[j].Summary { - return diags[i].Summary < diags[j].Summary + if n := cmp.Compare(a.Summary, b.Summary); n != 0 { + return n } // Then sort by locations as a tie breaker if summaries are the same. - iLocs := fmt.Sprintf("%v", diags[i].Locations) - jLocs := fmt.Sprintf("%v", diags[j].Locations) - return iLocs < jLocs + return cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations)) }) return diags diff --git a/bundle/config/validate/files_to_sync_test.go b/bundle/config/validate/files_to_sync_test.go index 1ee11bf7bcd..71a1dfaf5a7 100644 --- a/bundle/config/validate/files_to_sync_test.go +++ b/bundle/config/validate/files_to_sync_test.go @@ -63,7 +63,7 @@ func setupBundleForFilesToSyncTest(t *testing.T) *bundle.Bundle { m := mocks.NewMockWorkspaceClient(t) m.WorkspaceClient.Config = &sdkconfig.Config{ - Host: "https://foo.com", + Host: "https://foo.test", } // The initialization logic in [sync.New] performs a check on the destination path. diff --git a/bundle/config/validate/folder_permissions.go b/bundle/config/validate/folder_permissions.go index 575702c34cb..7ea2ee8cee8 100644 --- a/bundle/config/validate/folder_permissions.go +++ b/bundle/config/validate/folder_permissions.go @@ -53,7 +53,7 @@ func checkFolderPermission(ctx context.Context, b *bundle.Bundle, folderPath str return nil } - w := b.WorkspaceClient().Workspace + w := b.WorkspaceClient(ctx).Workspace obj, err := getClosestExistingObject(ctx, w, folderPath) if err != nil { return diag.FromErr(err) @@ -73,7 +73,7 @@ func checkFolderPermission(ctx context.Context, b *bundle.Bundle, folderPath str func getClosestExistingObject(ctx context.Context, w workspace.WorkspaceInterface, folderPath string) (*workspace.ObjectInfo, error) { for { - obj, err := w.GetStatusByPath(ctx, folderPath) + obj, err := w.GetStatusByPath(ctx, folderPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err == nil { return obj, nil } diff --git a/bundle/config/validate/folder_permissions_test.go b/bundle/config/validate/folder_permissions_test.go index 394ffee4e2f..0cc97907c62 100644 --- a/bundle/config/validate/folder_permissions_test.go +++ b/bundle/config/validate/folder_permissions_test.go @@ -19,28 +19,28 @@ func TestFolderPermissionsInheritedWhenRootPathDoesNotExist(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Workspace/Users/foo@bar.com", - ArtifactPath: "/Workspace/Users/otherfoo@bar.com/artifacts", - FilePath: "/Workspace/Users/foo@bar.com/files", - StatePath: "/Workspace/Users/foo@bar.com/state", - ResourcePath: "/Workspace/Users/foo@bar.com/resources", + RootPath: "/Workspace/Users/foo@bar.test", + ArtifactPath: "/Workspace/Users/otherfoo@bar.test/artifacts", + FilePath: "/Workspace/Users/foo@bar.test/files", + StatePath: "/Workspace/Users/foo@bar.test/state", + ResourcePath: "/Workspace/Users/foo@bar.test/resources", }, Permissions: []resources.Permission{ - {Level: permissions.CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: permissions.CAN_MANAGE, UserName: "foo@bar.test"}, }, }, } m := mocks.NewMockWorkspaceClient(t) api := m.GetMockWorkspaceAPI() - api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/otherfoo@bar.com/artifacts").Return(nil, &apierr.APIError{ + api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/otherfoo@bar.test/artifacts").Return(nil, &apierr.APIError{ StatusCode: 404, ErrorCode: "RESOURCE_DOES_NOT_EXIST", }) - api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/otherfoo@bar.com").Return(nil, &apierr.APIError{ + api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/otherfoo@bar.test").Return(nil, &apierr.APIError{ StatusCode: 404, ErrorCode: "RESOURCE_DOES_NOT_EXIST", }) - api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.com").Return(nil, &apierr.APIError{ + api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.test").Return(nil, &apierr.APIError{ StatusCode: 404, ErrorCode: "RESOURCE_DOES_NOT_EXIST", }) @@ -59,7 +59,7 @@ func TestFolderPermissionsInheritedWhenRootPathDoesNotExist(t *testing.T) { ObjectId: "1234", AccessControlList: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -76,20 +76,20 @@ func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Workspace/Users/foo@bar.com", - ArtifactPath: "/Workspace/Users/foo@bar.com/artifacts", - FilePath: "/Workspace/Users/foo@bar.com/files", - StatePath: "/Workspace/Users/foo@bar.com/state", - ResourcePath: "/Workspace/Users/foo@bar.com/resources", + RootPath: "/Workspace/Users/foo@bar.test", + ArtifactPath: "/Workspace/Users/foo@bar.test/artifacts", + FilePath: "/Workspace/Users/foo@bar.test/files", + StatePath: "/Workspace/Users/foo@bar.test/state", + ResourcePath: "/Workspace/Users/foo@bar.test/resources", }, Permissions: []resources.Permission{ - {Level: permissions.CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: permissions.CAN_MANAGE, UserName: "foo@bar.test"}, }, }, } m := mocks.NewMockWorkspaceClient(t) api := m.GetMockWorkspaceAPI() - api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.com").Return(&workspace.ObjectInfo{ + api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.test").Return(&workspace.ObjectInfo{ ObjectId: 1234, }, nil) @@ -100,13 +100,13 @@ func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) { ObjectId: "1234", AccessControlList: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, }, { - UserName: "foo2@bar.com", + UserName: "foo2@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -119,8 +119,8 @@ func TestValidateFolderPermissionsFailsOnMissingBundlePermission(t *testing.T) { require.Len(t, diags, 1) require.Equal(t, "workspace folder has permissions not configured in bundle", diags[0].Summary) require.Equal(t, diag.Warning, diags[0].Severity) - expectedDetail := "The following permissions apply to the workspace folder at \"/Workspace/Users/foo@bar.com\" " + - "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo2@bar.com\n\n" + + expectedDetail := "The following permissions apply to the workspace folder at \"/Workspace/Users/foo@bar.test\" " + + "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo2@bar.test\n\n" + "Add them to your bundle permissions or remove them from the folder.\n" + "See https://docs.databricks.com/dev-tools/bundles/permissions" require.Equal(t, expectedDetail, diags[0].Detail) @@ -130,20 +130,20 @@ func TestValidateFolderPermissionsFailsOnPermissionMismatch(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Workspace/Users/foo@bar.com", - ArtifactPath: "/Workspace/Users/foo@bar.com/artifacts", - FilePath: "/Workspace/Users/foo@bar.com/files", - StatePath: "/Workspace/Users/foo@bar.com/state", - ResourcePath: "/Workspace/Users/foo@bar.com/resources", + RootPath: "/Workspace/Users/foo@bar.test", + ArtifactPath: "/Workspace/Users/foo@bar.test/artifacts", + FilePath: "/Workspace/Users/foo@bar.test/files", + StatePath: "/Workspace/Users/foo@bar.test/state", + ResourcePath: "/Workspace/Users/foo@bar.test/resources", }, Permissions: []resources.Permission{ - {Level: permissions.CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: permissions.CAN_MANAGE, UserName: "foo@bar.test"}, }, }, } m := mocks.NewMockWorkspaceClient(t) api := m.GetMockWorkspaceAPI() - api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.com").Return(&workspace.ObjectInfo{ + api.EXPECT().GetStatusByPath(mock.Anything, "/Workspace/Users/foo@bar.test").Return(&workspace.ObjectInfo{ ObjectId: 1234, }, nil) @@ -154,7 +154,7 @@ func TestValidateFolderPermissionsFailsOnPermissionMismatch(t *testing.T) { ObjectId: "1234", AccessControlList: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo2@bar.com", + UserName: "foo2@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -180,7 +180,7 @@ func TestValidateFolderPermissionsFailsOnNoRootFolder(t *testing.T) { ResourcePath: "/NotExisting/resources", }, Permissions: []resources.Permission{ - {Level: permissions.CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: permissions.CAN_MANAGE, UserName: "foo@bar.test"}, }, }, } diff --git a/bundle/config/validate/no_variable_reference_in_resource_key.go b/bundle/config/validate/no_variable_reference_in_resource_key.go new file mode 100644 index 00000000000..5ad5b58f200 --- /dev/null +++ b/bundle/config/validate/no_variable_reference_in_resource_key.go @@ -0,0 +1,56 @@ +package validate + +import ( + "context" + "fmt" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/dynvar" +) + +type noVariableReferenceInResourceKey struct{} + +// NoVariableReferenceInResourceKey validates that no resource key contains a variable reference. +// Resource keys are used as identifiers throughout the deployment pipeline and must be static strings. +func NoVariableReferenceInResourceKey() bundle.Mutator { + return &noVariableReferenceInResourceKey{} +} + +func (m *noVariableReferenceInResourceKey) Name() string { + return "validate:no_variable_reference_in_resource_key" +} + +func (m *noVariableReferenceInResourceKey) Apply(_ context.Context, b *bundle.Bundle) diag.Diagnostics { + var diags diag.Diagnostics + + patterns := []dyn.Pattern{ + dyn.NewPattern(dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + dyn.NewPattern(dyn.Key("targets"), dyn.AnyKey(), dyn.Key("resources"), dyn.AnyKey(), dyn.AnyKey()), + } + + for _, pattern := range patterns { + _, err := dyn.MapByPattern( + b.Config.Value(), + pattern, + func(p dyn.Path, v dyn.Value) (dyn.Value, error) { + key := p[len(p)-1].Key() + if dynvar.ContainsVariableReference(key) { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("resource key %q must not contain variable references", key), + Locations: v.Locations(), + Paths: []dyn.Path{p}, + }) + } + return v, nil + }, + ) + if err != nil { + diags = append(diags, diag.FromErr(err)...) + } + } + + return diags +} diff --git a/bundle/config/validate/required.go b/bundle/config/validate/required.go index 82b02ed8b8c..6f886caab44 100644 --- a/bundle/config/validate/required.go +++ b/bundle/config/validate/required.go @@ -1,10 +1,10 @@ package validate import ( + "cmp" "context" "fmt" "slices" - "sort" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/internal/validation/generated" @@ -69,16 +69,14 @@ func warnForMissingFields(ctx context.Context, b *bundle.Bundle) diag.Diagnostic } // Sort diagnostics to make them deterministic - sort.Slice(diags, func(i, j int) bool { + slices.SortFunc(diags, func(a, b diag.Diagnostic) int { // First sort by summary - if diags[i].Summary != diags[j].Summary { - return diags[i].Summary < diags[j].Summary + if n := cmp.Compare(a.Summary, b.Summary); n != 0 { + return n } // Finally sort by locations as a tie breaker if summaries are the same. - iLocs := fmt.Sprintf("%v", diags[i].Locations) - jLocs := fmt.Sprintf("%v", diags[j].Locations) - return iLocs < jLocs + return cmp.Compare(fmt.Sprintf("%v", a.Locations), fmt.Sprintf("%v", b.Locations)) }) return diags diff --git a/bundle/config/validate/scripts.go b/bundle/config/validate/scripts.go index 421ca593cb1..04c6045bb42 100644 --- a/bundle/config/validate/scripts.go +++ b/bundle/config/validate/scripts.go @@ -3,12 +3,13 @@ package validate import ( "context" "fmt" + "maps" "regexp" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/utils" ) type validateScripts struct{} @@ -28,7 +29,7 @@ func (f *validateScripts) Apply(ctx context.Context, b *bundle.Bundle) diag.Diag // Sort the scripts to have a deterministic order for the // generated diagnostics. - scriptKeys := utils.SortedKeys(b.Config.Scripts) + scriptKeys := slices.Sorted(maps.Keys(b.Config.Scripts)) for _, k := range scriptKeys { script := b.Config.Scripts[k] diff --git a/bundle/config/validate/unique_resource_keys.go b/bundle/config/validate/unique_resource_keys.go index 3a227e5c1e8..12c13fd1a86 100644 --- a/bundle/config/validate/unique_resource_keys.go +++ b/bundle/config/validate/unique_resource_keys.go @@ -1,8 +1,9 @@ package validate import ( + "cmp" "context" - "sort" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" @@ -99,20 +100,17 @@ func (m *uniqueResourceKeys) Apply(ctx context.Context, b *bundle.Bundle) diag.D // Sort the locations and paths for consistent error messages. This helps // with unit testing. - sort.Slice(v.locations, func(i, j int) bool { - l1 := v.locations[i] - l2 := v.locations[j] - - if l1.File != l2.File { - return l1.File < l2.File + slices.SortFunc(v.locations, func(a, b dyn.Location) int { + if n := cmp.Compare(a.File, b.File); n != 0 { + return n } - if l1.Line != l2.Line { - return l1.Line < l2.Line + if n := cmp.Compare(a.Line, b.Line); n != 0 { + return n } - return l1.Column < l2.Column + return cmp.Compare(a.Column, b.Column) }) - sort.Slice(v.paths, func(i, j int) bool { - return v.paths[i].String() < v.paths[j].String() + slices.SortFunc(v.paths, func(a, b dyn.Path) int { + return cmp.Compare(a.String(), b.String()) }) // If there are multiple resources with the same key, report an error. diff --git a/bundle/config/validate/validate_artifact_path.go b/bundle/config/validate/validate_artifact_path.go index 78536d4bd78..4ea5c4308ad 100644 --- a/bundle/config/validate/validate_artifact_path.go +++ b/bundle/config/validate/validate_artifact_path.go @@ -96,7 +96,7 @@ func (v *validateArtifactPath) Apply(ctx context.Context, b *bundle.Bundle) diag return wrapErrorMsg(err.Error()) } volumeFullName := fmt.Sprintf("%s.%s.%s", catalogName, schemaName, volumeName) - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) _, err = w.Volumes.ReadByName(ctx, volumeFullName) if errors.Is(err, apierr.ErrPermissionDenied) { diff --git a/bundle/config/variable/variable.go b/bundle/config/variable/variable.go index c924616fa21..5f8ea6138c4 100644 --- a/bundle/config/variable/variable.go +++ b/bundle/config/variable/variable.go @@ -57,7 +57,7 @@ type Variable struct { } // True if the variable has been assigned a default value. Variables without a -// a default value are by defination required +// default value are by definition required func (v *Variable) HasDefault() bool { return v.Default != nil } diff --git a/bundle/config/workspace.go b/bundle/config/workspace.go index c699dc070b8..9cd397f13aa 100644 --- a/bundle/config/workspace.go +++ b/bundle/config/workspace.go @@ -1,9 +1,12 @@ package config import ( + "context" "os" "path/filepath" + "strconv" + "github.com/databricks/cli/bundle/env" "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/databricks-sdk-go" @@ -43,6 +46,11 @@ type Workspace struct { AzureLoginAppID string `json:"azure_login_app_id,omitempty"` // Unified host specific attributes. + // + // ExperimentalIsUnifiedHost is a deprecated no-op. Unified hosts are now + // detected automatically from /.well-known/databricks-config. The field is + // retained so existing databricks.yml files using it still validate against + // the bundle schema. ExperimentalIsUnifiedHost bool `json:"experimental_is_unified_host,omitempty"` AccountID string `json:"account_id,omitempty"` WorkspaceID string `json:"workspace_id,omitempty"` @@ -93,13 +101,20 @@ func (s User) MarshalJSON() ([]byte, error) { return marshal.Marshal(s) } -func (w *Workspace) Config() *config.Config { +func (w *Workspace) Config(ctx context.Context) *config.Config { + // Once bundle deploy started, old deployment is partially destroyed, so we should do utmost to complete it. + // Having client-side timeouts that kill the deployment seems counter-productive. We should just keep on + // trying and the user should be the one interrupting it if they decide so. + // Default is 30s + httpTimeout := 90 + if v, ok := env.HTTPTimeoutSeconds(ctx); ok { + if n, err := strconv.Atoi(v); err == nil { + httpTimeout = n + } + } + cfg := &config.Config{ - // Once bundle deploy started, old deployment is partially destroyed, so we should do utmost to complete it. - // Having client-side timeouts that kill the deployment seems counter-productive. We should just keep on - // trying and the user should be the one interrupting it if they decide so. - // Default is 30s - HTTPTimeoutSeconds: 90, + HTTPTimeoutSeconds: httpTimeout, // Default is 5min RetryTimeoutSeconds: 15 * 60, @@ -125,9 +140,8 @@ func (w *Workspace) Config() *config.Config { AzureLoginAppID: w.AzureLoginAppID, // Unified host - Experimental_IsUnifiedHost: w.ExperimentalIsUnifiedHost, - AccountID: w.AccountID, - WorkspaceID: w.WorkspaceID, + AccountID: w.AccountID, + WorkspaceID: w.WorkspaceID, } for k := range config.ConfigAttributes { @@ -156,13 +170,13 @@ func (w *Workspace) NormalizeHostURL() { } } -func (w *Workspace) Client() (*databricks.WorkspaceClient, error) { +func (w *Workspace) Client(ctx context.Context) (*databricks.WorkspaceClient, error) { // Extract query parameters (?o=, ?a=) from the host URL before building // the SDK config. This ensures workspace_id and account_id are available // for profile resolution during EnsureResolved(). w.NormalizeHostURL() - cfg := w.Config() + cfg := w.Config(ctx) // If only the host is configured, we try and unambiguously match it to // a profile in the user's databrickscfg file. Override the default loaders. diff --git a/bundle/config/workspace_test.go b/bundle/config/workspace_test.go index 4181d17170a..b1898db77c8 100644 --- a/bundle/config/workspace_test.go +++ b/bundle/config/workspace_test.go @@ -6,6 +6,7 @@ import ( "runtime" "testing" + "github.com/databricks/cli/bundle/env" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/databricks-sdk-go/config" @@ -34,7 +35,7 @@ func TestWorkspaceResolveProfileFromHost(t *testing.T) { t.Run("no config file", func(t *testing.T) { setupWorkspaceTest(t) - _, err := w.Client() + _, err := w.Client(t.Context()) assert.NoError(t, err) }) @@ -49,7 +50,7 @@ func TestWorkspaceResolveProfileFromHost(t *testing.T) { }) require.NoError(t, err) - client, err := w.Client() + client, err := w.Client(t.Context()) assert.NoError(t, err) assert.Equal(t, "default", client.Config.Profile) }) @@ -67,7 +68,7 @@ func TestWorkspaceResolveProfileFromHost(t *testing.T) { require.NoError(t, err) t.Setenv("DATABRICKS_CONFIG_FILE", filepath.Join(home, "customcfg")) - client, err := w.Client() + client, err := w.Client(t.Context()) assert.NoError(t, err) assert.Equal(t, "custom", client.Config.Profile) }) @@ -149,11 +150,30 @@ func TestWorkspaceClientNormalizesHostBeforeProfileResolution(t *testing.T) { w := Workspace{ Host: "https://spog.databricks.com/?o=222", } - client, err := w.Client() + client, err := w.Client(t.Context()) require.NoError(t, err) assert.Equal(t, "ws2", client.Config.Profile) } +func TestWorkspaceConfigHTTPTimeout(t *testing.T) { + for _, tc := range []struct { + envVal string + want int + }{ + {"", 90}, + {"5", 5}, + {"not-a-number", 90}, + } { + t.Run(tc.envVal, func(t *testing.T) { + if tc.envVal != "" { + t.Setenv(env.HTTPTimeoutSecondsVariable, tc.envVal) + } + w := Workspace{} + assert.Equal(t, tc.want, w.Config(t.Context()).HTTPTimeoutSeconds) + }) + } +} + func TestWorkspaceVerifyProfileForHost(t *testing.T) { // If both a workspace host and a profile are specified, // verify that the host configured in the profile matches @@ -165,7 +185,7 @@ func TestWorkspaceVerifyProfileForHost(t *testing.T) { t.Run("no config file", func(t *testing.T) { setupWorkspaceTest(t) - _, err := w.Client() + _, err := w.Client(t.Context()) assert.ErrorIs(t, err, fs.ErrNotExist) }) @@ -179,7 +199,7 @@ func TestWorkspaceVerifyProfileForHost(t *testing.T) { }) require.NoError(t, err) - _, err = w.Client() + _, err = w.Client(t.Context()) assert.NoError(t, err) }) @@ -193,7 +213,7 @@ func TestWorkspaceVerifyProfileForHost(t *testing.T) { }) require.NoError(t, err) - _, err = w.Client() + _, err = w.Client(t.Context()) assert.ErrorContains(t, err, "doesn’t match the host configured in the bundle") }) @@ -209,7 +229,7 @@ func TestWorkspaceVerifyProfileForHost(t *testing.T) { require.NoError(t, err) t.Setenv("DATABRICKS_CONFIG_FILE", filepath.Join(home, "customcfg")) - _, err = w.Client() + _, err = w.Client(t.Context()) assert.NoError(t, err) }) @@ -225,7 +245,7 @@ func TestWorkspaceVerifyProfileForHost(t *testing.T) { require.NoError(t, err) t.Setenv("DATABRICKS_CONFIG_FILE", filepath.Join(home, "customcfg")) - _, err = w.Client() + _, err = w.Client(t.Context()) assert.ErrorContains(t, err, "doesn’t match the host configured in the bundle") }) } diff --git a/bundle/configsync/diff.go b/bundle/configsync/diff.go index c4f01d4190a..dee7fa48116 100644 --- a/bundle/configsync/diff.go +++ b/bundle/configsync/diff.go @@ -139,7 +139,7 @@ func DetectChanges(ctx context.Context, b *bundle.Bundle, engine engine.EngineTy } } - plan, err := deployBundle.CalculatePlan(ctx, b.WorkspaceClient(), &b.Config) + plan, err := deployBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), &b.Config) if err != nil { return nil, fmt.Errorf("failed to calculate plan: %w", err) } @@ -191,7 +191,7 @@ func ensureSnapshotAvailable(ctx context.Context, b *bundle.Bundle, engine engin log.Debugf(ctx, "Resources state snapshot not found locally, pulling from remote") - f, err := deploy.StateFiler(b) + f, err := deploy.StateFiler(ctx, b) if err != nil { return fmt.Errorf("getting state filer: %w", err) } diff --git a/bundle/configsync/format.go b/bundle/configsync/format.go index a4671039b8a..f30fc62d58e 100644 --- a/bundle/configsync/format.go +++ b/bundle/configsync/format.go @@ -2,7 +2,8 @@ package configsync import ( "fmt" - "sort" + "maps" + "slices" "strings" ) @@ -15,27 +16,19 @@ func FormatTextOutput(changes Changes) string { return output.String() } - output.WriteString(fmt.Sprintf("Detected changes in %d resource(s):\n\n", len(changes))) + fmt.Fprintf(&output, "Detected changes in %d resource(s):\n\n", len(changes)) - resourceKeys := make([]string, 0, len(changes)) - for key := range changes { - resourceKeys = append(resourceKeys, key) - } - sort.Strings(resourceKeys) + resourceKeys := slices.Sorted(maps.Keys(changes)) for _, resourceKey := range resourceKeys { resourceChanges := changes[resourceKey] - output.WriteString(fmt.Sprintf("Resource: %s\n", resourceKey)) + fmt.Fprintf(&output, "Resource: %s\n", resourceKey) - paths := make([]string, 0, len(resourceChanges)) - for path := range resourceChanges { - paths = append(paths, path) - } - sort.Strings(paths) + paths := slices.Sorted(maps.Keys(resourceChanges)) for _, path := range paths { configChange := resourceChanges[path] - output.WriteString(fmt.Sprintf(" %s: %s\n", path, configChange.Operation)) + fmt.Fprintf(&output, " %s: %s\n", path, configChange.Operation) } output.WriteString("\n") diff --git a/bundle/configsync/patch.go b/bundle/configsync/patch.go index 9853641e6bb..e002d31335c 100644 --- a/bundle/configsync/patch.go +++ b/bundle/configsync/patch.go @@ -2,12 +2,13 @@ package configsync import ( "bytes" + "cmp" "context" "errors" "fmt" "os" "regexp" - "sort" + "slices" "strconv" "strings" @@ -62,8 +63,8 @@ func ApplyChangesToYAML(ctx context.Context, b *bundle.Bundle, fieldChanges []Fi }) } - sort.Slice(result, func(i, j int) bool { - return result[i].Path < result[j].Path + slices.SortFunc(result, func(a, b FileChange) int { + return cmp.Compare(a.Path, b.Path) }) return result, nil @@ -281,7 +282,7 @@ func strPathToJSONPointer(pathStr string) (string, error) { func clearAddedFlowStyle(content []byte, fieldChanges []FieldChange) ([]byte, error) { var doc yaml.Node if err := yaml.Unmarshal(content, &doc); err != nil { - return content, nil + return content, nil //nolint:nilerr // return original content if YAML parsing fails } for _, fc := range fieldChanges { for _, candidate := range fc.FieldCandidates { diff --git a/bundle/configsync/resolve.go b/bundle/configsync/resolve.go index 396480111f5..01018365e67 100644 --- a/bundle/configsync/resolve.go +++ b/bundle/configsync/resolve.go @@ -1,11 +1,13 @@ package configsync import ( + "cmp" "context" "fmt" "io/fs" + "maps" "path/filepath" - "sort" + "slices" "strings" "github.com/databricks/cli/bundle" @@ -157,10 +159,7 @@ func adjustArrayIndex(path *structpath.PatternNode, operations map[string][]stru } } - adjustedIndex := originalIndex + adjustment - if adjustedIndex < 0 { - adjustedIndex = 0 - } + adjustedIndex := max(originalIndex+adjustment, 0) return structpath.NewPatternIndex(parentPath, adjustedIndex) } @@ -170,11 +169,7 @@ func ResolveChanges(ctx context.Context, b *bundle.Bundle, configChanges Changes var result []FieldChange targetName := b.Config.Bundle.Target - resourceKeys := make([]string, 0, len(configChanges)) - for resourceKey := range configChanges { - resourceKeys = append(resourceKeys, resourceKey) - } - sort.Strings(resourceKeys) + resourceKeys := slices.Sorted(maps.Keys(configChanges)) for _, resourceKey := range resourceKeys { resourceChanges := configChanges[resourceKey] @@ -187,25 +182,25 @@ func ResolveChanges(ctx context.Context, b *bundle.Bundle, configChanges Changes } // Sort field paths by depth (deeper first), then operation type (removals before adds), then alphabetically - sort.SliceStable(fieldPaths, func(i, j int) bool { - depthI := fieldPathsDepths[fieldPaths[i]] - depthJ := fieldPathsDepths[fieldPaths[j]] + slices.SortStableFunc(fieldPaths, func(a, b string) int { + depthA := fieldPathsDepths[a] + depthB := fieldPathsDepths[b] - if depthI != depthJ { - return depthI > depthJ + if depthA != depthB { + return cmp.Compare(depthB, depthA) } - opI := resourceChanges[fieldPaths[i]].Operation - opJ := resourceChanges[fieldPaths[j]].Operation + opA := resourceChanges[a].Operation + opB := resourceChanges[b].Operation - if opI == OperationRemove && opJ != OperationRemove { - return true + if opA == OperationRemove && opB != OperationRemove { + return -1 } - if opI != OperationRemove && opJ == OperationRemove { - return false + if opA != OperationRemove && opB == OperationRemove { + return 1 } - return fieldPaths[i] < fieldPaths[j] + return cmp.Compare(a, b) }) // Create indices map for this resource, path -> indices, that we could use to replace with added elements diff --git a/bundle/configsync/variables.go b/bundle/configsync/variables.go new file mode 100644 index 00000000000..e7bdff3696d --- /dev/null +++ b/bundle/configsync/variables.go @@ -0,0 +1,598 @@ +package configsync + +import ( + "context" + "errors" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/mutator" + "github.com/databricks/cli/bundle/direct/dstate" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/dynvar" + "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/structs/structpath" +) + +// varPrefix is the dyn.Path prefix for the ${var.X} shorthand. +var varPrefix = dyn.NewPath(dyn.Key("var")) + +// RestoreVariableReferences replaces hardcoded change values with variable +// references (${var.foo}, ${bundle.target}, ${resources.X.Y.id}) when the +// value can be traced back to a reference in the original YAML. Resource IDs +// are injected from state since they aren't materialized into the resolved +// config's dyn.Value tree. +// +// For Replace operations, restoration consults the pre-resolved YAML at the +// exact field position and tries three steps in order: +// 1. If the YAML had a pure ref (${var.X}, ${bundle.X}, ${resources.X.Y.id}) +// and its resolved value equals the new value, the original ref is kept. +// 2. If the YAML had a compound string (e.g., "/mnt/${var.account}/raw/X"), +// the template is realigned: variables whose resolved values still appear +// at their expected positions are kept, and only literal segments change. +// 3. Fallback for fields whose YAML was a pure ${var.X} but whose resolved +// value doesn't match: search all bundle variables for a unique scalar +// match. On a unique match, the field is re-targeted to that variable +// (e.g., ${var.schema} → ${var.dev_schema}). Multiple matches are +// ambiguous and skipped. The fallback is gated on the YAML field already +// being a pure ${var.X}, so hardcoded literals are never promoted. +// +// For Add operations, restoration is limited to new sequence elements (e.g., +// a new task appended to the tasks array). Within the new element, a leaf is +// restored only when a sibling element in the same sequence has a pure +// variable reference at the exact same relative path whose resolved value +// matches the leaf value. Non-sequence Adds (new map fields) are left +// untouched. +// +// The pre-resolved config is obtained by re-loading the bundle from disk +// through the standard loader mutators (entry point + includes + target +// overrides) but skipping variable resolution. This gives a fully merged +// view where ${var.X} and ${resources.X.Y.id} references are still literal +// strings — enabling correct sibling lookup even for sequences split across +// files via target overrides. +func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChanges []FieldChange) error { + preResolved := loadPreResolvedConfig(ctx, b) + if !preResolved.IsValid() { + return errors.New("pre-resolved config unavailable; variable-backed fields will be hardcoded") + } + resolved := b.Config.Value() + + // Mirror mutator.lookup's source-linked deployment override: when enabled, + // ${workspace.file_path} resolves to b.SyncRootPath rather than the typed + // workspace.file_path field (which still holds the default deploy path). + // Without this, substring matching against the typed value misses the + // actual deployed path and variables are lost on Replace. Keep this in + // sync with mutator.lookup if new overrides are added there. + if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + fpPath := dyn.NewPath(dyn.Key("workspace"), dyn.Key("file_path")) + if updated, err := dyn.SetByPath(resolved, fpPath, dyn.V(b.SyncRootPath)); err == nil { + resolved = updated + } + } + + // Augment resolved with resource IDs from state — only when the config + // actually uses ${resources.X.Y.id} references. The IDs aren't materialized + // into b.Config.Value() (they live in the StateDB), so we inject them here + // to enable sibling-based restoration. Skipped entirely for bundles with + // no resource refs to avoid opening state DB files unnecessarily. + resourceRefs := collectResourceIDRefs(preResolved) + if len(resourceRefs) > 0 { + if lookup := resourceIDLookup(ctx, b); lookup != nil { + resolved = injectResourceIDs(ctx, resolved, resourceRefs, lookup) + } else { + log.Debugf(ctx, "variable restoration: state DB unavailable, skipping resource ID injection for %d refs", len(resourceRefs)) + } + } + + for i := range fieldChanges { + fc := &fieldChanges[i] + + var newValue any + switch fc.Change.Operation { + case OperationReplace: + fieldValue, ok := preResolvedValueAt(preResolved, fc.FieldCandidates) + if !ok { + continue + } + newValue = restoreOriginalRefs(fc.Change.Value, fieldValue, resolved) + case OperationAdd: + siblings, ok := sequenceSiblings(preResolved, fc.FieldCandidates) + if !ok { + continue + } + newValue = restoreFromSiblings(fc.Change.Value, siblings, resolved) + case OperationUnknown, OperationRemove, OperationSkip: + continue + } + + fc.Change = &ConfigChangeDesc{ + Operation: fc.Change.Operation, + Value: newValue, + } + } + return nil +} + +// loadPreResolvedConfig loads the bundle's configuration through the standard +// loader mutators (entry point, includes, target overrides) but without +// variable resolution. The resulting dyn.Value is fully merged across files +// and targets, yet retains ${...} references as literal strings. Returns +// InvalidValue if loading fails (restoration is then skipped). +func loadPreResolvedConfig(ctx context.Context, b *bundle.Bundle) dyn.Value { + fresh := &bundle.Bundle{ + BundleRootPath: b.BundleRootPath, + BundleRoot: b.BundleRoot, + } + mutator.DefaultMutators(ctx, fresh) + if target := b.Config.Bundle.Target; target != "" { + if _, ok := fresh.Config.Targets[target]; ok { + bundle.ApplyContext(ctx, fresh, mutator.SelectTarget(target)) + } + } + return fresh.Config.Value() +} + +// resourceIDLookup returns a function that resolves resource keys to their +// deployed IDs from state. For the direct engine, the StateDB is already open +// on b.DeploymentBundle. For the terraform engine, the config snapshot is +// opened locally (it was downloaded by ensureSnapshotAvailable during +// DetectChanges). Returns nil if no state is available. +func resourceIDLookup(ctx context.Context, b *bundle.Bundle) func(string) string { + if b.DeploymentBundle.StateDB.Path != "" { + return b.DeploymentBundle.StateDB.GetResourceID + } + _, statePath := b.StateFilenameConfigSnapshot(ctx) + db := &dstate.DeploymentState{} + if err := db.Open(statePath); err != nil { + log.Debugf(ctx, "variable restoration: failed to open state DB at %s: %v", statePath, err) + return nil + } + return db.GetResourceID +} + +// collectResourceIDRefs walks the pre-resolved merged config to find pure +// ${resources...id} references. Returns the unique set of paths +// so the caller can inject IDs at those positions; returns nil if no such +// references exist. +func collectResourceIDRefs(preResolved dyn.Value) []dyn.Path { + seen := map[string]bool{} + var paths []dyn.Path + _ = dyn.WalkReadOnly(preResolved, func(_ dyn.Path, v dyn.Value) error { + s, ok := v.AsString() + if !ok || !dynvar.IsPureVariableReference(s) || seen[s] { + return nil + } + seen[s] = true + p, ok := dynvar.PureReferenceToPath(s) + if !ok { + return nil + } + if len(p) != 4 || p[0].Key() != "resources" || p[3].Key() != "id" { + return nil + } + paths = append(paths, p) + return nil + }) + return paths +} + +// injectResourceIDs populates the resolved dyn.Value with IDs from state for +// the given resource reference paths. Skips references whose IDs aren't in +// state or that can't be written back into the dyn.Value tree. +func injectResourceIDs(ctx context.Context, resolved dyn.Value, paths []dyn.Path, lookupID func(string) string) dyn.Value { + for _, p := range paths { + resourceKey := p[:3].String() + id := lookupID(resourceKey) + if id == "" { + log.Debugf(ctx, "variable restoration: no state entry for resource %q", resourceKey) + continue + } + updated, err := dyn.SetByPath(resolved, p, dyn.V(id)) + if err != nil { + log.Debugf(ctx, "variable restoration: SetByPath failed for %s: %v", p, err) + continue + } + resolved = updated + } + return resolved +} + +// resolveReferencePath converts a variable reference string to the dyn.Path +// where its resolved value can be found in the bundle config. It applies the +// same ${var.X} → variables.X.value shorthand rewriting as the variable +// resolution mutator. +func resolveReferencePath(refStr string) (dyn.Path, bool) { + p, ok := dynvar.PureReferenceToPath(refStr) + if !ok { + return nil, false + } + + if p.HasPrefix(varPrefix) && len(p) >= 2 { + newPath := dyn.NewPath( + dyn.Key("variables"), + p[1], + dyn.Key("value"), + ) + if len(p) > 2 { + newPath = newPath.Append(p[2:]...) + } + return newPath, true + } + + return p, true +} + +// restoreOriginalRefs recursively restores variable references for Replace +// operations. For pure variable references, restores when the resolved value +// matches. For compound interpolation (e.g., "${var.X}_suffix"), preserves +// variables whose resolved values still appear at their expected positions. +// When the original is a pure ${var.X} but its resolved value doesn't match the +// new value, falls back to a global lookup: if the new value uniquely matches +// a different variable, that variable is used instead. The field's prior use +// of a variable is the false-positive guard. +func restoreOriginalRefs(value any, preResolved, resolved dyn.Value) any { + switch v := value.(type) { + case string, bool, int64: + if ref, ok := matchOriginalRef(value, preResolved, resolved); ok { + return ref + } + if s, ok := value.(string); ok { + if restored, ok := restoreCompoundInterpolation(s, preResolved, resolved); ok { + return restored + } + } + if isPureVarRef(preResolved) { + if ref, ok := matchAnyVariable(value, resolved); ok { + return ref + } + } + return value + + case map[string]any: + preMap, _ := preResolved.AsMap() + for key, val := range v { + var childPre dyn.Value + if preMap.Len() > 0 { + if p, ok := preMap.GetPairByString(key); ok { + childPre = p.Value + } + } + v[key] = restoreOriginalRefs(val, childPre, resolved) + } + return v + + case []any: + preSeq, _ := preResolved.AsSequence() + for i, val := range v { + var childPre dyn.Value + if i < len(preSeq) { + childPre = preSeq[i] + } + v[i] = restoreOriginalRefs(val, childPre, resolved) + } + return v + + default: + return value + } +} + +// restoreFromSiblings recursively restores variable references for new +// sequence elements. For each leaf, it consults sibling elements at the same +// relative path: if exactly one unique pure variable reference across siblings +// resolves to the leaf value, that reference is substituted. Multiple +// different matching references are treated as ambiguous and skipped. +func restoreFromSiblings(value any, siblings []dyn.Value, resolved dyn.Value) any { + return restoreFromSiblingsAt(value, siblings, resolved, dyn.EmptyPath) +} + +func restoreFromSiblingsAt(value any, siblings []dyn.Value, resolved dyn.Value, relPath dyn.Path) any { + switch v := value.(type) { + case string, bool, int64: + refs := map[string]struct{}{} + strVal, isStr := value.(string) + for _, sib := range siblings { + sv, err := dyn.GetByPath(sib, relPath) + if err != nil { + continue + } + s, ok := sv.AsString() + if !ok { + continue + } + if dynvar.IsPureVariableReference(s) { + rp, ok := resolveReferencePath(s) + if !ok { + continue + } + rv, getErr := dyn.GetByPath(resolved, rp) + if getErr != nil { + continue + } + if rv.AsAny() == value { + refs[s] = struct{}{} + } + } else if isStr && dynvar.ContainsVariableReference(s) { + // Compound interpolation in sibling: try to align the new + // value against the sibling's template. If all variables + // match at their positions, the template (possibly with + // updated literal segments) is used. + if restored, ok := restoreCompoundInterpolation(strVal, sv, resolved); ok { + refs[restored] = struct{}{} + } + } + } + if len(refs) == 1 { + for ref := range refs { + return ref + } + } + return value + + case map[string]any: + for key, val := range v { + v[key] = restoreFromSiblingsAt(val, siblings, resolved, relPath.Append(dyn.Key(key))) + } + return v + + case []any: + for i, val := range v { + v[i] = restoreFromSiblingsAt(val, siblings, resolved, relPath.Append(dyn.Index(i))) + } + return v + + default: + return value + } +} + +// isPureVarRef reports whether the pre-resolved value at the field is a pure +// ${var.X} reference. Used to gate the fallback substitution: only fields that +// already used a variable can be re-targeted to a different variable. +func isPureVarRef(preResolved dyn.Value) bool { + if !preResolved.IsValid() { + return false + } + s, ok := preResolved.AsString() + if !ok || !dynvar.IsPureVariableReference(s) { + return false + } + p, ok := dynvar.PureReferenceToPath(s) + if !ok { + return false + } + return p.HasPrefix(varPrefix) +} + +// matchAnyVariable searches all bundle variables for a unique scalar value that +// equals remoteValue. Returns the ${var.X} reference on a unique match, "" +// otherwise. Multiple matches are treated as ambiguous and skipped. +func matchAnyVariable(remoteValue any, resolved dyn.Value) (string, bool) { + variables, err := dyn.GetByPath(resolved, dyn.NewPath(dyn.Key("variables"))) + if err != nil { + return "", false + } + vmap, ok := variables.AsMap() + if !ok { + return "", false + } + var match string + count := 0 + for _, pair := range vmap.Pairs() { + name, ok := pair.Key.AsString() + if !ok { + continue + } + v, getErr := dyn.GetByPath(pair.Value, dyn.NewPath(dyn.Key("value"))) + if getErr != nil { + continue + } + switch v.Kind() { + case dyn.KindString, dyn.KindInt, dyn.KindBool: + if v.AsAny() == remoteValue { + match = pathToRef(varPrefix.Append(dyn.Key(name))) + count++ + } + case dyn.KindInvalid, dyn.KindMap, dyn.KindSequence, dyn.KindFloat, dyn.KindTime, dyn.KindNil: + // Skip non-scalar / unsupported variable values. + } + } + if count == 1 { + return match, true + } + return "", false +} + +// pathToRef formats a dyn.Path as a "${...}" interpolation reference. +func pathToRef(p dyn.Path) string { + return "${" + p.String() + "}" +} + +// matchOriginalRef checks if the pre-resolved config value at this position +// was a pure variable reference whose resolved value equals remoteValue. +func matchOriginalRef(remoteValue any, preResolved, resolved dyn.Value) (string, bool) { + if !preResolved.IsValid() { + return "", false + } + s, ok := preResolved.AsString() + if !ok || !dynvar.IsPureVariableReference(s) { + return "", false + } + + resolvedPath, ok := resolveReferencePath(s) + if !ok { + return "", false + } + + resolvedV, err := dyn.GetByPath(resolved, resolvedPath) + if err != nil { + return "", false + } + + if resolvedV.AsAny() == remoteValue { + return s, true + } + return "", false +} + +// restoreCompoundInterpolation handles strings with mixed variable references +// and literal text, e.g., "/mnt/${var.account}/raw/landing". +// +// Algorithm: for each variable in the template, find the first occurrence of +// its resolved value in the remote string and substitute it back to its raw +// ${...} form. Variables whose resolved value no longer appears are dropped +// (the user changed them); literal segments can grow, shrink, or disappear +// freely. Returns false if no variable ends up in the result (e.g., the user +// replaced everything with an unrelated string). +// +// Known limitation: substring-matching is unanchored. If ${var.X}="in" and the +// new value contains "in" inside an unrelated word, that occurrence is still +// rewritten to ${var.X}. Variables in the template are processed in order of +// appearance, which is usually what the user expects. +func restoreCompoundInterpolation(remoteValue string, preResolved, resolved dyn.Value) (string, bool) { + if !preResolved.IsValid() { + return "", false + } + template, ok := preResolved.AsString() + if !ok || !dynvar.ContainsVariableReference(template) || dynvar.IsPureVariableReference(template) { + return "", false + } + + segments := parseTemplateSegments(template, resolved) + if segments == nil { + return "", false + } + + result := remoteValue + for _, seg := range segments { + if !seg.isVariable || seg.resolvedValue == "" { + continue + } + idx := strings.Index(result, seg.resolvedValue) + if idx < 0 { + continue + } + result = result[:idx] + seg.raw + result[idx+len(seg.resolvedValue):] + } + + if !dynvar.ContainsVariableReference(result) { + return "", false + } + return result, true +} + +// templateSegment represents either a literal string or a variable reference +// within a template string. +type templateSegment struct { + raw string // as it appears in the template (literal text or "${var.X}") + isVariable bool + resolvedValue string // only set for variable segments +} + +// parseTemplateSegments splits a template string like "/mnt/${var.X}/raw" +// into alternating literal and variable segments, resolving each variable. +// Returns nil if any variable can't be resolved. +func parseTemplateSegments(template string, resolved dyn.Value) []templateSegment { + ref, ok := dynvar.NewRef(dyn.V(template)) + if !ok { + return nil + } + + var segments []templateSegment + cursor := 0 + + for _, m := range ref.Matches { + fullMatch := m[0] + + idx := strings.Index(template[cursor:], fullMatch) + if idx < 0 { + return nil + } + + if idx > 0 { + segments = append(segments, templateSegment{ + raw: template[cursor : cursor+idx], + }) + } + + resolvedPath, ok := resolveReferencePath(fullMatch) + if !ok { + return nil + } + + resolvedV, err := dyn.GetByPath(resolved, resolvedPath) + if err != nil { + return nil + } + + resolvedStr, ok := resolvedV.AsString() + if !ok { + return nil + } + + segments = append(segments, templateSegment{ + raw: fullMatch, + isVariable: true, + resolvedValue: resolvedStr, + }) + + cursor += idx + len(fullMatch) + } + + if cursor < len(template) { + segments = append(segments, templateSegment{ + raw: template[cursor:], + }) + } + + return segments +} + +// preResolvedValueAt returns the pre-resolved dyn.Value at the field path, +// if the field exists in the merged pre-resolved config. +func preResolvedValueAt(preResolved dyn.Value, candidates []string) (dyn.Value, bool) { + for _, candidate := range candidates { + p, err := dyn.NewPathFromString(candidate) + if err != nil { + continue + } + v, err := dyn.GetByPath(preResolved, p) + if err == nil { + return v, true + } + } + return dyn.InvalidValue, false +} + +// sequenceSiblings returns the sibling elements of the parent sequence when +// the field change represents adding a new element to a sequence. The path's +// last component must be an index ([*] or [N]) and the parent must resolve +// to a sequence in the pre-resolved config. Returns false for non-sequence +// Adds (e.g., new map fields). +func sequenceSiblings(preResolved dyn.Value, candidates []string) ([]dyn.Value, bool) { + for _, candidate := range candidates { + node, err := structpath.ParsePattern(candidate) + if err != nil { + continue + } + _, hasIndex := node.Index() + if !hasIndex && !node.BracketStar() { + continue + } + p, err := dyn.NewPathFromString(node.Parent().String()) + if err != nil { + continue + } + parentValue, err := dyn.GetByPath(preResolved, p) + if err != nil { + continue + } + seq, ok := parentValue.AsSequence() + if !ok { + continue + } + return seq, true + } + return nil, false +} diff --git a/bundle/configsync/variables_test.go b/bundle/configsync/variables_test.go new file mode 100644 index 00000000000..9dd94c0cfb7 --- /dev/null +++ b/bundle/configsync/variables_test.go @@ -0,0 +1,115 @@ +package configsync + +import ( + "testing" + + "github.com/databricks/cli/libs/dyn" + "github.com/stretchr/testify/assert" +) + +// TestRestoreOriginalRefs_HardcodedFieldNotRewritten fences the Replace safety +// invariant: a hardcoded leaf must never be rewritten to a variable reference +// just because the remote value coincidentally matches a variable elsewhere. +func TestRestoreOriginalRefs_HardcodedFieldNotRewritten(t *testing.T) { + preResolved := dyn.V("us-east-1") + resolved := dyn.V(map[string]dyn.Value{ + "variables": dyn.V(map[string]dyn.Value{ + "region": dyn.V(map[string]dyn.Value{"value": dyn.V("main")}), + }), + }) + // Even though "main" matches ${var.region}, restoreOriginalRefs must NOT + // rewrite it — the original was hardcoded. + result := restoreOriginalRefs("main", preResolved, resolved) + assert.Equal(t, "main", result) +} + +// TestRestoreFromSiblings_ValueMatchesVariableButDifferentPath fences the Add +// false-positive guard: a new leaf's value matching a variable at a DIFFERENT +// relative path in a sibling must not trigger restoration. +func TestRestoreFromSiblings_ValueMatchesVariableButDifferentPath(t *testing.T) { + // Sibling uses ${var.retry_count}=5 at .max_retries. New element has + // .min_retry_interval=5 — coincidental match at a DIFFERENT relative path. + siblings := []dyn.Value{ + dyn.V(map[string]dyn.Value{ + "task_key": dyn.V("main"), + "max_retries": dyn.V("${var.retry_count}"), + }), + } + resolved := dyn.V(map[string]dyn.Value{ + "variables": dyn.V(map[string]dyn.Value{ + "retry_count": dyn.V(map[string]dyn.Value{"value": dyn.V(int64(5))}), + }), + }) + value := map[string]any{ + "task_key": "other", + "min_retry_interval": int64(5), + } + result := restoreFromSiblings(value, siblings, resolved).(map[string]any) + assert.Equal(t, "other", result["task_key"]) + assert.Equal(t, int64(5), result["min_retry_interval"]) +} + +// TestRestoreFromSiblings_AmbiguousAcrossSiblings fences the multi-variable +// same-value rule: when two siblings use different variables at the same +// relative path that both resolve to the same value, restoration is skipped. +func TestRestoreFromSiblings_AmbiguousAcrossSiblings(t *testing.T) { + siblings := []dyn.Value{ + dyn.V(map[string]dyn.Value{"default": dyn.V("${var.landing_schema}")}), + dyn.V(map[string]dyn.Value{"default": dyn.V("${var.curated_schema}")}), + } + resolved := dyn.V(map[string]dyn.Value{ + "variables": dyn.V(map[string]dyn.Value{ + "landing_schema": dyn.V(map[string]dyn.Value{"value": dyn.V("raw_data")}), + "curated_schema": dyn.V(map[string]dyn.Value{"value": dyn.V("raw_data")}), + }), + }) + value := map[string]any{"default": "raw_data"} + result := restoreFromSiblings(value, siblings, resolved).(map[string]any) + assert.Equal(t, "raw_data", result["default"]) +} + +// TestRestoreCompoundInterpolation covers the template alignment algorithm. +// End-to-end coverage (pure ref match, sibling match, non-sequence skip, etc.) +// lives in acceptance/bundle/config-remote-sync/resolve_variables. +func TestRestoreCompoundInterpolation(t *testing.T) { + resolved := dyn.V(map[string]dyn.Value{ + "variables": dyn.V(map[string]dyn.Value{ + "host": dyn.V(map[string]dyn.Value{"value": dyn.V("dev-sql.example.com")}), + "port": dyn.V(map[string]dyn.Value{"value": dyn.V("1433")}), + "db": dyn.V(map[string]dyn.Value{"value": dyn.V("analytics_dev")}), + "acct": dyn.V(map[string]dyn.Value{"value": dyn.V("acct")}), + }), + }) + + tests := []struct { + name string + template string + remote string + want string + }{ + { + name: "suffix change", + template: "/mnt/${var.acct}/raw/landing", + remote: "/mnt/acct/raw/landing_v2", + want: "/mnt/${var.acct}/raw/landing_v2", + }, + { + name: "partial variable change preserves others", + template: "jdbc:sqlserver://${var.host}:${var.port};database=${var.db}", + remote: "jdbc:sqlserver://dev-sql.example.com:5432;database=analytics_dev", + want: "jdbc:sqlserver://${var.host}:5432;database=${var.db}", + }, + { + name: "completely unrelated value falls back to hardcoded", + template: "${var.acct}-phi-encryption-key", + remote: "master-encryption-key-v2", + want: "master-encryption-key-v2", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := restoreOriginalRefs(tt.remote, dyn.V(tt.template), resolved) + assert.Equal(t, tt.want, result) + }) + } +} diff --git a/bundle/deploy/filer.go b/bundle/deploy/filer.go index b65f08a6782..a6b36f8d04e 100644 --- a/bundle/deploy/filer.go +++ b/bundle/deploy/filer.go @@ -16,7 +16,7 @@ import ( ) // FilerFactory is a function that returns a filer.Filer. -type FilerFactory func(b *bundle.Bundle) (filer.Filer, error) +type FilerFactory func(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) type stateFiler struct { filer filer.Filer @@ -25,6 +25,19 @@ type stateFiler struct { root filer.WorkspaceRootPath } +// orgIDHeaders returns headers with X-Databricks-Org-Id set if a workspace ID +// is configured. SPOG hosts require this header to route requests to the +// correct workspace. +func (s stateFiler) orgIDHeaders() map[string]string { + wsID := s.apiClient.Config.WorkspaceID + if wsID == "" { + return nil + } + return map[string]string{ + "X-Databricks-Org-Id": wsID, + } +} + func (s stateFiler) Delete(ctx context.Context, path string, mode ...filer.DeleteMode) error { return s.filer.Delete(ctx, path, mode...) } @@ -50,7 +63,7 @@ func (s stateFiler) Read(ctx context.Context, path string) (io.ReadCloser, error var buf bytes.Buffer urlPath := "/api/2.0/workspace-files/" + url.PathEscape(strings.TrimLeft(absPath, "/")) - err = s.apiClient.Do(ctx, http.MethodGet, urlPath, nil, nil, nil, &buf) + err = s.apiClient.Do(ctx, http.MethodGet, urlPath, s.orgIDHeaders(), nil, nil, &buf) if err != nil { return nil, err } @@ -75,13 +88,13 @@ func (s stateFiler) Write(ctx context.Context, path string, reader io.Reader, mo // This API has a higher than 10 MB limits and allows to export large state files. // We don't use the same API for read because it doesn't correct get the file content for notebooks and returns // "File Not Found" error instead. -func StateFiler(b *bundle.Bundle) (filer.Filer, error) { - f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(), b.Config.Workspace.StatePath) +func StateFiler(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) { + f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(ctx), b.Config.Workspace.StatePath) if err != nil { return nil, err } - apiClient, err := client.New(b.WorkspaceClient().Config) + apiClient, err := client.New(b.WorkspaceClient(ctx).Config) if err != nil { return nil, fmt.Errorf("failed to create API client: %w", err) } diff --git a/bundle/deploy/files/delete.go b/bundle/deploy/files/delete.go index 971186d5b07..921a4d2f31e 100644 --- a/bundle/deploy/files/delete.go +++ b/bundle/deploy/files/delete.go @@ -23,7 +23,7 @@ func (m *delete) Name() string { func (m *delete) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { cmdio.LogString(ctx, "Deleting files...") - err := b.WorkspaceClient().Workspace.Delete(ctx, workspace.Delete{ + err := b.WorkspaceClient(ctx).Workspace.Delete(ctx, workspace.Delete{ //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. Path: b.Config.Workspace.RootPath, Recursive: true, }) diff --git a/bundle/deploy/files/sync.go b/bundle/deploy/files/sync.go index 73c73f9abaa..90f44a98d8b 100644 --- a/bundle/deploy/files/sync.go +++ b/bundle/deploy/files/sync.go @@ -35,12 +35,12 @@ func GetSyncOptions(ctx context.Context, b *bundle.Bundle) (*sync.SyncOptions, e Exclude: b.Config.Sync.Exclude, RemotePath: b.Config.Workspace.FilePath, - Host: b.WorkspaceClient().Config.Host, + Host: b.WorkspaceClient(ctx).Config.Host, Full: false, SnapshotBasePath: cacheDir, - WorkspaceClient: b.WorkspaceClient(), + WorkspaceClient: b.WorkspaceClient(ctx), } if b.Config.Workspace.CurrentUser != nil { diff --git a/bundle/deploy/lock/acquire.go b/bundle/deploy/lock/acquire.go index d4f788c3cac..6e4844ca5ff 100644 --- a/bundle/deploy/lock/acquire.go +++ b/bundle/deploy/lock/acquire.go @@ -22,10 +22,10 @@ func (m *acquire) Name() string { return "lock:acquire" } -func (m *acquire) init(b *bundle.Bundle) error { +func (m *acquire) init(ctx context.Context, b *bundle.Bundle) error { user := b.Config.Workspace.CurrentUser.UserName dir := b.Config.Workspace.StatePath - l, err := locker.CreateLocker(user, dir, b.WorkspaceClient()) + l, err := locker.CreateLocker(user, dir, b.WorkspaceClient(ctx)) if err != nil { return err } @@ -41,7 +41,7 @@ func (m *acquire) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics return nil } - err := m.init(b) + err := m.init(ctx, b) if err != nil { return diag.FromErr(err) } diff --git a/bundle/deploy/metadata/upload.go b/bundle/deploy/metadata/upload.go index ee87816de41..79546518a36 100644 --- a/bundle/deploy/metadata/upload.go +++ b/bundle/deploy/metadata/upload.go @@ -28,7 +28,7 @@ func (m *upload) Name() string { } func (m *upload) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(), b.Config.Workspace.StatePath) + f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(ctx), b.Config.Workspace.StatePath) if err != nil { return diag.FromErr(err) } diff --git a/bundle/deploy/resource_path_mkdir.go b/bundle/deploy/resource_path_mkdir.go index 14d29d78692..ab070b925e6 100644 --- a/bundle/deploy/resource_path_mkdir.go +++ b/bundle/deploy/resource_path_mkdir.go @@ -25,10 +25,10 @@ func (m *resourcePathMkdir) Apply(ctx context.Context, b *bundle.Bundle) diag.Di return nil } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) // Optimisitcally create the resource path. If it already exists ignore the error. - err := w.Workspace.MkdirsByPath(ctx, b.Config.Workspace.ResourcePath) + err := w.Workspace.MkdirsByPath(ctx, b.Config.Workspace.ResourcePath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. var aerr *apierr.APIError if errors.As(err, &aerr) && aerr.ErrorCode == "RESOURCE_ALREADY_EXISTS" { return nil diff --git a/bundle/deploy/state_pull.go b/bundle/deploy/state_pull.go index e54c65e299b..832fac87fbb 100644 --- a/bundle/deploy/state_pull.go +++ b/bundle/deploy/state_pull.go @@ -22,7 +22,7 @@ type statePull struct { } func (s *statePull) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - f, err := s.filerFactory(b) + f, err := s.filerFactory(ctx, b) if err != nil { return diag.FromErr(err) } diff --git a/bundle/deploy/state_pull_test.go b/bundle/deploy/state_pull_test.go index f3799aba469..e30aeaa3237 100644 --- a/bundle/deploy/state_pull_test.go +++ b/bundle/deploy/state_pull_test.go @@ -2,6 +2,7 @@ package deploy import ( "bytes" + "context" "encoding/json" "io" "io/fs" @@ -43,7 +44,7 @@ type statePullOpts struct { } func testStatePull(t *testing.T, opts statePullOpts) { - s := &statePull{func(b *bundle.Bundle) (filer.Filer, error) { + s := &statePull{func(_ context.Context, b *bundle.Bundle) (filer.Filer, error) { f := mockfiler.NewMockFiler(t) deploymentStateData, err := json.Marshal(DeploymentState{ @@ -248,7 +249,7 @@ func TestStatePullSnapshotExists(t *testing.T) { } func TestStatePullNoState(t *testing.T) { - s := &statePull{func(b *bundle.Bundle) (filer.Filer, error) { + s := &statePull{func(_ context.Context, b *bundle.Bundle) (filer.Filer, error) { f := mockfiler.NewMockFiler(t) f.EXPECT().Read(mock.Anything, DeploymentStateFileName).Return(nil, os.ErrNotExist) @@ -421,7 +422,7 @@ func TestStatePullAndNotebookIsRemovedLocally(t *testing.T) { } func TestStatePullNewerDeploymentStateVersion(t *testing.T) { - s := &statePull{func(b *bundle.Bundle) (filer.Filer, error) { + s := &statePull{func(_ context.Context, b *bundle.Bundle) (filer.Filer, error) { f := mockfiler.NewMockFiler(t) deploymentStateData, err := json.Marshal(DeploymentState{ diff --git a/bundle/deploy/state_push.go b/bundle/deploy/state_push.go index 176a907c8dc..65a886a86fe 100644 --- a/bundle/deploy/state_push.go +++ b/bundle/deploy/state_push.go @@ -19,7 +19,7 @@ func (s *statePush) Name() string { } func (s *statePush) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - f, err := s.filerFactory(b) + f, err := s.filerFactory(ctx, b) if err != nil { return diag.FromErr(err) } diff --git a/bundle/deploy/state_push_test.go b/bundle/deploy/state_push_test.go index e711e5a3c64..eb2a0936c9b 100644 --- a/bundle/deploy/state_push_test.go +++ b/bundle/deploy/state_push_test.go @@ -1,6 +1,7 @@ package deploy import ( + "context" "encoding/json" "io" "os" @@ -15,7 +16,7 @@ import ( ) func TestStatePush(t *testing.T) { - s := &statePush{func(b *bundle.Bundle) (filer.Filer, error) { + s := &statePush{func(_ context.Context, b *bundle.Bundle) (filer.Filer, error) { f := mockfiler.NewMockFiler(t) f.EXPECT().Write(mock.Anything, DeploymentStateFileName, mock.MatchedBy(func(r *os.File) bool { diff --git a/bundle/deploy/terraform/check_dashboards_modified_remotely.go b/bundle/deploy/terraform/check_dashboards_modified_remotely.go index 370b5d4124b..4e56eb8e1d1 100644 --- a/bundle/deploy/terraform/check_dashboards_modified_remotely.go +++ b/bundle/deploy/terraform/check_dashboards_modified_remotely.go @@ -87,7 +87,7 @@ func (l *checkDashboardsModifiedRemotely) Apply(ctx context.Context, b *bundle.B path := dyn.MustPathFromString("resources.dashboards." + dashboard.Name) loc := b.Config.GetLocation(path.String()) - actual, err := b.WorkspaceClient().Lakeview.GetByDashboardId(ctx, dashboard.ID) + actual, err := b.WorkspaceClient(ctx).Lakeview.GetByDashboardId(ctx, dashboard.ID) if err != nil { diags = diags.Append(diag.Diagnostic{ Severity: diag.Error, diff --git a/bundle/deploy/terraform/init.go b/bundle/deploy/terraform/init.go index dee43b24c71..cbabeb3fee1 100644 --- a/bundle/deploy/terraform/init.go +++ b/bundle/deploy/terraform/init.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "io/fs" + "maps" "os" "os/exec" "path/filepath" "runtime" + "slices" "strings" "github.com/databricks/cli/bundle" @@ -20,7 +22,6 @@ import ( "github.com/databricks/cli/libs/log" "github.com/hashicorp/hc-install/product" "github.com/hashicorp/terraform-exec/tfexec" - "golang.org/x/exp/maps" ) func findExecPath(ctx context.Context, b *bundle.Bundle, tf *config.Terraform, installer Installer) (string, error) { @@ -335,7 +336,7 @@ func Initialize(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { return diag.FromErr(err) } - environ, err := b.AuthEnv() + environ, err := b.AuthEnv(ctx) if err != nil { return diag.FromErr(err) } @@ -363,7 +364,7 @@ func Initialize(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { } // Configure environment variables for auth for Terraform to use. - log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(maps.Keys(environ), ", ")) + log.Debugf(ctx, "Environment variables for Terraform: %s", strings.Join(slices.Collect(maps.Keys(environ)), ", ")) err = tfe.SetEnv(environ) if err != nil { return diag.FromErr(err) diff --git a/bundle/deploy/terraform/init_test.go b/bundle/deploy/terraform/init_test.go index 84ad50cc257..323d073f813 100644 --- a/bundle/deploy/terraform/init_test.go +++ b/bundle/deploy/terraform/init_test.go @@ -3,9 +3,11 @@ package terraform import ( "context" "fmt" + "maps" "os" "path/filepath" "runtime" + "slices" "strings" "testing" @@ -18,7 +20,6 @@ import ( "github.com/hashicorp/hc-install/product" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "golang.org/x/exp/maps" ) func unsetEnv(t *testing.T, name string) { @@ -208,7 +209,7 @@ func TestSetProxyEnvVars(t *testing.T) { env = make(map[string]string, 0) err = setProxyEnvVars(t.Context(), env, b) require.NoError(t, err) - assert.ElementsMatch(t, []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"}, maps.Keys(env)) + assert.ElementsMatch(t, []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"}, slices.Collect(maps.Keys(env))) // Upper case set. clearEnv() @@ -218,7 +219,7 @@ func TestSetProxyEnvVars(t *testing.T) { env = make(map[string]string, 0) err = setProxyEnvVars(t.Context(), env, b) require.NoError(t, err) - assert.ElementsMatch(t, []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"}, maps.Keys(env)) + assert.ElementsMatch(t, []string{"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"}, slices.Collect(maps.Keys(env))) } func TestSetUserAgentExtra_Python(t *testing.T) { diff --git a/bundle/deploy/terraform/install.go b/bundle/deploy/terraform/install.go index 0302ad025cf..09412d9031e 100644 --- a/bundle/deploy/terraform/install.go +++ b/bundle/deploy/terraform/install.go @@ -21,10 +21,11 @@ type tfInstaller struct{} // Install installs a Terraform binary using the HashiCorp installer library. func (i tfInstaller) Install(ctx context.Context, dir string, version *version.Version) (string, error) { installer := &releases.ExactVersion{ - Product: product.Terraform, - Version: version, - InstallDir: dir, - Timeout: 1 * time.Minute, + Product: product.Terraform, + Version: version, + InstallDir: dir, + Timeout: 1 * time.Minute, + ArmoredPublicKey: hashicorpPublicKey, } return installer.Install(ctx) } diff --git a/bundle/deploy/terraform/lifecycle_test.go b/bundle/deploy/terraform/lifecycle_test.go index b07b4888906..7f56248bb44 100644 --- a/bundle/deploy/terraform/lifecycle_test.go +++ b/bundle/deploy/terraform/lifecycle_test.go @@ -17,6 +17,7 @@ func TestConvertLifecycleForAllResources(t *testing.T) { ignoredResources := []string{ "catalogs", "external_locations", + "vector_search_endpoints", } for resourceType := range supportedResources { diff --git a/bundle/deploy/terraform/pubkey.go b/bundle/deploy/terraform/pubkey.go new file mode 100644 index 00000000000..e7609056524 --- /dev/null +++ b/bundle/deploy/terraform/pubkey.go @@ -0,0 +1,137 @@ +package terraform + +// hashicorpPublicKey is HashiCorp's release-signing public key with self-signatures +// refreshed on 2026-02-19 (expiration extended to ~2035). +// +// The embedded key in hc-install v0.9.3 has a UserID self-signature that expired +// on 2026-04-18, which breaks Terraform checksum verification. hc-install#355 +// added refreshed signatures, but go-crypto v1 only reads the first armored +// block and keeps one SelfSignature per Identity, so the fix is a no-op when +// relying on the embedded key. We pass this refreshed block to +// releases.ExactVersion.ArmoredPublicKey directly. +// +// Source: https://github.com/hashicorp/hc-install/blob/main/internal/pubkey/pubkey.go +// +// (second -----BEGIN PGP PUBLIC KEY BLOCK----- block, added in PR #355) +const hashicorpPublicKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGB9+xkBEACabYZOWKmgZsHTdRDiyPJxhbuUiKX65GUWkyRMJKi/1dviVxOX +PG6hBPtF48IFnVgxKpIb7G6NjBousAV+CuLlv5yqFKpOZEGC6sBV+Gx8Vu1CICpl +Zm+HpQPcIzwBpN+Ar4l/exCG/f/MZq/oxGgH+TyRF3XcYDjG8dbJCpHO5nQ5Cy9h +QIp3/Bh09kET6lk+4QlofNgHKVT2epV8iK1cXlbQe2tZtfCUtxk+pxvU0UHXp+AB +0xc3/gIhjZp/dePmCOyQyGPJbp5bpO4UeAJ6frqhexmNlaw9Z897ltZmRLGq1p4a +RnWL8FPkBz9SCSKXS8uNyV5oMNVn4G1obCkc106iWuKBTibffYQzq5TG8FYVJKrh +RwWB6piacEB8hl20IIWSxIM3J9tT7CPSnk5RYYCTRHgA5OOrqZhC7JefudrP8n+M +pxkDgNORDu7GCfAuisrf7dXYjLsxG4tu22DBJJC0c/IpRpXDnOuJN1Q5e/3VUKKW +mypNumuQpP5lc1ZFG64TRzb1HR6oIdHfbrVQfdiQXpvdcFx+Fl57WuUraXRV6qfb +4ZmKHX1JEwM/7tu21QE4F1dz0jroLSricZxfaCTHHWNfvGJoZ30/MZUrpSC0IfB3 +iQutxbZrwIlTBt+fGLtm3vDtwMFNWM+Rb1lrOxEQd2eijdxhvBOHtlIcswARAQAB +tERIYXNoaUNvcnAgU2VjdXJpdHkgKGhhc2hpY29ycC5jb20vc2VjdXJpdHkpIDxz +ZWN1cml0eUBoYXNoaWNvcnAuY29tPokCVAQTAQoAPgIbAwULCQgHAgYVCgkICwIE +FgIDAQIeAQIXgBYhBMh0AR8KtAURDQIQVTQ2XZRy10aPBQJplkfQBQkQrOy3AAoJ +EDQ2XZRy10aPw6gP/3GUEMUa6mCRuuSOT9UnziPIvXYd63mcN6A6Jwmwj8JaB2qu +OCijvJkw56UbZK3x1FZIbe0hA6VUAwNSNmSIxVJkilgwIYYFO0tnL79XhIeP7jYF +ydXLZ4rTi1FDl8lltAujTNARdY8UGg4hGlcM9OrEeXEFLWugJNiChL15FVoxZqIS +jeduaEqyxGfJnyVwy8z3pZfgODeFr7xs2NkUIMSfuRg24VcL4aW8Frt3jW8P45y3 +o/5fsi6Aw2tZ0wD9NSgkVc8VD1NRV9eSZ95Bv+Awf9IXa+Cn5OCjc8Jc+XF+nLfB +oPswOO7E8dLiuBUw6/GzSLMbVs8qf8BNXB92dOe1VccVTqjCxK2sEpVaHh7e+co8 +d8lDGBIWMGh7NS6XlGORpFb/T6gxjjOYUV3SKd4QDebUUG8kMkb5juLljOoq+YOP +vgNLDZLZteFpmH+zB9DpOY1YtHZB/OD+DtzLMaSl6VPF2Ln0j5aQGwNDt7sheyAe +sXbu0qn2H5FxojSfvhT0kUDKZ0mgg5y3Oflg49MiAOhjLGY0JocFpBeMILw27fbw +fpIBP7siQWFTFJ1O+l2NQiWAwC2x5fX2EakyCBJmrkPV2hr4nEogNqg9/RDskIUq +cpcOOd/0BntiXMyUCCH2AoCt5acaTQ0WU6CAosZPojOYhtGGgOgeQSdflpMSuQIN +BGB9+xkBEACoklYsfvWRCjOwS8TOKBTfl8myuP9V9uBNbyHufzNETbhYeT33Cj0M +GCNd9GdoaknzBQLbQVSQogA+spqVvQPz1MND18GIdtmr0BXENiZE7SRvu76jNqLp +KxYALoK2Pc3yK0JGD30HcIIgx+lOofrVPA2dfVPTj1wXvm0rbSGA4Wd4Ng3d2AoR +G/wZDAQ7sdZi1A9hhfugTFZwfqR3XAYCk+PUeoFrkJ0O7wngaon+6x2GJVedVPOs +2x/XOR4l9ytFP3o+5ILhVnsK+ESVD9AQz2fhDEU6RhvzaqtHe+sQccR3oVLoGcat +ma5rbfzH0Fhj0JtkbP7WreQf9udYgXxVJKXLQFQgel34egEGG+NlbGSPG+qHOZtY +4uWdlDSvmo+1P95P4VG/EBteqyBbDDGDGiMs6lAMg2cULrwOsbxWjsWka8y2IN3z +1stlIJFvW2kggU+bKnQ+sNQnclq3wzCJjeDBfucR3a5WRojDtGoJP6Fc3luUtS7V +5TAdOx4dhaMFU9+01OoH8ZdTRiHZ1K7RFeAIslSyd4iA/xkhOhHq89F4ECQf3Bt4 +ZhGsXDTaA/VgHmf3AULbrC94O7HNqOvTWzwGiWHLfcxXQsr+ijIEQvh6rHKmJK8R +9NMHqc3L18eMO6bqrzEHW0Xoiu9W8Yj+WuB3IKdhclT3w0pO4Pj8gQARAQABiQI8 +BBgBCgAmAhsMFiEEyHQBHwq0BRENAhBVNDZdlHLXRo8FAmmWR+0FCRCs7NQACgkQ +NDZdlHLXRo/R0A//QW1opBlzWSmWww1q9QuJA2WCIIs8tJKRDOsmgJPscNpzwZFU +N1Df0wWNjqi1BDReei7lZTHwUk+ebBn0bkI3ANmmgYg7LBueAt5UWSingOc+rvKA +N32BDzBYkMckRzJSQsmeC5hm3J3wLSy90uaIlrJJE9GJZkf/W2Ob+4SQZZ+dnnRP +JokDdW1DuZS9PbxSLJKD5eIWHBxJnFM1CmHfOfrjTJ+MYvVGM5sxSY8R7E+GADj5 +L/i4N+tTFJLuTMYARGfA6d+KPKcMJtgpUPjSMAg8nGUhukctpuBs27mOKW0CBtmJ +82X/qYROTL0+vGTvUYflYiuceVlhX/kw0JZnMaG5V/mpHq8SwD07pCGOf69j/mNa +5EL3++Pmzg0s0stw3Ea5pCN0cL/nKkoWchHBfW15W4JOnKAIspyD1vH670P4WfeV +E9B9d6tgKSbM/9JlXoQS5ZdG+kbdosieELhmVWmvojyK7K+Ry6C9wgd+UfnW5jXd +iNwKW3KHuautQwlFhHRNMyDg08c+pI5emTMT3IUQyGWo+Gska3TqGujFcABx7Ip+ +mHNmMrCkSD+XC2bvzvRR7FcM0/B9fsjLX/Wttm5vRJ1d2oAoEPvw2IZnJIXpOt2z +zo55sJTztNu4lWGgDVgtp9SXO5a0E5YvFHQNZN5QLeVTTFu6I7qG+ME1E/K5Ag0E +YH3+JQEQALivllTjMolxUW2OxrXb+a2Pt6vjCBsiJzrUj0Pa63U+lT9jldbCCfgP +wDpcDuO1O05Q8k1MoYZ6HddjWnqKG7S3eqkV5c3ct3amAXp513QDKZUfIDylOmhU +qvxjEgvGjdRjz6kECFGYr6Vnj/p6AwWv4/FBRFlrq7cnQgPynbIH4hrWvewp3Tqw +GVgqm5RRofuAugi8iZQVlAiQZJo88yaztAQ/7VsXBiHTn61ugQ8bKdAsr8w/ZZU5 +HScHLqRolcYg0cKN91c0EbJq9k1LUC//CakPB9mhi5+aUVUGusIM8ECShUEgSTCi +KQiJUPZ2CFbbPE9L5o9xoPCxjXoX+r7L/WyoCPTeoS3YRUMEnWKvc42Yxz3meRb+ +BmaqgbheNmzOah5nMwPupJYmHrjWPkX7oyyHxLSFw4dtoP2j6Z7GdRXKa2dUYdk2 +x3JYKocrDoPHh3Q0TAZujtpdjFi1BS8pbxYFb3hHmGSdvz7T7KcqP7ChC7k2RAKO +GiG7QQe4NX3sSMgweYpl4OwvQOn73t5CVWYp/gIBNZGsU3Pto8g27vHeWyH9mKr4 +cSepDhw+/X8FGRNdxNfpLKm7Vc0Sm9Sof8TRFrBTqX+vIQupYHRi5QQCuYaV6OVr +ITeegNK3So4m39d6ajCR9QxRbmjnx9UcnSYYDmIB6fpBuwT0ogNtABEBAAGJBHIE +GAEKACYCGwIWIQTIdAEfCrQFEQ0CEFU0Nl2UctdGjwUCYH4bgAUJAeFQ2wJAwXQg +BBkBCgAdFiEEs2y6kaLAcwxDX8KAsLRBCXaFtnYFAmB9/iUACgkQsLRBCXaFtnYX +BhAAlxejyFXoQwyGo9U+2g9N6LUb/tNtH29RHYxy4A3/ZUY7d/FMkArmh4+dfjf0 +p9MJz98Zkps20kaYP+2YzYmaizO6OA6RIddcEXQDRCPHmLts3097mJ/skx9qLAf6 +rh9J7jWeSqWO6VW6Mlx8j9m7sm3Ae1OsjOx/m7lGZOhY4UYfY627+Jf7WQ5103Qs +lgQ09es/vhTCx0g34SYEmMW15Tc3eCjQ21b1MeJD/V26npeakV8iCZ1kHZHawPq/ +aCCuYEcCeQOOteTWvl7HXaHMhHIx7jjOd8XX9V+UxsGz2WCIxX/j7EEEc7CAxwAN +nWp9jXeLfxYfjrUB7XQZsGCd4EHHzUyCf7iRJL7OJ3tz5Z+rOlNjSgci+ycHEccL +YeFAEV+Fz+sj7q4cFAferkr7imY1XEI0Ji5P8p/uRYw/n8uUf7LrLw5TzHmZsTSC +UaiL4llRzkDC6cVhYfqQWUXDd/r385OkE4oalNNE+n+txNRx92rpvXWZ5qFYfv7E +95fltvpXc0iOugPMzyof3lwo3Xi4WZKc1CC/jEviKTQhfn3WZukuF5lbz3V1PQfI +xFsYe9WYQmp25XGgezjXzp89C/OIcYsVB1KJAKihgbYdHyUN4fRCmOszmOUwEAKR +3k5j4X8V5bk08sA69NVXPn2ofxyk3YYOMYWW8ouObnXoS8QJEDQ2XZRy10aPMpsQ +AIbwX21erVqUDMPn1uONP6o4NBEq4MwG7d+fT85rc1U0RfeKBwjucAE/iStZDQoM +ZKWvGhFR+uoyg1LrXNKuSPB82unh2bpvj4zEnJsJadiwtShTKDsikhrfFEK3aCK8 +Zuhpiu3jxMFDhpFzlxsSwaCcGJqcdwGhWUx0ZAVD2X71UCFoOXPjF9fNnpy80YNp +flPjj2RnOZbJyBIM0sWIVMd8F44qkTASf8K5Qb47WFN5tSpePq7OCm7s8u+lYZGK +wR18K7VliundR+5a8XAOyUXOL5UsDaQCK4Lj4lRaeFXunXl3DJ4E+7BKzZhReJL6 +EugV5eaGonA52TWtFdB8p+79wPUeI3KcdPmQ9Ll5Zi/jBemY4bzasmgKzNeMtwWP +fk6WgrvBwptqohw71HDymGxFUnUP7XYYjic2sVKhv9AevMGycVgwWBiWroDCQ9Ja +btKfxHhI2p+g+rcywmBobWJbZsujTNjhtme+kNn1mhJsD3bKPjKQfAxaTskBLb0V +wgV21891TS1Dq9kdPLwoS4XNpYg2LLB4p9hmeG3fu9+OmqwY5oKXsHiWc43dei9Y +yxZ1AAUOIaIdPkq+YG/PhlGE4YcQZ4RPpltAr0HfGgZhmXWigbGS+66pUj+Ojysc +j0K5tCVxVu0fhhFpOlHv0LWaxCbnkgkQH9jfMEJkAWMOuQINBGCAXCYBEADW6RNr +ZVGNXvHVBqSiOWaxl1XOiEoiHPt50Aijt25yXbG+0kHIFSoR+1g6Lh20JTCChgfQ +kGGjzQvEuG1HTw07YhsvLc0pkjNMfu6gJqFox/ogc53mz69OxXauzUQ/TZ27GDVp +UBu+EhDKt1s3OtA6Bjz/csop/Um7gT0+ivHyvJ/jGdnPEZv8tNuSE/Uo+hn/Q9hg +8SbveZzo3C+U4KcabCESEFl8Gq6aRi9vAfa65oxD5jKaIz7cy+pwb0lizqlW7H9t +Qlr3dBfdIcdzgR55hTFC5/XrcwJ6/nHVH/xGskEasnfCQX8RYKMuy0UADJy72TkZ +bYaCx+XXIcVB8GTOmJVoAhrTSSVLAZspfCnjwnSxisDn3ZzsYrq3cV6sU8b+QlIX +7VAjurE+5cZiVlaxgCjyhKqlGgmonnReWOBacCgL/UvuwMmMp5TTLmiLXLT7uxeG +ojEyoCk4sMrqrU1jevHyGlDJH9Taux15GILDwnYFfAvPF9WCid4UZ4Ouwjcaxfys +3LxNiZIlUsXNKwS3mhiMRL4TRsbs4k4QE+LIMOsauIvcvm8/frydvQ/kUwIhVTH8 +0XGOH909bYtJvY3fudK7ShIwm7ZFTduBJUG473E/Fn3VkhTmBX6+PjOC50HR/Hyb +waRCzfDruMe3TAcE/tSP5CUOb9C7+P+hPzQcDwARAQABiQRyBBgBCgAmAhsCFiEE +yHQBHwq0BRENAhBVNDZdlHLXRo8FAmmWSAoFCRCqi+QCQMF0IAQZAQoAHRYhBDdO +x1tIWRNgSoMcx8ggxtXNJ6uHBQJggFwmAAoJEMggxtXNJ6uHRfAP/2CGdSyg0K7U +66Vygl0dugxrMm8O3/Oe211BKdQsFUSWAznOTRTK/zvMUHO4LJAlYvdtZ6xDa4XH +l9FYQ8MR9ZV0OuOlAZvU4IJDLPVCU09X/UzX/GEoZL0R5esvwPAXopMaRHCfXJeI +/gEaB94UhAeYlwpcRn0eSuk1vyZx7GRE6/hog8DCf4hoT40dW20gGe58xcvJ+mRY +lC0lr16WH08wuUcee6+dgu+4Cg6SG6+zt9cMyl8VnTUL5BK/V3MebnYZJK0RFDNn +nXDhzStgOd5gOeIL+xBPXHd0/ld/rDM74SFExpuS+hNsyo+xMQ/HJavak21MFinu +l9COwfGEmlAXTGMY30Lf3Pt/eAkbwgmGc966VSoRmOFEXJVlDr+yJR6ru+7j50z8 +lAv6Lsop7sun1Qysbo0swf6W1qgPf6VWbx91NTFLkw0+gD8jxwrU5ZMkeSuntX9d +pjuZS29CflXXIRPlvhuiDPicwTpYuIUx37vHveAH5gnowZg247x780Urrsx8duTX +8CI9MAnqzm4dFAiRlwE8bvLk+l9wekiXA9gIMZiVNqNlduXIqvAG21Wdgq8qyeXK +y/XWCVKDQOmEbFAltfNam8E3KEw0fl199x+93d5ckDGcPzUYPbNkCuIwngC/ZN96 +pDafF3Z12fSNfhZUe0C8td8KAszYa96GCRA0Nl2UctdGj1gKD/4jOGhEGTg88Vyu +PVjeK+zkwrTIZSvHdUHfTt/+rTLSNb/RQiBCUQuEZvafj6FrntS7bAEhccGqH894 +T3St5K0AXWkvsLd6K+cbIQdlnFA2zb6geJUCk6qx5NgWpRc3i0DS7CheGwl+Bwu7 ++n9pNjNjiHV+rYDgqbQXG0dtGysB0/3qIRgEDHFO0HJu/dcte4oXrQIqrZrpOwe8 +WxqFqdU918JpSUcc8coiFp9YtwpgqQNxGVZ+rhgnTGdZzk1f/Yhhimh+2B0ReaFv +k3UzVBj3HQ9C6+Ot3MyDEhSgdhjr9e25Tm9S5YfhwtWmghRw9RKPyLMSXSxm/Uc0 +mK1NucAp8TQBwKqKzNpCk5IdrBSWRUbjOoOFyzyCsY6gS285GCpSIzI39hTf+3gd +wYPlE6fj+F2TZzdhx62DPnzBzBHnByYTVdJ649bx0FFp4Q+5TbIWtxu/AQkRDxmW +NQfE+6GgeshlrhXWsh6+PGDzt+2raG6zUT913sdz7Ctw4fLjmsKOTdTz3Xa9pr8l +xfI/JuukSgt9o/n3GirhTB3zE1w/I/Xt6k7oASiP3zQSuHtB/CYKYHDtOCWwjo7J +PEGtb/FkreKNxsk/p20jnlrB8WZxxswdr2Vri9NmFeyMDVX7qF3WqT+8aCV9GtS1 +GCHx/5nGBdDwoxEsXqpI3IUqPb6FDg== +=wtp+ +-----END PGP PUBLIC KEY BLOCK-----` diff --git a/bundle/deploy/terraform/tfdyn/convert_job.go b/bundle/deploy/terraform/tfdyn/convert_job.go index 83683f5e896..c9f7e8219a4 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job.go +++ b/bundle/deploy/terraform/tfdyn/convert_job.go @@ -1,10 +1,10 @@ package tfdyn import ( + "cmp" "context" "fmt" "slices" - "sort" "strings" "github.com/databricks/cli/bundle/internal/tf/schema" @@ -101,7 +101,7 @@ func patchApplyPolicyDefaultValues(_ dyn.Path, v dyn.Value) (dyn.Value, error) { } } - sort.Strings(paths) + slices.Sort(paths) valList := make([]dyn.Value, len(paths)) for i, s := range paths { valList[i] = dyn.V(s) @@ -132,19 +132,22 @@ func convertJobResource(ctx context.Context, vin dyn.Value) (dyn.Value, error) { var err error tasks, ok := vin.Get("tasks").AsSequence() if ok { - sort.Slice(tasks, func(i, j int) bool { + slices.SortFunc(tasks, func(a, b dyn.Value) int { // We sort the tasks by their task key. Tasks without task keys are ordered // before tasks with task keys. We do not error for those tasks // since presence of a task_key is validated for in the Jobs backend. - tk1, ok := tasks[i].Get("task_key").AsString() - if !ok { - return true + tk1, ok1 := a.Get("task_key").AsString() + tk2, ok2 := b.Get("task_key").AsString() + if !ok1 && ok2 { + return -1 } - tk2, ok := tasks[j].Get("task_key").AsString() - if !ok { - return false + if ok1 && !ok2 { + return 1 } - return tk1 < tk2 + if !ok1 && !ok2 { + return 0 + } + return cmp.Compare(tk1, tk2) }) vout, err = dyn.Set(vin, "tasks", dyn.V(tasks)) if err != nil { diff --git a/bundle/deploy/terraform/tfdyn/convert_job_test.go b/bundle/deploy/terraform/tfdyn/convert_job_test.go index 782075fc7f5..1f67369e916 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_job_test.go @@ -293,7 +293,7 @@ func TestConvertJobApplyPolicyDefaultValues(t *testing.T) { // TestSupportedTypeTasksComplete verifies that supportedTypeTasks includes all task types with a Source field. func TestSupportedTypeTasksComplete(t *testing.T) { // Use reflection to find all task types that have a Source field - taskType := reflect.TypeOf(jobs.Task{}) + taskType := reflect.TypeFor[jobs.Task]() var tasksWithSource []string for i := range taskType.NumField() { diff --git a/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go b/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go index f16b6b85951..fe6620a00c2 100644 --- a/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_pipeline_test.go @@ -19,7 +19,7 @@ func TestConvertPipeline(t *testing.T) { // This fields is not part of TF schema yet, but once we upgrade to TF version that supports it, this test will fail because run_as // will be exposed which is expected and test will need to be updated. RunAs: &pipelines.RunAs{ - UserName: "foo@bar.com", + UserName: "foo@bar.test", }, // We expect AllowDuplicateNames and DryRun to be ignored and not passed to the TF output. // This is not supported by TF now, so we don't want to expose it. @@ -123,7 +123,7 @@ func TestConvertPipeline(t *testing.T) { }, }, "run_as": map[string]any{ - "user_name": "foo@bar.com", + "user_name": "foo@bar.test", }, }, out.Pipeline["my_pipeline"]) diff --git a/bundle/deploy/terraform/util_test.go b/bundle/deploy/terraform/util_test.go index 59b0f03635f..752d8063012 100644 --- a/bundle/deploy/terraform/util_test.go +++ b/bundle/deploy/terraform/util_test.go @@ -75,7 +75,7 @@ func TestParseResourcesStateWithExistingStateFile(t *testing.T) { "storage": "dbfs:/123456", "target": "test_dev", "timeouts": null, - "url": "https://test.com" + "url": "https://test.test" }, "sensitive_attributes": [] } diff --git a/bundle/deployplan/plan.go b/bundle/deployplan/plan.go index e0dcd9b2886..b35357c7c28 100644 --- a/bundle/deployplan/plan.go +++ b/bundle/deployplan/plan.go @@ -100,6 +100,7 @@ type ChangeDesc struct { const ( ReasonBackendDefault = "backend_default" ReasonAlias = "alias" + ReasonURLNormalization = "url_normalization" ReasonRemoteAlreadySet = "remote_already_set" ReasonEmpty = "empty" ReasonCustom = "custom" diff --git a/bundle/direct/apply.go b/bundle/direct/apply.go index 42d80efab34..e7186f64670 100644 --- a/bundle/direct/apply.go +++ b/bundle/direct/apply.go @@ -86,7 +86,9 @@ func (d *DeploymentUnit) Recreate(ctx context.Context, db *dstate.DeploymentStat return fmt.Errorf("deleting old id=%s: %w", oldID, err) } - err = db.SaveState(d.ResourceKey, "", nil, nil) + // Drop the state entry so a subsequent failure of Create leaves no malformed + // (empty-id) entry behind. The next plan will see "no state" and retry as Create. + err = db.DeleteState(d.ResourceKey) if err != nil { return fmt.Errorf("deleting state: %w", err) } diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 10baf64d8ae..f6bcea316cd 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -23,7 +23,6 @@ import ( "github.com/databricks/cli/libs/structs/structdiff" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structvar" - "github.com/databricks/cli/libs/utils" "github.com/databricks/databricks-sdk-go" ) @@ -182,16 +181,15 @@ func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks } dbentry, hasEntry := b.StateDB.GetResourceEntry(resourceKey) - if !hasEntry { + // Tolerate empty-id entries from older partial-recreate failures + // (apply.Recreate now deletes state on the way through, but pre-fix + // state files may still carry a malformed entry). Treat as missing + // and let the resource be re-created on this plan. + if !hasEntry || dbentry.ID == "" { entry.Action = deployplan.Create return true } - if dbentry.ID == "" { - logdiag.LogError(ctx, fmt.Errorf("%s: invalid state: empty id", errorPrefix)) - return false - } - savedState, err := parseState(adapter.StateType(), dbentry.State) if err != nil { logdiag.LogError(ctx, fmt.Errorf("%s: interpreting state: %w", errorPrefix, err)) @@ -518,7 +516,7 @@ func isEmpty(rv reflect.Value) bool { return rv.Len() == 0 } - // Certain structs come up set even if fully empty and and not set by client, e.g. email_notifications and webhook_notifications + // Certain structs come up set even if fully empty and not set by client, e.g. email_notifications and webhook_notifications if isEmptyStruct(rv) { return true } @@ -950,8 +948,11 @@ func extractReferences(root dyn.Value, node string) (map[string]string, error) { if !ok { return nil } - // Store the original string that contains references, not individual references - refs[p.String()] = ref.Str + // Store the original string that contains references, not individual references. + // Convert dyn.Path to structpath string because refs are later parsed by structpath.ParsePath. + // dyn.Path.String() uses dot notation which is ambiguous for keys containing dots; + // structpath uses bracket notation (['key.with.dots']) which round-trips correctly. + refs[dynPathToStructPath(p).String()] = ref.Str return nil }) if err != nil { @@ -960,6 +961,19 @@ func extractReferences(root dyn.Value, node string) (map[string]string, error) { return refs, nil } +// dynPathToStructPath converts a dyn.Path to a structpath.PathNode. +func dynPathToStructPath(p dyn.Path) *structpath.PathNode { + var node *structpath.PathNode + for _, c := range p { + if key := c.Key(); key != "" { + node = structpath.NewStringKey(node, key) + } else { + node = structpath.NewIndex(node, c.Index()) + } + } + return node +} + func (b *DeploymentBundle) getAdapterForKey(resourceKey string) (*dresources.Adapter, error) { group := config.GetResourceTypeFromKey(resourceKey) if group == "" { @@ -968,7 +982,7 @@ func (b *DeploymentBundle) getAdapterForKey(resourceKey string) (*dresources.Ada adapter, ok := b.Adapters[group] if !ok { - return nil, fmt.Errorf("resource type %q not supported, available: %s", group, strings.Join(utils.SortedKeys(b.Adapters), ", ")) + return nil, fmt.Errorf("resource type %q not supported, available: %s", group, strings.Join(slices.Sorted(maps.Keys(b.Adapters)), ", ")) } return adapter, nil diff --git a/bundle/direct/bundle_plan_test.go b/bundle/direct/bundle_plan_test.go new file mode 100644 index 00000000000..ccfb7cb517f --- /dev/null +++ b/bundle/direct/bundle_plan_test.go @@ -0,0 +1,37 @@ +package direct + +import ( + "testing" + + "github.com/databricks/cli/libs/dyn" + "github.com/stretchr/testify/assert" +) + +func TestDynPathToStructPath(t *testing.T) { + tests := []struct { + path dyn.Path + expected string + }{ + { + path: dyn.NewPath(dyn.Key("foo"), dyn.Key("bar")), + expected: "foo.bar", + }, + { + path: dyn.NewPath(dyn.Key("foo"), dyn.Index(1), dyn.Key("bar")), + expected: "foo[1].bar", + }, + { + path: dyn.NewPath(dyn.Key("configuration"), dyn.Key("europris.swipe.egress_streaming_schema")), + expected: "configuration['europris.swipe.egress_streaming_schema']", + }, + { + path: dyn.NewPath(dyn.Key("tags"), dyn.Key("it's.here")), + expected: "tags['it''s.here']", + }, + } + + for _, tc := range tests { + node := dynPathToStructPath(tc.path) + assert.Equal(t, tc.expected, node.String()) + } +} diff --git a/bundle/direct/dresources/README.md b/bundle/direct/dresources/README.md index afd48024f0e..a20b68c4ebd 100644 --- a/bundle/direct/dresources/README.md +++ b/bundle/direct/dresources/README.md @@ -1,22 +1,62 @@ # Guidelines on implementing a resource +## Core constraints + - See adapter.go on what methods are needed and what constraints are present. - Return SDK errors directly, no need to wrap them. Things like current operation, resource key, id are already added by the caller and will be part of the error message. - - Although the arguments are pointers, they are never nil, so nil checks are not needed. The passed id argument is never empty string. + - Although the arguments to resource methods like DoCreate are pointers, they are never nil, so nil checks are not needed. The passed id argument is never empty string. - When returning id from DoCreate() and from DoUpdateWithID() there is no need to check that returned id is non-empty, this will be done by the framework and converted to error. An exception could be made if default error message lacks the necessary context. - The arguments point to actual struct that will be persisted in state, any changes to it will affect what is stored in state. Usually there is no need to change it, but if there is, there should always be detailed explanation. - Each Create/Update/Delete method should correspond to one API call. We persist state right after, so there is minimum chance of having orphaned resources. - - The logic what kind of update it is should be in FieldTriggers / ClassifyChange methods. The methods performing update should not have logic in them on what method to call. - - Create/Update/Delete methods should not need to read any state. (We can implement support for passing remoteState we already to these methods if such need arises though). - - Prefer “with refresh” variants of methods if resource API supports that. That avoids explicit DoRead() call. - - For update with complex logic, ensure that DoUpdate() does not result in no-op. If certain fields could not be updated, they should be excluded at plan level in resources.yml. + - We should calculate the update type during plan phase. This means it should be configured via resources.yml as much as possible, falling back to OverrideChangeDesc(). The DoUpdate() implementation should be as predictable as possible based on the plan. In particular, avoid reading remote state in DoUpdate() to decide what kind of update to dod. + - Create/Update/Delete methods should not need to do read requests. They can read state passed to them via \*PlanEntry but that should be reserved for exceptional cases. Most resources should have 1-1 mapping to single SDK/API call. + - For update with complex logic, ensure that DoUpdate() never results in no-op. If certain fields could not be updated, they should be excluded at plan level in resources.yml. + +## Field classification in resources.yml + +Each field with special plan/deploy behavior must be declared in `resources.yml`. Choose the right category: + + - **`backend_defaults`**: The backend may fill in a value when the user doesn't specify one. Suppresses the diff when the user's config is nil/empty but remote has a value. Optionally restrict to specific allowed remote values via `values:`. Use for fields the API fills in as defaults (e.g., `format`, `run_if`, `node_type_id`). Link to TF provider suppression comment in the same format as existing entries. + - **`ignore_remote_changes`**: Ignore changes the remote makes to this field. Use for fields the backend manages (e.g., cloud-provider attributes like `aws_attributes`, `gcp_attributes`) or fields not returned by the update endpoint. Reason codes: + - `output_only` — the field is computed by the backend; the user never sets it + - `input_only` — accepted on create/update but not returned by GET (e.g., write-only tokens, flags) + - `managed` — managed by the cloud provider or platform, not by the user config + - **`ignore_local_changes`**: Ignore changes the user makes to this field. Use for fields that cannot be updated via API — either they are immutable after creation or require a separate API that is not yet implemented. Must have a comment in resources.yml explaining why. + - **`recreate_on_changes`**: Changing this field requires delete + create. Use for truly immutable fields (name, type, location). The reason should reference API docs or TF provider. + - **`update_id_on_changes`**: Changing this field changes the resource's ID. Requires `DoUpdateWithID` to be implemented. + +## Update mask + +When implementing DoUpdate, use a **static list** of updatable API field names or `*` if the API supports it. + +Do **not** derive update mask field names from `entry.Changes`. The paths in `entry.Changes` are engine-internal Go struct paths, not API field names. Mapping them to API fields is fragile: it breaks when struct layout changes, silently skips nested updates, and conflicts with the direct engine's full-update model. + +If a resource has fields that must not be sent in updates (deploy-only, lifecycle-only, etc.), document them explicitly with a `var` block and a comment explaining each exclusion. + +## Async APIs: WaitAfterCreate / WaitAfterUpdate -Nice to have +For resources whose create or update is asynchronous (the resource is not immediately ready after the call returns), implement `WaitAfterCreate` and/or `WaitAfterUpdate` instead of polling inline inside DoCreate/DoUpdate. These are the correct extension points in the framework, and polling inline bypasses state persistence timing. + +## Slice ordering: KeyedSlices + +If the API may return a slice's elements in a different order between calls (e.g., `depends_on` in job tasks, `privileges` in grants), implement `KeyedSlices` to compare elements by a natural key rather than by index. Without this, every deploy after any reordering shows phantom diffs. + +## State backward compatibility + +The state struct is serialized to JSON and persisted between deploys. Backward incompatible changes will result in a drift, which depending +on field behaviour might result in recreate. See dstate/migrate.go on how to handle state migration. + +## OverrideChangeDesc + +Use `OverrideChangeDesc` only as a last resort when `resources.yml` settings cannot express the needed logic. Skipping an action with `change.Action = deployplan.Skip` in `OverrideChangeDesc` creates a silent no-op: the plan shows no change even if the user's config differs from remote. Document the skip reason clearly in both the comment and `change.Reason`. + +## Nice to have - Add link to corresponding API documentation before each method. - Add link to corresponding terraform resource implementation at the top of the file. -Testing +## Testing + - Make sure to implement CRUD for testserver in libs/testserver - Test first with go test ./bundle/direct/dresources - You might need to add test fixture in all\_test.go @@ -24,3 +64,8 @@ Testing - See acceptance/bundle/resources/volumes - Prefer smaller tests for each operation. - Make sure bundle deploy/plan/debug plan/summary/destroy are covered + - Add an invariant test config in acceptance/bundle/invariant/configs/.yml.tmpl + - See existing configs in that directory for the format. + - Add bind/unbind tests in acceptance/bundle/deployment/bind// + - These verify that binding an existing resource and then deploying/destroying works correctly. + - For new resource types, run at least one test on a live cloud environment. diff --git a/bundle/direct/dresources/adapter.go b/bundle/direct/dresources/adapter.go index bd4e7c750a3..f931208c3cc 100644 --- a/bundle/direct/dresources/adapter.go +++ b/bundle/direct/dresources/adapter.go @@ -154,7 +154,7 @@ func loadKeyedSlices(call *calladapt.BoundCaller) (map[string]any, error) { } func (a *Adapter) initMethods(resource any) error { - err := calladapt.EnsureNoExtraMethods(resource, calladapt.TypeOf[IResource]()) + err := calladapt.EnsureNoExtraMethods(resource, reflect.TypeFor[IResource]()) if err != nil { return err } @@ -164,7 +164,7 @@ func (a *Adapter) initMethods(resource any) error { } // RemapState is optional when remote type already matches state type. - a.remapState, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "RemapState") + a.remapState, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "RemapState") if err != nil { return err } @@ -186,37 +186,37 @@ func (a *Adapter) initMethods(resource any) error { // Optional methods with varying signatures: - a.doUpdate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoUpdate") + a.doUpdate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoUpdate") if err != nil { return err } - a.doUpdateWithID, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoUpdateWithID") + a.doUpdateWithID, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoUpdateWithID") if err != nil { return err } - a.waitAfterCreate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "WaitAfterCreate") + a.waitAfterCreate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "WaitAfterCreate") if err != nil { return err } - a.waitAfterUpdate, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "WaitAfterUpdate") + a.waitAfterUpdate, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "WaitAfterUpdate") if err != nil { return err } - a.overrideChangeDesc, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "OverrideChangeDesc") + a.overrideChangeDesc, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "OverrideChangeDesc") if err != nil { return err } - a.doResize, err = calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "DoResize") + a.doResize, err = calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "DoResize") if err != nil { return err } - keyedSlicesCall, err := calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), "KeyedSlices") + keyedSlicesCall, err := calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), "KeyedSlices") if err != nil { return err } @@ -535,7 +535,7 @@ func (a *Adapter) KeyedSlices() map[string]any { // prepareCallRequired prepares a call and ensures the method is found. func prepareCallRequired(resource any, methodName string) (*calladapt.BoundCaller, error) { - caller, err := calladapt.PrepareCall(resource, calladapt.TypeOf[IResource](), methodName) + caller, err := calladapt.PrepareCall(resource, reflect.TypeFor[IResource](), methodName) if err != nil { return nil, fmt.Errorf("%s: %w", methodName, err) } diff --git a/bundle/direct/dresources/all.go b/bundle/direct/dresources/all.go index 6a7381a3fc7..ddc30c41f54 100644 --- a/bundle/direct/dresources/all.go +++ b/bundle/direct/dresources/all.go @@ -30,6 +30,7 @@ var SupportedResources = map[string]any{ "secret_scopes": (*ResourceSecretScope)(nil), "model_serving_endpoints": (*ResourceModelServingEndpoint)(nil), "quality_monitors": (*ResourceQualityMonitor)(nil), + "vector_search_endpoints": (*ResourceVectorSearchEndpoint)(nil), // Permissions "jobs.permissions": (*ResourcePermissions)(nil), @@ -45,6 +46,7 @@ var SupportedResources = map[string]any{ "secret_scopes.permissions": (*ResourceSecretScopeAcls)(nil), "model_serving_endpoints.permissions": (*ResourcePermissions)(nil), "dashboards.permissions": (*ResourcePermissions)(nil), + "vector_search_endpoints.permissions": (*ResourcePermissions)(nil), // Grants "catalogs.grants": (*ResourceGrants)(nil), diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 63caa5cfed3..9f0dc07e90b 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -27,6 +27,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/postgres" "github.com/databricks/databricks-sdk-go/service/serving" "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -239,6 +240,13 @@ var testConfig map[string]any = map[string]any{ DatasetSchema: "myschema", }, }, + + "vector_search_endpoints": &resources.VectorSearchEndpoint{ + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "my-endpoint", + EndpointType: vectorsearch.EndpointTypeStandard, + }, + }, } type prepareWorkspace func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) @@ -421,7 +429,7 @@ var testDeps = map[string]prepareWorkspace{ parentPath := "/Workspace/Users/user@example.com" // Create parent directory if it doesn't exist - err := client.Workspace.MkdirsByPath(ctx, parentPath) + err := client.Workspace.MkdirsByPath(ctx, parentPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { return nil, err } @@ -473,6 +481,24 @@ var testDeps = map[string]prepareWorkspace{ }, nil }, + "vector_search_endpoints.permissions": func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) { + waiter, err := client.VectorSearchEndpoints.CreateEndpoint(ctx, vectorsearch.CreateEndpoint{ + Name: "vs-endpoint-permissions", + EndpointType: vectorsearch.EndpointTypeStandard, + }) + if err != nil { + return nil, err + } + + return &PermissionsState{ + ObjectID: "/vector-search-endpoints/" + waiter.Response.Id, + EmbeddedSlice: []StatePermission{{ + Level: "CAN_MANAGE", + UserName: "user@example.com", + }}, + }, nil + }, + "alerts.permissions": func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) { resp, err := client.AlertsV2.CreateAlert(ctx, sql.CreateAlertV2Request{ Alert: sql.AlertV2{ diff --git a/bundle/direct/dresources/apitypes.generated.yml b/bundle/direct/dresources/apitypes.generated.yml index 8dfabd1098e..a80b3baa69b 100644 --- a/bundle/direct/dresources/apitypes.generated.yml +++ b/bundle/direct/dresources/apitypes.generated.yml @@ -30,7 +30,7 @@ postgres_branches: postgres.BranchSpec postgres_endpoints: postgres.EndpointSpec -postgres_projects: postgres.ProjectSpec +postgres_projects: postgres.ProjectStatus quality_monitors: catalog.CreateMonitor @@ -44,4 +44,6 @@ sql_warehouses: sql.EditWarehouseRequest synced_database_tables: database.SyncedDatabaseTable +vector_search_endpoints: vectorsearch.CreateEndpoint + volumes: catalog.CreateVolumeRequestContent diff --git a/bundle/direct/dresources/app.go b/bundle/direct/dresources/app.go index c9ee96e082c..5f882170613 100644 --- a/bundle/direct/dresources/app.go +++ b/bundle/direct/dresources/app.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "github.com/databricks/cli/bundle/appdeploy" @@ -161,14 +162,27 @@ func (r *ResourceApp) DoCreate(ctx context.Context, config *AppState) (string, * return app.Name, nil, nil } +var UpdateMaskFields = []string{ + "description", + "budget_policy_id", + "usage_policy_id", + "resources", + "user_api_scopes", + "compute_size", + "git_repository", + "telemetry_export_destinations", +} + +var updateMask = strings.Join(UpdateMaskFields, ",") + func (r *ResourceApp) DoUpdate(ctx context.Context, id string, config *AppState, entry *PlanEntry) (*AppRemote, error) { - // Use "*" to update all App API fields. Deploy-only fields (source_code_path, config, + // Deploy-only fields (source_code_path, config, // git_source, lifecycle) are not part of apps.App and thus excluded from the request body. if hasAppChanges(entry) { request := apps.AsyncUpdateAppRequest{ App: &config.App, AppName: id, - UpdateMask: "*", + UpdateMask: updateMask, } updateWaiter, err := r.client.Apps.CreateUpdate(ctx, request) if err != nil { @@ -185,46 +199,48 @@ func (r *ResourceApp) DoUpdate(ctx context.Context, id string, config *AppState, } } + return nil, r.manageLifecycle(ctx, id, config, remoteIsStarted(entry)) +} + +func (r *ResourceApp) manageLifecycle(ctx context.Context, id string, config *AppState, alreadyStarted bool) error { if config.Lifecycle == nil || config.Lifecycle.Started == nil { - return nil, nil + return nil } desiredStarted := *config.Lifecycle.Started - remoteStarted := remoteIsStarted(entry) - if desiredStarted { // lifecycle.started=true: ensure the app compute is running and deploy the latest code. - if !remoteStarted { + if !alreadyStarted { startWaiter, err := r.client.Apps.Start(ctx, apps.StartAppRequest{Name: id}) if err != nil { - return nil, err + return err } startedApp, err := startWaiter.Get() if err != nil { - return nil, err + return err } if err := appdeploy.WaitForDeploymentToComplete(ctx, r.client, startedApp); err != nil { - return nil, err + return err } } deployment := appdeploy.BuildDeployment(config.SourceCodePath, config.Config, config.GitSource) if err := appdeploy.Deploy(ctx, r.client, id, deployment); err != nil { - return nil, err + return err } } else { // lifecycle.started=false: ensure the app compute is stopped. - if remoteStarted { + if alreadyStarted { stopWaiter, err := r.client.Apps.Stop(ctx, apps.StopAppRequest{Name: id}) if err != nil { - return nil, err + return err } if _, err = stopWaiter.Get(); err != nil { - return nil, err + return err } } } - return nil, nil + return nil } // deployOnlyFields are AppState fields managed via the Deploy API, not the App Update API. @@ -252,7 +268,7 @@ func hasAppChanges(entry *PlanEntry) bool { // OverrideChangeDesc skips source_code_path drift when the remote value is empty. // This happens when an app has no deployment yet (DefaultSourceCodePath is unset). func (*ResourceApp) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *AppRemote) error { - if path.String() == "source_code_path" && remote.SourceCodePath == "" { + if path.String() == "source_code_path" && (remote.SourceCodePath == "" || remote.SourceCodePath == "null") { change.Action = deployplan.Skip change.Reason = "no deployment" } @@ -306,7 +322,15 @@ func (r *ResourceApp) DoDelete(ctx context.Context, id string) error { } func (r *ResourceApp) WaitAfterCreate(ctx context.Context, config *AppState) (*AppRemote, error) { - return r.waitForApp(ctx, r.client, config.Name) + remote, err := r.waitForApp(ctx, r.client, config.Name) + if err != nil { + return nil, err + } + alreadyStarted := remote.Lifecycle != nil && remote.Lifecycle.Started != nil && *remote.Lifecycle.Started + if err := r.manageLifecycle(ctx, config.Name, config, alreadyStarted); err != nil { + return nil, err + } + return remote, nil } // waitForApp waits for the app to reach the target state. The target state is either ACTIVE or STOPPED. diff --git a/bundle/direct/dresources/app_test.go b/bundle/direct/dresources/app_test.go index ad9ca01e8a4..edb99c4cffb 100644 --- a/bundle/direct/dresources/app_test.go +++ b/bundle/direct/dresources/app_test.go @@ -1,6 +1,9 @@ package dresources import ( + "reflect" + "slices" + "strings" "testing" "github.com/databricks/cli/libs/testserver" @@ -120,3 +123,46 @@ func TestAppDoCreate_RetriesWhenGetReturnsNotFound(t *testing.T) { assert.Equal(t, 2, createCallCount, "expected Create to be called twice") assert.Equal(t, 1, getCallCount, "expected Get to be called once to check app state") } + +func TestAppDoUpdate_UpdateMaskHasAllFields(t *testing.T) { + // iterate over all apps.App fields using reflection and ensure that UpdateMaskFields contains all of them. + config := GetGeneratedResourceConfig("apps") + require.NotNil(t, config) + var nonUpdatableFields []string + for _, field := range config.IgnoreRemoteChanges { + nonUpdatableFields = append(nonUpdatableFields, field.Field.String()) + } + + for _, field := range config.RecreateOnChanges { + nonUpdatableFields = append(nonUpdatableFields, field.Field.String()) + } + + config = GetResourceConfig("apps") + require.NotNil(t, config) + for _, field := range config.IgnoreRemoteChanges { + nonUpdatableFields = append(nonUpdatableFields, field.Field.String()) + } + + for _, field := range config.RecreateOnChanges { + nonUpdatableFields = append(nonUpdatableFields, field.Field.String()) + } + + fields := reflect.TypeFor[apps.App]() + var allFields []string + for i := range fields.NumField() { + field := fields.Field(i) + jsonTag := field.Tag.Get("json") + if jsonTag == "" || jsonTag == "-" { + continue + } + jsonTag = strings.TrimSuffix(jsonTag, ",omitempty") + allFields = append(allFields, jsonTag) + if !slices.Contains(nonUpdatableFields, jsonTag) { + assert.Contains(t, UpdateMaskFields, jsonTag, "field %s is not in UpdateMaskFields and not marked as non-updatable", jsonTag) + } + } + + for _, field := range UpdateMaskFields { + assert.Contains(t, allFields, field, "field %s is in UpdateMaskFields but not in apps.App struct", field) + } +} diff --git a/bundle/direct/dresources/catalog.go b/bundle/direct/dresources/catalog.go index a9afa71cbf0..2e090ddfb8e 100644 --- a/bundle/direct/dresources/catalog.go +++ b/bundle/direct/dresources/catalog.go @@ -23,15 +23,16 @@ func (*ResourceCatalog) PrepareState(input *resources.Catalog) *catalog.CreateCa func (*ResourceCatalog) RemapState(info *catalog.CatalogInfo) *catalog.CreateCatalog { return &catalog.CreateCatalog{ - Comment: info.Comment, - ConnectionName: info.ConnectionName, - Name: info.Name, - Options: info.Options, - Properties: info.Properties, - ProviderName: info.ProviderName, - ShareName: info.ShareName, - StorageRoot: info.StorageRoot, - ForceSendFields: utils.FilterFields[catalog.CreateCatalog](info.ForceSendFields), + Comment: info.Comment, + ConnectionName: info.ConnectionName, + ManagedEncryptionSettings: info.ManagedEncryptionSettings, + Name: info.Name, + Options: info.Options, + Properties: info.Properties, + ProviderName: info.ProviderName, + ShareName: info.ShareName, + StorageRoot: info.StorageRoot, + ForceSendFields: utils.FilterFields[catalog.CreateCatalog](info.ForceSendFields), } } @@ -53,6 +54,7 @@ func (r *ResourceCatalog) DoUpdate(ctx context.Context, id string, config *catal Comment: config.Comment, EnablePredictiveOptimization: "", // Not supported by DABs IsolationMode: "", // Not supported by DABs + ManagedEncryptionSettings: config.ManagedEncryptionSettings, Name: id, NewName: "", // Only set if name actually changes (see DoUpdateWithID) Options: config.Options, @@ -75,6 +77,7 @@ func (r *ResourceCatalog) DoUpdateWithID(ctx context.Context, id string, config Comment: config.Comment, EnablePredictiveOptimization: "", // Not supported by DABs IsolationMode: "", // Not supported by DABs + ManagedEncryptionSettings: config.ManagedEncryptionSettings, Name: id, NewName: "", // Initialized below if needed Options: config.Options, diff --git a/bundle/direct/dresources/config.go b/bundle/direct/dresources/config.go index 0b71f513829..70f6dbb1d8d 100644 --- a/bundle/direct/dresources/config.go +++ b/bundle/direct/dresources/config.go @@ -75,48 +75,36 @@ var resourcesYAML []byte //go:embed resources.generated.yml var resourcesGeneratedYAML []byte -var ( - configOnce sync.Once - globalConfig *Config - generatedConfigOnce sync.Once - generatedConfig *Config - empty = ResourceLifecycleConfig{ - IgnoreRemoteChanges: nil, - IgnoreLocalChanges: nil, - RecreateOnChanges: nil, - UpdateIDOnChanges: nil, - BackendDefaults: nil, - } -) +var empty = ResourceLifecycleConfig{ + IgnoreRemoteChanges: nil, + IgnoreLocalChanges: nil, + RecreateOnChanges: nil, + UpdateIDOnChanges: nil, + BackendDefaults: nil, +} -// MustLoadConfig loads and parses the embedded resources.yml configuration. -// The config is loaded once and cached for subsequent calls. -// Panics if the embedded YAML is invalid. -func MustLoadConfig() *Config { - configOnce.Do(func() { - globalConfig = &Config{ - Resources: nil, - } - if err := yaml.Unmarshal(resourcesYAML, globalConfig); err != nil { +func mustParseConfig(data []byte) func() *Config { + return sync.OnceValue(func() *Config { + c := &Config{Resources: nil} + if err := yaml.Unmarshal(data, c); err != nil { panic(err) } + return c }) - return globalConfig } -// MustLoadGeneratedConfig loads and parses the embedded resources.generated.yml configuration. -// The config is loaded once and cached for subsequent calls. -// Panics if the embedded YAML is invalid. +var loadConfig = mustParseConfig(resourcesYAML) + +var loadGeneratedConfig = mustParseConfig(resourcesGeneratedYAML) + +// MustLoadConfig returns the parsed resources.yml configuration. +func MustLoadConfig() *Config { + return loadConfig() +} + +// MustLoadGeneratedConfig returns the parsed resources.generated.yml configuration. func MustLoadGeneratedConfig() *Config { - generatedConfigOnce.Do(func() { - generatedConfig = &Config{ - Resources: nil, - } - if err := yaml.Unmarshal(resourcesGeneratedYAML, generatedConfig); err != nil { - panic(err) - } - }) - return generatedConfig + return loadGeneratedConfig() } // GetResourceConfig returns the lifecycle config for a given resource type. diff --git a/bundle/direct/dresources/dashboard.go b/bundle/direct/dresources/dashboard.go index f33d7946e8c..6c4a9f1f611 100644 --- a/bundle/direct/dresources/dashboard.go +++ b/bundle/direct/dresources/dashboard.go @@ -281,7 +281,7 @@ func (r *ResourceDashboard) DoCreate(ctx context.Context, config *DashboardState // The API returns 404 if the parent directory doesn't exist. // If the parent directory doesn't exist, create it and try again. if err != nil && apierr.IsMissing(err) { - err = r.client.Workspace.MkdirsByPath(ctx, config.ParentPath) + err = r.client.Workspace.MkdirsByPath(ctx, config.ParentPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { return "", nil, fmt.Errorf("failed to create parent directory: %w", err) } diff --git a/bundle/direct/dresources/external_location.go b/bundle/direct/dresources/external_location.go index f9416567cbb..a9715b06190 100644 --- a/bundle/direct/dresources/external_location.go +++ b/bundle/direct/dresources/external_location.go @@ -25,8 +25,9 @@ func (*ResourceExternalLocation) RemapState(info *catalog.ExternalLocationInfo) return &catalog.CreateExternalLocation{ Comment: info.Comment, CredentialName: info.CredentialName, - // Output-only field mirrored into state to avoid churn in remapped config. + // Output-only fields mirrored into state to avoid churn in remapped config. EffectiveEnableFileEvents: info.EffectiveEnableFileEvents, + EffectiveFileEventQueue: info.EffectiveFileEventQueue, EnableFileEvents: info.EnableFileEvents, EncryptionDetails: info.EncryptionDetails, Fallback: info.Fallback, @@ -54,10 +55,10 @@ func (r *ResourceExternalLocation) DoCreate(ctx context.Context, config *catalog // DoUpdate updates the external location in place and returns remote state. func (r *ResourceExternalLocation) DoUpdate(ctx context.Context, id string, config *catalog.CreateExternalLocation, _ *PlanEntry) (*catalog.ExternalLocationInfo, error) { updateRequest := catalog.UpdateExternalLocation{ - Comment: config.Comment, - CredentialName: config.CredentialName, - // Output-only field; never sent in update payload. - EffectiveEnableFileEvents: false, + Comment: config.Comment, + CredentialName: config.CredentialName, + EffectiveEnableFileEvents: false, // Output-only field; never sent in update payload. + EffectiveFileEventQueue: nil, EnableFileEvents: config.EnableFileEvents, EncryptionDetails: config.EncryptionDetails, Fallback: config.Fallback, @@ -79,10 +80,10 @@ func (r *ResourceExternalLocation) DoUpdate(ctx context.Context, id string, conf // DoUpdateWithID updates the external location and returns the new ID if the name changes. func (r *ResourceExternalLocation) DoUpdateWithID(ctx context.Context, id string, config *catalog.CreateExternalLocation) (string, *catalog.ExternalLocationInfo, error) { updateRequest := catalog.UpdateExternalLocation{ - Comment: config.Comment, - CredentialName: config.CredentialName, - // Output-only field; never sent in update payload. - EffectiveEnableFileEvents: false, + Comment: config.Comment, + CredentialName: config.CredentialName, + EffectiveEnableFileEvents: false, // Output-only field; never sent in update payload. + EffectiveFileEventQueue: nil, EnableFileEvents: config.EnableFileEvents, EncryptionDetails: config.EncryptionDetails, Fallback: config.Fallback, diff --git a/bundle/direct/dresources/job.go b/bundle/direct/dresources/job.go index f10813b2fcb..9477bf52517 100644 --- a/bundle/direct/dresources/job.go +++ b/bundle/direct/dresources/job.go @@ -71,12 +71,18 @@ func getEnvironmentKey(x jobs.JobEnvironment) (string, string) { return "environment_key", x.EnvironmentKey } +func getDependsOnTaskKey(x jobs.TaskDependency) (string, string) { + return "task_key", x.TaskKey +} + func (*ResourceJob) KeyedSlices() map[string]any { return map[string]any{ - "tasks": getTaskKey, - "parameters": getParameterName, - "job_clusters": getJobClusterKey, - "environments": getEnvironmentKey, + "tasks": getTaskKey, + "parameters": getParameterName, + "job_clusters": getJobClusterKey, + "environments": getEnvironmentKey, + "tasks[*].depends_on": getDependsOnTaskKey, + "tasks[*].for_each_task.task.depends_on": getDependsOnTaskKey, } } diff --git a/bundle/direct/dresources/job_test.go b/bundle/direct/dresources/job_test.go index 012c9d70001..8b8c85c8a24 100644 --- a/bundle/direct/dresources/job_test.go +++ b/bundle/direct/dresources/job_test.go @@ -10,7 +10,7 @@ import ( // TestJobRemote verifies that all fields from jobs.Job (except Settings and pagination/internal fields) // are present in JobRemote. func TestJobRemote(t *testing.T) { - assertFieldsCovered(t, reflect.TypeOf(jobs.Job{}), reflect.TypeOf(JobRemote{}), map[string]bool{ + assertFieldsCovered(t, reflect.TypeFor[jobs.Job](), reflect.TypeFor[JobRemote](), map[string]bool{ "Settings": true, // Embedded as jobs.JobSettings "ForceSendFields": true, // Internal marshaling field "HasMore": true, // Pagination field, not relevant for single job read diff --git a/bundle/direct/dresources/model.go b/bundle/direct/dresources/model.go index 9373de99bab..52a3b1075de 100644 --- a/bundle/direct/dresources/model.go +++ b/bundle/direct/dresources/model.go @@ -13,6 +13,15 @@ type ResourceMlflowModel struct { client *databricks.WorkspaceClient } +// MlflowModelRemote wraps the API response with the numeric model ID. +// The state ID for models is the model name (used for CRUD operations), but +// the permissions API requires the numeric ID. This wrapper exposes the numeric +// ID as model_id, analogous to ModelServingEndpointRemote.EndpointId for serving endpoints. +type MlflowModelRemote struct { + ml.ModelDatabricks + ModelId string `json:"model_id"` +} + func (*ResourceMlflowModel) New(client *databricks.WorkspaceClient) *ResourceMlflowModel { return &ResourceMlflowModel{client: client} } @@ -21,53 +30,39 @@ func (*ResourceMlflowModel) PrepareState(input *resources.MlflowModel) *ml.Creat return &input.CreateModelRequest } -func (*ResourceMlflowModel) RemapState(model *ml.ModelDatabricks) *ml.CreateModelRequest { +func (*ResourceMlflowModel) RemapState(output *MlflowModelRemote) *ml.CreateModelRequest { return &ml.CreateModelRequest{ - Name: model.Name, - Tags: model.Tags, - Description: model.Description, - ForceSendFields: utils.FilterFields[ml.CreateModelRequest](model.ForceSendFields), + Name: output.Name, + Tags: output.Tags, + Description: output.Description, + ForceSendFields: utils.FilterFields[ml.CreateModelRequest](output.ForceSendFields), } } -func (r *ResourceMlflowModel) DoRead(ctx context.Context, id string) (*ml.ModelDatabricks, error) { +func (r *ResourceMlflowModel) DoRead(ctx context.Context, id string) (*MlflowModelRemote, error) { response, err := r.client.ModelRegistry.GetModel(ctx, ml.GetModelRequest{ Name: id, }) if err != nil { return nil, err } - return response.RegisteredModelDatabricks, nil + return &MlflowModelRemote{ + ModelDatabricks: *response.RegisteredModelDatabricks, + ModelId: response.RegisteredModelDatabricks.Id, + }, nil } -func (r *ResourceMlflowModel) DoCreate(ctx context.Context, config *ml.CreateModelRequest) (string, *ml.ModelDatabricks, error) { +func (r *ResourceMlflowModel) DoCreate(ctx context.Context, config *ml.CreateModelRequest) (string, *MlflowModelRemote, error) { response, err := r.client.ModelRegistry.CreateModel(ctx, *config) if err != nil { return "", nil, err } - // Create API call returns [ml.Model] while DoRead returns [ml.ModelDatabricks]. - // Thus we need to convert the response to the expected type. - modelDatabricks := &ml.ModelDatabricks{ - Name: response.RegisteredModel.Name, - Description: response.RegisteredModel.Description, - Tags: response.RegisteredModel.Tags, - ForceSendFields: utils.FilterFields[ml.ModelDatabricks](response.RegisteredModel.ForceSendFields, "CreationTimestamp", "Id", "LastUpdatedTimestamp", "LatestVersions", "PermissionLevel", "UserId"), - - // Coping the fields only to satisfy the linter. These fields are not - // part of the configuration tree so they don't need to be copied. - // The linter works as a safeguard to ensure we add new fields to the bundle config tree - // to the mapping logic here as well. - CreationTimestamp: 0, - Id: "", - LastUpdatedTimestamp: 0, - LatestVersions: nil, - PermissionLevel: "", - UserId: "", - } - return response.RegisteredModel.Name, modelDatabricks, nil + // Return nil for refresh output; the engine will call DoRead to populate the full state + // including the numeric model ID needed for permissions. + return response.RegisteredModel.Name, nil, nil } -func (r *ResourceMlflowModel) DoUpdate(ctx context.Context, id string, config *ml.CreateModelRequest, _ *PlanEntry) (*ml.ModelDatabricks, error) { +func (r *ResourceMlflowModel) DoUpdate(ctx context.Context, id string, config *ml.CreateModelRequest, entry *PlanEntry) (*MlflowModelRemote, error) { updateRequest := ml.UpdateModelRequest{ Name: id, Description: config.Description, @@ -79,26 +74,27 @@ func (r *ResourceMlflowModel) DoUpdate(ctx context.Context, id string, config *m return nil, err } - // Update API call returns [ml.Model] while DoRead returns [ml.ModelDatabricks]. - // Thus we need to convert the response to the expected type. - modelDatabricks := &ml.ModelDatabricks{ - Name: response.RegisteredModel.Name, - Description: response.RegisteredModel.Description, - Tags: response.RegisteredModel.Tags, - ForceSendFields: utils.FilterFields[ml.ModelDatabricks](response.RegisteredModel.ForceSendFields, "CreationTimestamp", "Id", "LastUpdatedTimestamp", "LatestVersions", "PermissionLevel", "UserId"), - - // Coping the fields only to satisfy the linter. These fields are not - // part of the configuration tree so they don't need to be copied. - // The linter works as a safeguard to ensure we add new fields to the bundle config tree - // to the mapping logic here as well. - CreationTimestamp: 0, - Id: "", - LastUpdatedTimestamp: 0, - LatestVersions: nil, - PermissionLevel: "", - UserId: "", + // Carry forward model_id from existing state since UpdateModelResponse doesn't include it. + var modelId string + if old, ok := entry.RemoteState.(*MlflowModelRemote); ok { + modelId = old.ModelId } - return modelDatabricks, nil + + return &MlflowModelRemote{ + ModelDatabricks: ml.ModelDatabricks{ + CreationTimestamp: 0, + Description: response.RegisteredModel.Description, + Id: "", + LastUpdatedTimestamp: 0, + LatestVersions: nil, + Name: response.RegisteredModel.Name, + PermissionLevel: "", + Tags: response.RegisteredModel.Tags, + UserId: "", + ForceSendFields: utils.FilterFields[ml.ModelDatabricks](response.RegisteredModel.ForceSendFields, "CreationTimestamp", "Id", "LastUpdatedTimestamp", "LatestVersions", "PermissionLevel", "UserId"), + }, + ModelId: modelId, + }, nil } func (r *ResourceMlflowModel) DoDelete(ctx context.Context, id string) error { diff --git a/bundle/direct/dresources/model_serving_endpoint.go b/bundle/direct/dresources/model_serving_endpoint.go index 3822897ff33..06a8dbda40f 100644 --- a/bundle/direct/dresources/model_serving_endpoint.go +++ b/bundle/direct/dresources/model_serving_endpoint.go @@ -86,7 +86,7 @@ func configOutputToInput(output *serving.EndpointCoreConfigOutput) *serving.Endp } } -func (*ResourceModelServingEndpoint) RemapState(state *RefreshOutput) *serving.CreateServingEndpoint { +func (*ResourceModelServingEndpoint) RemapState(state *ModelServingEndpointRemote) *serving.CreateServingEndpoint { details := state.EndpointDetails // Map the remote state (ServingEndpointDetailed) to the local state (CreateServingEndpoint) // for proper comparison during diff calculation @@ -107,23 +107,23 @@ func (*ResourceModelServingEndpoint) RemapState(state *RefreshOutput) *serving.C } } -type RefreshOutput struct { +type ModelServingEndpointRemote struct { EndpointDetails *serving.ServingEndpointDetailed `json:"endpoint_details"` EndpointId string `json:"endpoint_id"` } -func (r *ResourceModelServingEndpoint) DoRead(ctx context.Context, id string) (*RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) DoRead(ctx context.Context, id string) (*ModelServingEndpointRemote, error) { endpoint, err := r.client.ServingEndpoints.GetByName(ctx, id) if err != nil { return nil, err } - return &RefreshOutput{ + return &ModelServingEndpointRemote{ EndpointDetails: endpoint, EndpointId: endpoint.Id, }, nil } -func (r *ResourceModelServingEndpoint) DoCreate(ctx context.Context, config *serving.CreateServingEndpoint) (string, *RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) DoCreate(ctx context.Context, config *serving.CreateServingEndpoint) (string, *ModelServingEndpointRemote, error) { waiter, err := r.client.ServingEndpoints.Create(ctx, *config) if err != nil { return "", nil, err @@ -133,23 +133,23 @@ func (r *ResourceModelServingEndpoint) DoCreate(ctx context.Context, config *ser } // waitForEndpointReady waits for the serving endpoint to be ready (not updating) -func (r *ResourceModelServingEndpoint) waitForEndpointReady(ctx context.Context, name string) (*RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) waitForEndpointReady(ctx context.Context, name string) (*ModelServingEndpointRemote, error) { details, err := r.client.ServingEndpoints.WaitGetServingEndpointNotUpdating(ctx, name, 35*time.Minute, nil) if err != nil { return nil, err } - return &RefreshOutput{ + return &ModelServingEndpointRemote{ EndpointDetails: details, EndpointId: details.Id, }, nil } -func (r *ResourceModelServingEndpoint) WaitAfterCreate(ctx context.Context, config *serving.CreateServingEndpoint) (*RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) WaitAfterCreate(ctx context.Context, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { return r.waitForEndpointReady(ctx, config.Name) } -func (r *ResourceModelServingEndpoint) WaitAfterUpdate(ctx context.Context, config *serving.CreateServingEndpoint) (*RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) WaitAfterUpdate(ctx context.Context, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { return r.waitForEndpointReady(ctx, config.Name) } @@ -285,7 +285,7 @@ func (r *ResourceModelServingEndpoint) updateTags(ctx context.Context, id string return nil } -func (r *ResourceModelServingEndpoint) DoUpdate(ctx context.Context, id string, config *serving.CreateServingEndpoint, entry *PlanEntry) (*RefreshOutput, error) { +func (r *ResourceModelServingEndpoint) DoUpdate(ctx context.Context, id string, config *serving.CreateServingEndpoint, entry *PlanEntry) (*ModelServingEndpointRemote, error) { var err error // Terraform makes these API calls sequentially. We do the same here. diff --git a/bundle/direct/dresources/permissions.go b/bundle/direct/dresources/permissions.go index ba8e3ccfb2f..eac5e2dcdbc 100644 --- a/bundle/direct/dresources/permissions.go +++ b/bundle/direct/dresources/permissions.go @@ -26,6 +26,7 @@ var permissionResourceToObjectType = map[string]string{ "model_serving_endpoints": "/serving-endpoints/", "pipelines": "/pipelines/", "sql_warehouses": "/sql/warehouses/", + "vector_search_endpoints": "/vector-search-endpoints/", } type ResourcePermissions struct { @@ -53,6 +54,23 @@ type PermissionsState struct { EmbeddedSlice []StatePermission `json:"__embed__,omitempty"` } +// permissionIDFields maps resource types that use a non-standard ID field for +// the permissions API (most resources use "id"). +var permissionIDFields = map[string]string{ + "model_serving_endpoints": "endpoint_id", // internal numeric ID, not the name used in CRUD APIs + "models": "model_id", // numeric model ID, not the model name used as CRUD state ID + "postgres_projects": "project_id", // bare project_id, not the hierarchical "projects/{id}" state ID + "vector_search_endpoints": "endpoint_uuid", // endpoint UUID, not the endpoint name used as deployment ID +} + +// objectIDRef returns the reference expression for the permissions object ID. +func objectIDRef(prefix, baseNode, resourceType string) string { + if field, ok := permissionIDFields[resourceType]; ok { + return prefix + "${" + baseNode + "." + field + "}" + } + return prefix + "${" + baseNode + ".id}" +} + func PreparePermissionsInputConfig(inputConfig any, node string) (*structvar.StructVar, error) { baseNode, ok := strings.CutSuffix(node, ".permissions") if !ok { @@ -75,28 +93,13 @@ func PreparePermissionsInputConfig(inputConfig any, node string) (*structvar.Str return nil, err } - objectIdRef := prefix + "${" + baseNode + ".id}" - // For permissions, model serving endpoint uses its internal ID, which is different - // from its CRUD APIs which use the name. - // We have a wrapper struct [RefreshOutput] from which we read the internal ID - // in order to set the appropriate permissions. - if strings.HasPrefix(baseNode, "resources.model_serving_endpoints.") { - objectIdRef = prefix + "${" + baseNode + ".endpoint_id}" - } - - // Postgres projects store their hierarchical name ("projects/{project_id}") as the state ID, - // but the permissions API expects just the project_id. - if strings.HasPrefix(baseNode, "resources.postgres_projects.") { - objectIdRef = prefix + "${" + baseNode + ".project_id}" - } - return &structvar.StructVar{ Value: &PermissionsState{ ObjectID: "", // Always a reference, defined in Refs below EmbeddedSlice: permissions, }, Refs: map[string]string{ - "object_id": objectIdRef, + "object_id": objectIDRef(prefix, baseNode, resourceType), }, }, nil } diff --git a/bundle/direct/dresources/pipeline_test.go b/bundle/direct/dresources/pipeline_test.go index 65ab31b2131..da769aa947b 100644 --- a/bundle/direct/dresources/pipeline_test.go +++ b/bundle/direct/dresources/pipeline_test.go @@ -10,7 +10,7 @@ import ( // TestPipelineRemote verifies that all fields from pipelines.GetPipelineResponse // (except Spec and internal fields) are present in PipelineRemote. func TestPipelineRemote(t *testing.T) { - assertFieldsCovered(t, reflect.TypeOf(pipelines.GetPipelineResponse{}), reflect.TypeOf(PipelineRemote{}), map[string]bool{ + assertFieldsCovered(t, reflect.TypeFor[pipelines.GetPipelineResponse](), reflect.TypeFor[PipelineRemote](), map[string]bool{ "Spec": true, // Embedded as pipelines.CreatePipeline (via makePipelineRemote) "ForceSendFields": true, // Internal marshaling field "Name": true, // Available through embedded CreatePipeline diff --git a/bundle/direct/dresources/postgres_endpoint.go b/bundle/direct/dresources/postgres_endpoint.go index aa2c06c82a2..63f0eb0f2db 100644 --- a/bundle/direct/dresources/postgres_endpoint.go +++ b/bundle/direct/dresources/postgres_endpoint.go @@ -3,6 +3,7 @@ package dresources import ( "context" "errors" + "net/http" "strings" "time" @@ -180,7 +181,7 @@ func (r *ResourcePostgresEndpoint) DoDelete(ctx context.Context, id string) erro if err != nil { // Check if this is a reconciliation in progress error var apiErr *apierr.APIError - if errors.As(err, &apiErr) && apiErr.StatusCode == 409 && + if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict && strings.Contains(apiErr.Message, "reconciliation") { // Check if we've exceeded the timeout if time.Now().After(deadline) { diff --git a/bundle/direct/dresources/postgres_project.go b/bundle/direct/dresources/postgres_project.go index 222d201d8f1..e1e802b2678 100644 --- a/bundle/direct/dresources/postgres_project.go +++ b/bundle/direct/dresources/postgres_project.go @@ -40,6 +40,7 @@ func (*ResourcePostgresProject) RemapState(remote *postgres.Project) *PostgresPr ProjectSpec: postgres.ProjectSpec{ BudgetPolicyId: "", CustomTags: nil, + DefaultBranch: "", DefaultEndpointSettings: nil, DisplayName: "", EnablePgNativeLogin: false, diff --git a/bundle/direct/dresources/resources.generated.yml b/bundle/direct/dresources/resources.generated.yml index 5a02c184f4e..6c3778d3494 100644 --- a/bundle/direct/dresources/resources.generated.yml +++ b/bundle/direct/dresources/resources.generated.yml @@ -165,11 +165,7 @@ resources: ignore_remote_changes: - field: effective_enable_file_events reason: spec:output_only - - field: file_event_queue.managed_aqs.managed_resource_id - reason: spec:output_only - - field: file_event_queue.managed_pubsub.managed_resource_id - reason: spec:output_only - - field: file_event_queue.managed_sqs.managed_resource_id + - field: effective_file_event_queue reason: spec:output_only # jobs: no api field behaviors @@ -245,6 +241,8 @@ resources: reason: spec:input_only - field: custom_tags reason: spec:input_only + - field: default_branch + reason: spec:input_only - field: default_endpoint_settings reason: spec:input_only - field: display_name @@ -289,4 +287,6 @@ resources: - field: unity_catalog_provisioning_state reason: spec:output_only + # vector_search_endpoints: no api field behaviors + # volumes: no api field behaviors diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml index e71d8745c30..569fca9ee82 100644 --- a/bundle/direct/dresources/resources.yml +++ b/bundle/direct/dresources/resources.yml @@ -376,6 +376,8 @@ resources: - field: name reason: immutable backend_defaults: + # Backend sets it "MEDIUM" when not specified in the config + - field: compute_size # lifecycle.started is derived from remote compute status in RemapState, so the # remote side always has a value. When the user omits lifecycle from config, # both old and new are nil and backend_defaults correctly skips the remote value. @@ -383,6 +385,9 @@ resources: # drift detection applies (e.g. detecting out-of-band stop). - field: lifecycle - field: lifecycle.started + ignore_remote_changes: + - field: space # This field is not yet supported by Update APIs but exposed in the API spec. TODO: fix when update APIs supports it. + reason: managed secret_scopes: backend_defaults: @@ -472,6 +477,10 @@ resources: - field: min_num_clusters reason: managed + # creator_name is readonly, can't be updated via API + - field: creator_name + reason: output_only + backend_defaults: # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/sql/resource_sql_endpoint.go#L69 # m["enable_serverless_compute"].Computed = true @@ -498,3 +507,8 @@ resources: reason: immutable - field: endpoint_id reason: immutable + + vector_search_endpoints: + recreate_on_changes: + - field: endpoint_type + reason: immutable diff --git a/bundle/direct/dresources/type_test.go b/bundle/direct/dresources/type_test.go index 3321725049f..8e711c9bf03 100644 --- a/bundle/direct/dresources/type_test.go +++ b/bundle/direct/dresources/type_test.go @@ -67,6 +67,7 @@ var knownMissingInRemoteType = map[string][]string{ "postgres_projects": { "budget_policy_id", "custom_tags", + "default_branch", "default_endpoint_settings", "display_name", "enable_pg_native_login", @@ -74,6 +75,10 @@ var knownMissingInRemoteType = map[string][]string{ "pg_version", "project_id", }, + "vector_search_endpoints": { + "min_qps", + "usage_policy_id", + }, } // commonMissingInStateType lists fields that are commonly missing across all resource types. diff --git a/bundle/direct/dresources/util.go b/bundle/direct/dresources/util.go index c13f720fcd5..3bd0ab4ec73 100644 --- a/bundle/direct/dresources/util.go +++ b/bundle/direct/dresources/util.go @@ -1,6 +1,7 @@ package dresources import ( + "errors" "fmt" "regexp" @@ -40,14 +41,11 @@ func ParsePostgresName(name string) (PostgresNameComponents, error) { // This is copied from the retries package of the databricks-sdk-go. It should be made public, // but for now, I'm copying it here. func shouldRetry(err error) bool { - if err == nil { - return false + var e *retries.Err + if errors.As(err, &e) { + return !e.Halt } - e := err.(*retries.Err) - if e == nil { - return false - } - return !e.Halt + return false } // collectUpdatePathsWithPrefix extracts field paths from Changes that have action=Update, diff --git a/bundle/direct/dresources/vector_search_endpoint.go b/bundle/direct/dresources/vector_search_endpoint.go new file mode 100644 index 00000000000..24bbd1a6e74 --- /dev/null +++ b/bundle/direct/dresources/vector_search_endpoint.go @@ -0,0 +1,115 @@ +package dresources + +import ( + "context" + "time" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/libs/structs/structpath" + "github.com/databricks/cli/libs/utils" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" +) + +var ( + pathBudgetPolicyId = structpath.MustParsePath("budget_policy_id") + pathMinQps = structpath.MustParsePath("min_qps") +) + +// VectorSearchEndpointRemote is remote state for a vector search endpoint. It embeds API response +// fields for drift comparison and adds endpoint_uuid for permissions; deployment state id remains the endpoint name. +type VectorSearchEndpointRemote struct { + *vectorsearch.EndpointInfo + EndpointUuid string `json:"endpoint_uuid"` +} + +func newVectorSearchEndpointRemote(info *vectorsearch.EndpointInfo) *VectorSearchEndpointRemote { + if info == nil { + return nil + } + return &VectorSearchEndpointRemote{ + EndpointInfo: info, + EndpointUuid: info.Id, + } +} + +type ResourceVectorSearchEndpoint struct { + client *databricks.WorkspaceClient +} + +func (*ResourceVectorSearchEndpoint) New(client *databricks.WorkspaceClient) *ResourceVectorSearchEndpoint { + return &ResourceVectorSearchEndpoint{client: client} +} + +func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *vectorsearch.CreateEndpoint { + return &input.CreateEndpoint +} + +func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *vectorsearch.CreateEndpoint { + var minQps int64 + if remote.ScalingInfo != nil { + minQps = remote.ScalingInfo.RequestedMinQps + } + return &vectorsearch.CreateEndpoint{ + Name: remote.Name, + EndpointType: remote.EndpointType, + BudgetPolicyId: remote.BudgetPolicyId, + UsagePolicyId: "", // Missing in remote + MinQps: minQps, + ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"), + } +} + +func (r *ResourceVectorSearchEndpoint) DoRead(ctx context.Context, id string) (*VectorSearchEndpointRemote, error) { + info, err := r.client.VectorSearchEndpoints.GetEndpointByEndpointName(ctx, id) + if err != nil { + return nil, err + } + return newVectorSearchEndpointRemote(info), nil +} + +func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (string, *VectorSearchEndpointRemote, error) { + waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, *config) + if err != nil { + return "", nil, err + } + id := config.Name + return id, newVectorSearchEndpointRemote(waiter.Response), nil +} + +func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *vectorsearch.CreateEndpoint) (*VectorSearchEndpointRemote, error) { + info, err := r.client.VectorSearchEndpoints.WaitGetEndpointVectorSearchEndpointOnline(ctx, config.Name, 60*time.Minute, nil) + if err != nil { + return nil, err + } + return newVectorSearchEndpointRemote(info), nil +} + +func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *vectorsearch.CreateEndpoint, entry *PlanEntry) (*VectorSearchEndpointRemote, error) { + if entry.Changes.HasChange(pathBudgetPolicyId) { + _, err := r.client.VectorSearchEndpoints.UpdateEndpointBudgetPolicy(ctx, vectorsearch.PatchEndpointBudgetPolicyRequest{ + EndpointName: id, + BudgetPolicyId: config.BudgetPolicyId, + }) + if err != nil { + return nil, err + } + } + + if entry.Changes.HasChange(pathMinQps) { + _, err := r.client.VectorSearchEndpoints.PatchEndpoint(ctx, vectorsearch.PatchEndpointRequest{ + EndpointName: id, + MinQps: config.MinQps, + ForceSendFields: nil, + }) + if err != nil { + return nil, err + } + } + + return nil, nil +} + +func (r *ResourceVectorSearchEndpoint) DoDelete(ctx context.Context, id string) error { + return r.client.VectorSearchEndpoints.DeleteEndpointByEndpointName(ctx, id) +} diff --git a/bundle/direct/dresources/volume.go b/bundle/direct/dresources/volume.go index 62a08987eea..35196bdb388 100644 --- a/bundle/direct/dresources/volume.go +++ b/bundle/direct/dresources/volume.go @@ -6,7 +6,9 @@ import ( "strings" "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/utils" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/catalog" @@ -112,6 +114,35 @@ func (r *ResourceVolume) DoDelete(ctx context.Context, id string) error { return r.client.Volumes.DeleteByName(ctx, id) } +// OverrideChangeDesc suppresses drift for storage_location when the only difference +// is a trailing slash. The UC API strips trailing slashes on create, so remote returns +// "s3://bucket/path" while the config may have "s3://bucket/path/". +// +// This matches the Terraform provider's suppressLocationDiff behavior. +// https://github.com/databricks/terraform-provider-databricks/blob/v1.65.1/catalog/resource_volume.go#L25 +func (*ResourceVolume) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, _ *catalog.VolumeInfo) error { + if change.Action == deployplan.Skip { + return nil + } + + if path.String() != "storage_location" { + return nil + } + + newStr, newOk := change.New.(string) + remoteStr, remoteOk := change.Remote.(string) + if !newOk || !remoteOk { + return nil + } + + if newStr != remoteStr && strings.TrimRight(newStr, "/") == strings.TrimRight(remoteStr, "/") { + change.Action = deployplan.Skip + change.Reason = deployplan.ReasonURLNormalization + } + + return nil +} + func getNameFromID(id string) (string, error) { items := strings.Split(id, ".") if len(items) == 0 { diff --git a/bundle/direct/dstate/state.go b/bundle/direct/dstate/state.go index 468fb7bfc3b..3f6bcce2fc5 100644 --- a/bundle/direct/dstate/state.go +++ b/bundle/direct/dstate/state.go @@ -3,7 +3,9 @@ package dstate import ( "context" "encoding/json" + "errors" "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -118,7 +120,7 @@ func (db *DeploymentState) Open(path string) error { data, err := os.ReadFile(path) if err != nil { - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { // Create new database with serial=0, will be incremented to 1 in Finalize() db.Data = NewDatabase("", 0) db.Path = path diff --git a/bundle/direct/graph.go b/bundle/direct/graph.go index 433eb8dc57f..386c590f53c 100644 --- a/bundle/direct/graph.go +++ b/bundle/direct/graph.go @@ -2,17 +2,18 @@ package direct import ( "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/libs/dagrun" - "github.com/databricks/cli/libs/utils" ) func makeGraph(plan *deployplan.Plan) (*dagrun.Graph, error) { g := dagrun.NewGraph() // Add all nodes first - for _, resourceKey := range utils.SortedKeys(plan.Plan) { + for _, resourceKey := range slices.Sorted(maps.Keys(plan.Plan)) { g.AddNode(resourceKey) } diff --git a/bundle/docsgen/README.md b/bundle/docsgen/README.md index 220a14c1c9c..0468945dc7d 100644 --- a/bundle/docsgen/README.md +++ b/bundle/docsgen/README.md @@ -1,14 +1,14 @@ ## docs-autogen 1. Install [Golang](https://go.dev/doc/install) -2. Run `make docs` from the repo +2. Run `./task generate-docs` from the repo 3. See generated documents in `./bundle/docsgen/output` directory -4. To change descriptions update content in `./bundle/internal/schema/annotations.yml` or `./bundle/internal/schema/annotations_openapi_overrides.yml` and re-run `make docs` +4. To change descriptions update content in `./bundle/internal/schema/annotations.yml` or `./bundle/internal/schema/annotations_openapi_overrides.yml` and re-run `./task generate-docs` For simpler usage run it together with copy command to move resulting files to local `docs` repo. Note that it will overwrite any local changes in affected files. Example: ``` -make docs && cp bundle/docgen/output/*.md ../docs/source/dev-tools/bundles +task generate-docs && cp bundle/docgen/output/*.md ../docs/source/dev-tools/bundles ``` To change intro sections for files update them in `templates/` directory @@ -76,4 +76,4 @@ github.com/databricks/cli/bundle/config.Bundle: ### TODO -Add file watcher to track changes in the annotation files and re-run `make docs` script automtically +Add file watcher to track changes in the annotation files and re-run `./task generate-docs` script automtically diff --git a/bundle/docsgen/main.go b/bundle/docsgen/main.go index 31dae7533f7..c06811ebbdd 100644 --- a/bundle/docsgen/main.go +++ b/bundle/docsgen/main.go @@ -1,7 +1,9 @@ package main import ( + "errors" "fmt" + "io/fs" "log" "os" "path" @@ -30,7 +32,7 @@ func main() { outputDir := path.Join(docsDir, "output") templatesDir := path.Join(docsDir, "templates") - if _, err := os.Stat(outputDir); os.IsNotExist(err) { + if _, err := os.Stat(outputDir); errors.Is(err, fs.ErrNotExist) { if err := os.MkdirAll(outputDir, 0o755); err != nil { log.Fatal(err) } @@ -43,7 +45,7 @@ func main() { err = generateDocs( []string{path.Join(annotationDir, "annotations.yml")}, path.Join(outputDir, rootFileName), - reflect.TypeOf(config.Root{}), + reflect.TypeFor[config.Root](), fillTemplateVariables(string(rootHeader)), ) if err != nil { @@ -56,7 +58,7 @@ func main() { err = generateDocs( []string{path.Join(annotationDir, "annotations_openapi.yml"), path.Join(annotationDir, "annotations_openapi_overrides.yml"), path.Join(annotationDir, "annotations.yml")}, path.Join(outputDir, resourcesFileName), - reflect.TypeOf(config.Resources{}), + reflect.TypeFor[config.Resources](), fillTemplateVariables(string(resourcesHeader)), ) if err != nil { diff --git a/bundle/docsgen/nodes.go b/bundle/docsgen/nodes.go index 41d37f338c1..a6f629ca429 100644 --- a/bundle/docsgen/nodes.go +++ b/bundle/docsgen/nodes.go @@ -1,7 +1,8 @@ package main import ( - "sort" + "cmp" + "slices" "strings" "github.com/databricks/cli/libs/jsonschema" @@ -130,8 +131,8 @@ func buildNodes(s jsonschema.Schema, refs map[string]*jsonschema.Schema, ownFiel } } - sort.Slice(nodes, func(i, j int) bool { - return nodes[i].Title < nodes[j].Title + slices.SortFunc(nodes, func(a, b rootNode) int { + return cmp.Compare(a.Title, b.Title) }) return nodes } @@ -154,8 +155,8 @@ func getMapKeyPrefix(s string) string { } func removePluralForm(s string) string { - if strings.HasSuffix(s, "s") { - return strings.TrimSuffix(s, "s") + if before, ok := strings.CutSuffix(s, "s"); ok { + return before } return s } @@ -193,8 +194,8 @@ func getAttributes(props, refs map[string]*jsonschema.Schema, ownFields map[stri Link: reference, }) } - sort.Slice(attributes, func(i, j int) bool { - return attributes[i].Title < attributes[j].Title + slices.SortFunc(attributes, func(a, b attributeNode) int { + return cmp.Compare(a.Title, b.Title) }) return attributes } diff --git a/bundle/docsgen/output/reference.md b/bundle/docsgen/output/reference.md index ca4a347e1a5..ea8f922575e 100644 --- a/bundle/docsgen/output/reference.md +++ b/bundle/docsgen/output/reference.md @@ -1,7 +1,7 @@ --- description: 'Configuration reference for databricks.yml' last_update: - date: 2025-09-13 + date: 2026-04-23 --- @@ -122,6 +122,10 @@ The bundle attributes when deploying to this target, - Map - The definition of the bundle deployment. For supported attributes see [\_](/dev-tools/bundles/deployment-modes.md). See [\_](#bundledeployment). +- - `engine` + - String + - The deployment engine to use. Valid values are `terraform` and `direct`. Takes priority over `DATABRICKS_BUNDLE_ENGINE` environment variable. Default is "terraform". + - - `git` - Map - The Git version control details that are associated with your bundle. For supported attributes see [\_](/dev-tools/bundles/settings.md#git). See [\_](#bundlegit). @@ -385,6 +389,35 @@ Defines bundle deployment presets. See [\_](/dev-tools/bundles/deployment-modes. ::: +## python + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `mutators` + - Sequence + - Mutators contains a list of fully qualified function paths to mutator functions. Example: ["my_project.mutators:add_default_cluster"] + +- - `resources` + - Sequence + - Resources contains a list of fully qualified function paths to load resources defined in Python code. Example: ["my_project.resources:load_resources"] + +- - `venv_path` + - String + - VEnvPath is path to the virtual environment. If enabled, Python code will execute within this environment. If disabled, it defaults to using the Python interpreter available in the current shell. + +::: + + ## resources **`Type: Map`** @@ -406,9 +439,17 @@ resources: - Type - Description +- - `alerts` + - Map + - See [\_](#resourcesalerts). + - - `apps` - Map - - The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). + - The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). See [\_](#resourcesapps). + +- - `catalogs` + - Map + - See [\_](#resourcescatalogs). - - `clusters` - Map @@ -416,7 +457,7 @@ resources: - - `dashboards` - Map - - The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). + - The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). See [\_](#resourcesdashboards). - - `database_catalogs` - Map @@ -424,12 +465,16 @@ resources: - - `database_instances` - Map - - + - See [\_](#resourcesdatabase_instances). - - `experiments` - Map - The experiment definitions for the bundle, where each key is the name of the experiment. See [\_](/dev-tools/bundles/resources.md#experiments). +- - `external_locations` + - Map + - See [\_](#resourcesexternal_locations). + - - `jobs` - Map - The job definitions for the bundle, where each key is the name of the job. See [\_](/dev-tools/bundles/resources.md#jobs). @@ -446,6 +491,18 @@ resources: - Map - The pipeline definitions for the bundle, where each key is the name of the pipeline. See [\_](/dev-tools/bundles/resources.md#pipelines). +- - `postgres_branches` + - Map + - See [\_](#resourcespostgres_branches). + +- - `postgres_endpoints` + - Map + - See [\_](#resourcespostgres_endpoints). + +- - `postgres_projects` + - Map + - See [\_](#resourcespostgres_projects). + - - `quality_monitors` - Map - The quality monitor definitions for the bundle, where each key is the name of the quality monitor. See [\_](/dev-tools/bundles/resources.md#quality_monitors). @@ -470,6 +527,10 @@ resources: - Map - See [\_](#resourcessynced_database_tables). +- - `vector_search_endpoints` + - Map + - See [\_](#resourcesvector_search_endpoints). + - - `volumes` - Map - The volume definitions for the bundle, where each key is the name of the volume. See [\_](/dev-tools/bundles/resources.md#volumes). @@ -477,16 +538,16 @@ resources: ::: -### resources.secret_scopes +### resources.alerts **`Type: Map`** -The secret scope definitions for the bundle, where each key is the name of the secret scope. See [\_](/dev-tools/bundles/resources.md#secret_scopes). + ```yaml -secret_scopes: - : - : +alerts: + : + : ``` @@ -496,34 +557,90 @@ secret_scopes: - Type - Description -- - `backend_type` +- - `create_time` - String - - The backend type the scope will be created with. If not specified, will default to `DATABRICKS` + - -- - `keyvault_metadata` +- - `custom_description` + - String + - + +- - `custom_summary` + - String + - + +- - `display_name` + - String + - + +- - `effective_run_as` - Map - - The metadata for the secret scope if the `backend_type` is `AZURE_KEYVAULT`. See [\_](#resourcessecret_scopesnamekeyvault_metadata). + - See [\_](#resourcesalertsnameeffective_run_as). + +- - `evaluation` + - Map + - See [\_](#resourcesalertsnameevaluation). + +- - `file_path` + - String + - + +- - `id` + - String + - - - `lifecycle` - Map - - 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. See [\_](#resourcessecret_scopesnamelifecycle). + - See [\_](#resourcesalertsnamelifecycle). -- - `name` +- - `lifecycle_state` - String - - Scope name requested by the user. Scope names are unique. + - + +- - `owner_user_name` + - String + - + +- - `parent_path` + - String + - - - `permissions` - Sequence - - The permissions to apply to the secret scope. Permissions are managed via secret scope ACLs. See [\_](#resourcessecret_scopesnamepermissions). + - See [\_](#resourcesalertsnamepermissions). + +- - `query_text` + - String + - + +- - `run_as` + - Map + - See [\_](#resourcesalertsnamerun_as). + +- - `run_as_user_name` + - String + - + +- - `schedule` + - Map + - See [\_](#resourcesalertsnameschedule). + +- - `update_time` + - String + - + +- - `warehouse_id` + - String + - ::: -### resources.secret_scopes._name_.lifecycle +### resources.alerts._name_.lifecycle **`Type: Map`** -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. + @@ -540,11 +657,11 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co ::: -### resources.secret_scopes._name_.permissions +### resources.alerts._name_.permissions **`Type: Sequence`** -The permissions to apply to the secret scope. Permissions are managed via secret scope ACLs. + @@ -556,7 +673,7 @@ The permissions to apply to the secret scope. Permissions are managed via secret - - `group_name` - String - - The name of the group that has the permission set in level. This field translates to a `principal` field in secret scope ACL. + - The name of the group that has the permission set in level. - - `level` - String @@ -564,25 +681,25 @@ The permissions to apply to the secret scope. Permissions are managed via secret - - `service_principal_name` - String - - The application ID of an active service principal. This field translates to a `principal` field in secret scope ACL. + - The name of the service principal that has the permission set in level. - - `user_name` - String - - The name of the user that has the permission set in level. This field translates to a `principal` field in secret scope ACL. + - The name of the user that has the permission set in level. ::: -### resources.synced_database_tables +### resources.apps **`Type: Map`** - +The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). ```yaml -synced_database_tables: - : - : +apps: + : + : ``` @@ -592,71 +709,150 @@ synced_database_tables: - Type - Description -- - `data_synchronization_status` +- - `active_deployment` - Map - - See [\_](#resourcessynced_database_tablesnamedata_synchronization_status). + - See [\_](#resourcesappsnameactive_deployment). -- - `database_instance_name` +- - `app_status` + - Map + - See [\_](#resourcesappsnameapp_status). + +- - `budget_policy_id` - String - -- - `effective_database_instance_name` +- - `compute_size` - String - -- - `effective_logical_database_name` +- - `compute_status` + - Map + - See [\_](#resourcesappsnamecompute_status). + +- - `config` + - Map + - See [\_](#resourcesappsnameconfig). + +- - `create_time` - String - -- - `lifecycle` +- - `creator` + - String + - + +- - `default_source_code_path` + - String + - + +- - `description` + - String + - + +- - `effective_budget_policy_id` + - String + - + +- - `effective_usage_policy_id` + - String + - + +- - `effective_user_api_scopes` + - Sequence + - + +- - `git_repository` - Map - - 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. See [\_](#resourcessynced_database_tablesnamelifecycle). + - See [\_](#resourcesappsnamegit_repository). -- - `logical_database_name` +- - `git_source` + - Map + - 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. See [\_](#resourcesappsnamegit_source). + +- - `id` - String - +- - `lifecycle` + - Map + - See [\_](#resourcesappsnamelifecycle). + - - `name` - String - -- - `spec` +- - `oauth2_app_client_id` + - String + - + +- - `oauth2_app_integration_id` + - String + - + +- - `pending_deployment` - Map - - See [\_](#resourcessynced_database_tablesnamespec). + - See [\_](#resourcesappsnamepending_deployment). -- - `unity_catalog_provisioning_state` +- - `permissions` + - Sequence + - See [\_](#resourcesappsnamepermissions). + +- - `resources` + - Sequence + - See [\_](#resourcesappsnameresources). + +- - `service_principal_client_id` - String - -::: +- - `service_principal_id` + - Integer + - +- - `service_principal_name` + - String + - -### resources.synced_database_tables._name_.lifecycle +- - `source_code_path` + - String + - -**`Type: Map`** +- - `space` + - String + - -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. +- - `telemetry_export_destinations` + - Sequence + - See [\_](#resourcesappsnametelemetry_export_destinations). +- - `update_time` + - String + - +- - `updater` + - String + - -:::list-table +- - `url` + - String + - -- - Key - - Type - - Description +- - `usage_policy_id` + - String + - -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `user_api_scopes` + - Sequence + - ::: -## run_as +### resources.apps._name_.config **`Type: Map`** -The identity to use when running Declarative Automation Bundles workflows. See [\_](/dev-tools/bundles/run-as.md). + @@ -666,28 +862,23 @@ The identity to use when running Declarative Automation Bundles workflows. See [ - Type - Description -- - `service_principal_name` - - String - - The application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. +- - `command` + - Sequence + - -- - `user_name` - - String - - The email of an active workspace user. Non-admin users can only set this field to their own email. +- - `env` + - Sequence + - See [\_](#resourcesappsnameconfigenv). ::: -## scripts +### resources.apps._name_.config.env -**`Type: Map`** +**`Type: Sequence`** -```yaml -scripts: - : - : -``` :::list-table @@ -696,18 +887,26 @@ scripts: - Type - Description -- - `content` +- - `name` + - String + - + +- - `value` + - String + - + +- - `value_from` - String - ::: -## sync +### resources.apps._name_.lifecycle **`Type: Map`** -The files and file paths to include or exclude in the bundle. See [\_](/dev-tools/bundles/settings.md#sync). + @@ -717,32 +916,1228 @@ The files and file paths to include or exclude in the bundle. See [\_](/dev-tool - Type - Description -- - `exclude` - - Sequence - - A list of files or folders to exclude from the bundle. - -- - `include` - - Sequence - - A list of files or folders to include in the bundle. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. -- - `paths` - - Sequence - - The local folder paths, which can be outside the bundle root, to synchronize to the workspace when the bundle is deployed. +- - `started` + - Boolean + - Lifecycle setting to deploy the resource in started mode. Only supported for apps, clusters, and sql_warehouses in direct deployment mode. ::: -## targets +### resources.apps._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -Defines deployment targets for the bundle. See [\_](/dev-tools/bundles/settings.md#targets) + -```yaml -targets: - : - : -``` + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - + +- - `level` + - String + - + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - + +::: + + +### resources.catalogs + +**`Type: Map`** + + + +```yaml +catalogs: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `comment` + - String + - + +- - `connection_name` + - String + - + +- - `grants` + - Sequence + - See [\_](#resourcescatalogsnamegrants). + +- - `lifecycle` + - Map + - See [\_](#resourcescatalogsnamelifecycle). + +- - `managed_encryption_settings` + - Map + - See [\_](#resourcescatalogsnamemanaged_encryption_settings). + +- - `name` + - String + - + +- - `options` + - Map + - + +- - `properties` + - Map + - + +- - `provider_name` + - String + - + +- - `share_name` + - String + - + +- - `storage_root` + - String + - + +::: + + +### resources.catalogs._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.dashboards + +**`Type: Map`** + +The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). + +```yaml +dashboards: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `create_time` + - String + - + +- - `dashboard_id` + - String + - + +- - `dataset_catalog` + - String + - Sets the default catalog for all datasets in this dashboard. When set, this overrides the catalog specified in individual dataset definitions. + +- - `dataset_schema` + - String + - Sets the default schema for all datasets in this dashboard. When set, this overrides the schema specified in individual dataset definitions. + +- - `display_name` + - String + - + +- - `embed_credentials` + - Boolean + - + +- - `etag` + - String + - + +- - `file_path` + - String + - + +- - `lifecycle` + - Map + - See [\_](#resourcesdashboardsnamelifecycle). + +- - `lifecycle_state` + - String + - + +- - `parent_path` + - String + - + +- - `path` + - String + - + +- - `permissions` + - Sequence + - See [\_](#resourcesdashboardsnamepermissions). + +- - `serialized_dashboard` + - Any + - + +- - `update_time` + - String + - + +- - `warehouse_id` + - String + - + +::: + + +### resources.dashboards._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.dashboards._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### resources.database_instances + +**`Type: Map`** + + + +```yaml +database_instances: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `capacity` + - String + - + +- - `child_instance_refs` + - Sequence + - See [\_](#resourcesdatabase_instancesnamechild_instance_refs). + +- - `creation_time` + - String + - + +- - `creator` + - String + - + +- - `custom_tags` + - Sequence + - See [\_](#resourcesdatabase_instancesnamecustom_tags). + +- - `effective_capacity` + - String + - + +- - `effective_custom_tags` + - Sequence + - See [\_](#resourcesdatabase_instancesnameeffective_custom_tags). + +- - `effective_enable_pg_native_login` + - Boolean + - + +- - `effective_enable_readable_secondaries` + - Boolean + - + +- - `effective_node_count` + - Integer + - + +- - `effective_retention_window_in_days` + - Integer + - + +- - `effective_stopped` + - Boolean + - + +- - `effective_usage_policy_id` + - String + - + +- - `enable_pg_native_login` + - Boolean + - + +- - `enable_readable_secondaries` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#resourcesdatabase_instancesnamelifecycle). + +- - `name` + - String + - + +- - `node_count` + - Integer + - + +- - `parent_instance_ref` + - Map + - See [\_](#resourcesdatabase_instancesnameparent_instance_ref). + +- - `permissions` + - Sequence + - See [\_](#resourcesdatabase_instancesnamepermissions). + +- - `pg_version` + - String + - + +- - `read_only_dns` + - String + - + +- - `read_write_dns` + - String + - + +- - `retention_window_in_days` + - Integer + - + +- - `state` + - String + - + +- - `stopped` + - Boolean + - + +- - `uid` + - String + - + +- - `usage_policy_id` + - String + - + +::: + + +### resources.database_instances._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.database_instances._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### resources.external_locations + +**`Type: Map`** + + + +```yaml +external_locations: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `comment` + - String + - + +- - `credential_name` + - String + - + +- - `effective_enable_file_events` + - Boolean + - + +- - `effective_file_event_queue` + - Map + - See [\_](#resourcesexternal_locationsnameeffective_file_event_queue). + +- - `enable_file_events` + - Boolean + - + +- - `encryption_details` + - Map + - See [\_](#resourcesexternal_locationsnameencryption_details). + +- - `fallback` + - Boolean + - + +- - `file_event_queue` + - Map + - See [\_](#resourcesexternal_locationsnamefile_event_queue). + +- - `grants` + - Sequence + - See [\_](#resourcesexternal_locationsnamegrants). + +- - `lifecycle` + - Map + - See [\_](#resourcesexternal_locationsnamelifecycle). + +- - `name` + - String + - + +- - `read_only` + - Boolean + - + +- - `skip_validation` + - Boolean + - + +- - `url` + - String + - + +::: + + +### resources.external_locations._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.postgres_branches + +**`Type: Map`** + + + +```yaml +postgres_branches: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch_id` + - String + - + +- - `expire_time` + - Map + - + +- - `is_protected` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#resourcespostgres_branchesnamelifecycle). + +- - `no_expiry` + - Boolean + - + +- - `parent` + - String + - + +- - `source_branch` + - String + - + +- - `source_branch_lsn` + - String + - + +- - `source_branch_time` + - Map + - + +- - `ttl` + - String + - + +::: + + +### resources.postgres_branches._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.postgres_endpoints + +**`Type: Map`** + + + +```yaml +postgres_endpoints: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `autoscaling_limit_max_cu` + - Any + - + +- - `autoscaling_limit_min_cu` + - Any + - + +- - `disabled` + - Boolean + - + +- - `endpoint_id` + - String + - + +- - `endpoint_type` + - String + - + +- - `group` + - Map + - See [\_](#resourcespostgres_endpointsnamegroup). + +- - `lifecycle` + - Map + - See [\_](#resourcespostgres_endpointsnamelifecycle). + +- - `no_suspension` + - Boolean + - + +- - `parent` + - String + - + +- - `settings` + - Map + - See [\_](#resourcespostgres_endpointsnamesettings). + +- - `suspend_timeout_duration` + - String + - + +::: + + +### resources.postgres_endpoints._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.postgres_projects + +**`Type: Map`** + + + +```yaml +postgres_projects: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `budget_policy_id` + - String + - + +- - `custom_tags` + - Sequence + - See [\_](#resourcespostgres_projectsnamecustom_tags). + +- - `default_branch` + - String + - + +- - `default_endpoint_settings` + - Map + - See [\_](#resourcespostgres_projectsnamedefault_endpoint_settings). + +- - `display_name` + - String + - + +- - `enable_pg_native_login` + - Boolean + - + +- - `history_retention_duration` + - String + - + +- - `lifecycle` + - Map + - See [\_](#resourcespostgres_projectsnamelifecycle). + +- - `permissions` + - Sequence + - See [\_](#resourcespostgres_projectsnamepermissions). + +- - `pg_version` + - Integer + - + +- - `project_id` + - String + - + +::: + + +### resources.postgres_projects._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.postgres_projects._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### resources.secret_scopes + +**`Type: Map`** + +The secret scope definitions for the bundle, where each key is the name of the secret scope. See [\_](/dev-tools/bundles/resources.md#secret_scopes). + +```yaml +secret_scopes: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `backend_type` + - String + - The backend type the scope will be created with. If not specified, will default to `DATABRICKS` + +- - `keyvault_metadata` + - Map + - The metadata for the secret scope if the `backend_type` is `AZURE_KEYVAULT`. See [\_](#resourcessecret_scopesnamekeyvault_metadata). + +- - `lifecycle` + - Map + - 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. See [\_](#resourcessecret_scopesnamelifecycle). + +- - `name` + - String + - Scope name requested by the user. Scope names are unique. + +- - `permissions` + - Sequence + - The permissions to apply to the secret scope. Permissions are managed via secret scope ACLs. See [\_](#resourcessecret_scopesnamepermissions). + +::: + + +### resources.secret_scopes._name_.lifecycle + +**`Type: Map`** + +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. + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.secret_scopes._name_.permissions + +**`Type: Sequence`** + +The permissions to apply to the secret scope. Permissions are managed via secret scope ACLs. + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. This field translates to a `principal` field in secret scope ACL. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The application ID of an active service principal. This field translates to a `principal` field in secret scope ACL. + +- - `user_name` + - String + - The name of the user that has the permission set in level. This field translates to a `principal` field in secret scope ACL. + +::: + + +### resources.synced_database_tables + +**`Type: Map`** + + + +```yaml +synced_database_tables: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `data_synchronization_status` + - Map + - See [\_](#resourcessynced_database_tablesnamedata_synchronization_status). + +- - `database_instance_name` + - String + - + +- - `effective_database_instance_name` + - String + - + +- - `effective_logical_database_name` + - String + - + +- - `lifecycle` + - Map + - See [\_](#resourcessynced_database_tablesnamelifecycle). + +- - `logical_database_name` + - String + - + +- - `name` + - String + - + +- - `spec` + - Map + - See [\_](#resourcessynced_database_tablesnamespec). + +- - `unity_catalog_provisioning_state` + - String + - + +::: + + +### resources.synced_database_tables._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.vector_search_endpoints + +**`Type: Map`** + + + +```yaml +vector_search_endpoints: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `budget_policy_id` + - String + - + +- - `endpoint_type` + - String + - + +- - `lifecycle` + - Map + - See [\_](#resourcesvector_search_endpointsnamelifecycle). + +- - `min_qps` + - Integer + - + +- - `name` + - String + - + +- - `permissions` + - Sequence + - See [\_](#resourcesvector_search_endpointsnamepermissions). + +- - `usage_policy_id` + - String + - + +::: + + +### resources.vector_search_endpoints._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.vector_search_endpoints._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +## run_as + +**`Type: Map`** + +The identity to use when running Declarative Automation Bundles resources. See [\_](/dev-tools/bundles/run-as.md). + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - + +- - `service_principal_name` + - String + - The application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. + +- - `user_name` + - String + - The email of an active workspace user. Non-admin users can only set this field to their own email. + +::: + + +## scripts + +**`Type: Map`** + + + +```yaml +scripts: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `content` + - String + - + +::: + + +## sync + +**`Type: Map`** + +The files and file paths to include or exclude in the bundle. See [\_](/dev-tools/bundles/settings.md#sync). + + + +:::list-table + +- - Key + - Type + - Description + +- - `exclude` + - Sequence + - A list of files or folders to exclude from the bundle. + +- - `include` + - Sequence + - A list of files or folders to include in the bundle. + +- - `paths` + - Sequence + - The local folder paths, which can be outside the bundle root, to synchronize to the workspace when the bundle is deployed. + +::: + + +## targets + +**`Type: Map`** + +Defines deployment targets for the bundle. See [\_](/dev-tools/bundles/settings.md#targets) + +```yaml +targets: + : + : +``` :::list-table @@ -753,74 +2148,1259 @@ targets: - - `artifacts` - Map - - The artifacts to include in the target deployment. See [\_](#targetsnameartifacts). + - The artifacts to include in the target deployment. See [\_](#targetsnameartifacts). + +- - `bundle` + - Map + - The bundle attributes when deploying to this target. See [\_](#targetsnamebundle). + +- - `cluster_id` + - String + - The ID of the cluster to use for this target. + +- - `compute_id` + - String + - Deprecated: please use cluster_id instead + +- - `default` + - Boolean + - Whether this target is the default target. + +- - `git` + - Map + - The Git version control settings for the target. See [\_](#targetsnamegit). + +- - `mode` + - String + - The deployment mode for the target. Valid values are `development` or `production`. See [\_](/dev-tools/bundles/deployment-modes.md). + +- - `permissions` + - Sequence + - The permissions for deploying and running the bundle in the target. See [\_](#targetsnamepermissions). + +- - `presets` + - Map + - The deployment presets for the target. See [\_](#targetsnamepresets). + +- - `resources` + - Map + - The resource definitions for the target. See [\_](#targetsnameresources). + +- - `run_as` + - Map + - The identity to use to run the bundle, see [\_](/dev-tools/bundles/run-as.md). See [\_](#targetsnamerun_as). + +- - `sync` + - Map + - The local paths to sync to the target workspace when a bundle is run or deployed. See [\_](#targetsnamesync). + +- - `variables` + - Map + - The custom variable definitions for the target. See [\_](#targetsnamevariables). + +- - `workspace` + - Map + - The Databricks workspace for the target. See [\_](#targetsnameworkspace). + +::: + + +### targets._name_.artifacts + +**`Type: Map`** + +The artifacts to include in the target deployment. + +```yaml +artifacts: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `build` + - String + - An optional set of build commands to run locally before deployment. + +- - `dynamic_version` + - Boolean + - Whether to patch the wheel version dynamically based on the timestamp of the whl file. If this is set to `true`, new code can be deployed without having to update the version in `setup.py` or `pyproject.toml`. This setting is only valid when `type` is set to `whl`. See [\_](/dev-tools/bundles/settings.md#bundle-syntax-mappings-artifacts). + +- - `executable` + - String + - The executable type. Valid values are `bash`, `sh`, and `cmd`. + +- - `files` + - Sequence + - The relative or absolute path to the built artifact files. See [\_](#targetsnameartifactsnamefiles). + +- - `path` + - String + - The local path of the directory for the artifact. + +- - `type` + - String + - Required if the artifact is a Python wheel. The type of the artifact. Valid values are `whl` and `jar`. + +::: + + +### targets._name_.artifacts._name_.files + +**`Type: Sequence`** + +The relative or absolute path to the built artifact files. + + + +:::list-table + +- - Key + - Type + - Description + +- - `source` + - String + - Required. The artifact source file. + +::: + + +### targets._name_.bundle + +**`Type: Map`** + +The bundle attributes when deploying to this target. + + + +:::list-table + +- - Key + - Type + - Description + +- - `cluster_id` + - String + - The ID of a cluster to use to run the bundle. See [\_](/dev-tools/bundles/settings.md#cluster_id). + +- - `compute_id` + - String + - Deprecated. The ID of the compute to use to run the bundle. + +- - `databricks_cli_version` + - String + - The Databricks CLI version to use for the bundle. See [\_](/dev-tools/bundles/settings.md#databricks_cli_version). + +- - `deployment` + - Map + - The definition of the bundle deployment. For supported attributes see [\_](/dev-tools/bundles/deployment-modes.md). See [\_](#targetsnamebundledeployment). + +- - `engine` + - String + - The deployment engine to use. Valid values are `terraform` and `direct`. Takes priority over `DATABRICKS_BUNDLE_ENGINE` environment variable. Default is "terraform". + +- - `git` + - Map + - The Git version control details that are associated with your bundle. For supported attributes see [\_](/dev-tools/bundles/settings.md#git). See [\_](#targetsnamebundlegit). + +- - `name` + - String + - The name of the bundle. + +- - `uuid` + - String + - Reserved. A Universally Unique Identifier (UUID) for the bundle that uniquely identifies the bundle in internal Databricks systems. This is generated when a bundle project is initialized using a Databricks template (using the `databricks bundle init` command). + +::: + + +### targets._name_.bundle.deployment + +**`Type: Map`** + +The definition of the bundle deployment. For supported attributes see [\_](/dev-tools/bundles/deployment-modes.md). + + + +:::list-table + +- - Key + - Type + - Description + +- - `fail_on_active_runs` + - Boolean + - Whether to fail on active runs. If this is set to true a deployment that is running can be interrupted. + +- - `lock` + - Map + - The deployment lock attributes. See [\_](#targetsnamebundledeploymentlock). + +::: + + +### targets._name_.bundle.deployment.lock + +**`Type: Map`** + +The deployment lock attributes. + + + +:::list-table + +- - Key + - Type + - Description + +- - `enabled` + - Boolean + - Whether this lock is enabled. + +- - `force` + - Boolean + - Whether to force this lock if it is enabled. + +::: + + +### targets._name_.bundle.git + +**`Type: Map`** + +The Git version control details that are associated with your bundle. For supported attributes see [\_](/dev-tools/bundles/settings.md#git). + + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - The Git branch name. See [\_](/dev-tools/bundles/settings.md#git). + +- - `origin_url` + - String + - The origin URL of the repository. See [\_](/dev-tools/bundles/settings.md#git). + +::: + + +### targets._name_.git + +**`Type: Map`** + +The Git version control settings for the target. + + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - The Git branch name. See [\_](/dev-tools/bundles/settings.md#git). + +- - `origin_url` + - String + - The origin URL of the repository. See [\_](/dev-tools/bundles/settings.md#git). + +::: + + +### targets._name_.permissions + +**`Type: Sequence`** + +The permissions for deploying and running the bundle in the target. + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### targets._name_.presets + +**`Type: Map`** + +The deployment presets for the target. + + + +:::list-table + +- - Key + - Type + - Description + +- - `artifacts_dynamic_version` + - Boolean + - Whether to enable dynamic_version on all artifacts. + +- - `jobs_max_concurrent_runs` + - Integer + - The maximum concurrent runs for a job. + +- - `name_prefix` + - String + - The prefix for job runs of the bundle. + +- - `pipelines_development` + - Boolean + - Whether pipeline deployments should be locked in development mode. + +- - `source_linked_deployment` + - Boolean + - Whether to link the deployment to the bundle source. + +- - `tags` + - Map + - The tags for the bundle deployment. + +- - `trigger_pause_status` + - String + - A pause status to apply to all job triggers and schedules. Valid values are PAUSED or UNPAUSED. + +::: + + +### targets._name_.resources + +**`Type: Map`** + +The resource definitions for the target. + + + +:::list-table + +- - Key + - Type + - Description + +- - `alerts` + - Map + - See [\_](#targetsnameresourcesalerts). + +- - `apps` + - Map + - The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). See [\_](#targetsnameresourcesapps). + +- - `catalogs` + - Map + - See [\_](#targetsnameresourcescatalogs). + +- - `clusters` + - Map + - The cluster definitions for the bundle, where each key is the name of a cluster. See [\_](/dev-tools/bundles/resources.md#clusters). + +- - `dashboards` + - Map + - The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). See [\_](#targetsnameresourcesdashboards). + +- - `database_catalogs` + - Map + - + +- - `database_instances` + - Map + - See [\_](#targetsnameresourcesdatabase_instances). + +- - `experiments` + - Map + - The experiment definitions for the bundle, where each key is the name of the experiment. See [\_](/dev-tools/bundles/resources.md#experiments). + +- - `external_locations` + - Map + - See [\_](#targetsnameresourcesexternal_locations). + +- - `jobs` + - Map + - The job definitions for the bundle, where each key is the name of the job. See [\_](/dev-tools/bundles/resources.md#jobs). + +- - `model_serving_endpoints` + - Map + - The model serving endpoint definitions for the bundle, where each key is the name of the model serving endpoint. See [\_](/dev-tools/bundles/resources.md#model_serving_endpoints). + +- - `models` + - Map + - The model definitions for the bundle, where each key is the name of the model. See [\_](/dev-tools/bundles/resources.md#models). + +- - `pipelines` + - Map + - The pipeline definitions for the bundle, where each key is the name of the pipeline. See [\_](/dev-tools/bundles/resources.md#pipelines). + +- - `postgres_branches` + - Map + - See [\_](#targetsnameresourcespostgres_branches). + +- - `postgres_endpoints` + - Map + - See [\_](#targetsnameresourcespostgres_endpoints). + +- - `postgres_projects` + - Map + - See [\_](#targetsnameresourcespostgres_projects). + +- - `quality_monitors` + - Map + - The quality monitor definitions for the bundle, where each key is the name of the quality monitor. See [\_](/dev-tools/bundles/resources.md#quality_monitors). + +- - `registered_models` + - Map + - The registered model definitions for the bundle, where each key is the name of the Unity Catalog registered model. See [\_](/dev-tools/bundles/resources.md#registered_models) + +- - `schemas` + - Map + - The schema definitions for the bundle, where each key is the name of the schema. See [\_](/dev-tools/bundles/resources.md#schemas). + +- - `secret_scopes` + - Map + - The secret scope definitions for the bundle, where each key is the name of the secret scope. See [\_](/dev-tools/bundles/resources.md#secret_scopes). See [\_](#targetsnameresourcessecret_scopes). + +- - `sql_warehouses` + - Map + - The SQL warehouse definitions for the bundle, where each key is the name of the warehouse. See [\_](/dev-tools/bundles/resources.md#sql_warehouses). + +- - `synced_database_tables` + - Map + - See [\_](#targetsnameresourcessynced_database_tables). + +- - `vector_search_endpoints` + - Map + - See [\_](#targetsnameresourcesvector_search_endpoints). + +- - `volumes` + - Map + - The volume definitions for the bundle, where each key is the name of the volume. See [\_](/dev-tools/bundles/resources.md#volumes). + +::: + + +### targets._name_.resources.alerts + +**`Type: Map`** + + + +```yaml +alerts: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `create_time` + - String + - + +- - `custom_description` + - String + - + +- - `custom_summary` + - String + - + +- - `display_name` + - String + - + +- - `effective_run_as` + - Map + - See [\_](#targetsnameresourcesalertsnameeffective_run_as). + +- - `evaluation` + - Map + - See [\_](#targetsnameresourcesalertsnameevaluation). + +- - `file_path` + - String + - + +- - `id` + - String + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcesalertsnamelifecycle). + +- - `lifecycle_state` + - String + - + +- - `owner_user_name` + - String + - + +- - `parent_path` + - String + - + +- - `permissions` + - Sequence + - See [\_](#targetsnameresourcesalertsnamepermissions). + +- - `query_text` + - String + - + +- - `run_as` + - Map + - See [\_](#targetsnameresourcesalertsnamerun_as). + +- - `run_as_user_name` + - String + - + +- - `schedule` + - Map + - See [\_](#targetsnameresourcesalertsnameschedule). + +- - `update_time` + - String + - + +- - `warehouse_id` + - String + - + +::: + + +### targets._name_.resources.alerts._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### targets._name_.resources.alerts._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### targets._name_.resources.apps + +**`Type: Map`** + +The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). + +```yaml +apps: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `active_deployment` + - Map + - See [\_](#targetsnameresourcesappsnameactive_deployment). + +- - `app_status` + - Map + - See [\_](#targetsnameresourcesappsnameapp_status). + +- - `budget_policy_id` + - String + - + +- - `compute_size` + - String + - + +- - `compute_status` + - Map + - See [\_](#targetsnameresourcesappsnamecompute_status). + +- - `config` + - Map + - See [\_](#targetsnameresourcesappsnameconfig). + +- - `create_time` + - String + - + +- - `creator` + - String + - + +- - `default_source_code_path` + - String + - + +- - `description` + - String + - + +- - `effective_budget_policy_id` + - String + - + +- - `effective_usage_policy_id` + - String + - + +- - `effective_user_api_scopes` + - Sequence + - + +- - `git_repository` + - Map + - See [\_](#targetsnameresourcesappsnamegit_repository). + +- - `git_source` + - Map + - 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. See [\_](#targetsnameresourcesappsnamegit_source). + +- - `id` + - String + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcesappsnamelifecycle). + +- - `name` + - String + - + +- - `oauth2_app_client_id` + - String + - + +- - `oauth2_app_integration_id` + - String + - + +- - `pending_deployment` + - Map + - See [\_](#targetsnameresourcesappsnamepending_deployment). + +- - `permissions` + - Sequence + - See [\_](#targetsnameresourcesappsnamepermissions). + +- - `resources` + - Sequence + - See [\_](#targetsnameresourcesappsnameresources). + +- - `service_principal_client_id` + - String + - + +- - `service_principal_id` + - Integer + - + +- - `service_principal_name` + - String + - + +- - `source_code_path` + - String + - + +- - `space` + - String + - + +- - `telemetry_export_destinations` + - Sequence + - See [\_](#targetsnameresourcesappsnametelemetry_export_destinations). + +- - `update_time` + - String + - + +- - `updater` + - String + - + +- - `url` + - String + - + +- - `usage_policy_id` + - String + - + +- - `user_api_scopes` + - Sequence + - + +::: + + +### targets._name_.resources.apps._name_.config + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `command` + - Sequence + - + +- - `env` + - Sequence + - See [\_](#targetsnameresourcesappsnameconfigenv). + +::: + + +### targets._name_.resources.apps._name_.config.env + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `name` + - String + - + +- - `value` + - String + - + +- - `value_from` + - String + - + +::: + + +### targets._name_.resources.apps._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +- - `started` + - Boolean + - Lifecycle setting to deploy the resource in started mode. Only supported for apps, clusters, and sql_warehouses in direct deployment mode. + +::: + + +### targets._name_.resources.apps._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - + +- - `level` + - String + - + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - + +::: + + +### targets._name_.resources.catalogs + +**`Type: Map`** + + + +```yaml +catalogs: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `comment` + - String + - + +- - `connection_name` + - String + - + +- - `grants` + - Sequence + - See [\_](#targetsnameresourcescatalogsnamegrants). + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcescatalogsnamelifecycle). + +- - `managed_encryption_settings` + - Map + - See [\_](#targetsnameresourcescatalogsnamemanaged_encryption_settings). + +- - `name` + - String + - + +- - `options` + - Map + - + +- - `properties` + - Map + - + +- - `provider_name` + - String + - + +- - `share_name` + - String + - + +- - `storage_root` + - String + - + +::: + + +### targets._name_.resources.catalogs._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### targets._name_.resources.dashboards + +**`Type: Map`** + +The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). + +```yaml +dashboards: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `create_time` + - String + - + +- - `dashboard_id` + - String + - + +- - `dataset_catalog` + - String + - Sets the default catalog for all datasets in this dashboard. When set, this overrides the catalog specified in individual dataset definitions. + +- - `dataset_schema` + - String + - Sets the default schema for all datasets in this dashboard. When set, this overrides the schema specified in individual dataset definitions. + +- - `display_name` + - String + - + +- - `embed_credentials` + - Boolean + - + +- - `etag` + - String + - + +- - `file_path` + - String + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcesdashboardsnamelifecycle). + +- - `lifecycle_state` + - String + - + +- - `parent_path` + - String + - + +- - `path` + - String + - + +- - `permissions` + - Sequence + - See [\_](#targetsnameresourcesdashboardsnamepermissions). + +- - `serialized_dashboard` + - Any + - + +- - `update_time` + - String + - + +- - `warehouse_id` + - String + - + +::: + + +### targets._name_.resources.dashboards._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### targets._name_.resources.dashboards._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + +### targets._name_.resources.database_instances + +**`Type: Map`** + + + +```yaml +database_instances: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `capacity` + - String + - + +- - `child_instance_refs` + - Sequence + - See [\_](#targetsnameresourcesdatabase_instancesnamechild_instance_refs). + +- - `creation_time` + - String + - + +- - `creator` + - String + - + +- - `custom_tags` + - Sequence + - See [\_](#targetsnameresourcesdatabase_instancesnamecustom_tags). + +- - `effective_capacity` + - String + - + +- - `effective_custom_tags` + - Sequence + - See [\_](#targetsnameresourcesdatabase_instancesnameeffective_custom_tags). + +- - `effective_enable_pg_native_login` + - Boolean + - + +- - `effective_enable_readable_secondaries` + - Boolean + - + +- - `effective_node_count` + - Integer + - -- - `bundle` - - Map - - The bundle attributes when deploying to this target. See [\_](#targetsnamebundle). +- - `effective_retention_window_in_days` + - Integer + - -- - `cluster_id` - - String - - The ID of the cluster to use for this target. +- - `effective_stopped` + - Boolean + - -- - `compute_id` +- - `effective_usage_policy_id` - String - - Deprecated: please use cluster_id instead + - -- - `default` +- - `enable_pg_native_login` - Boolean - - Whether this target is the default target. + - -- - `git` +- - `enable_readable_secondaries` + - Boolean + - + +- - `lifecycle` - Map - - The Git version control settings for the target. See [\_](#targetsnamegit). + - See [\_](#targetsnameresourcesdatabase_instancesnamelifecycle). -- - `mode` +- - `name` - String - - The deployment mode for the target. Valid values are `development` or `production`. See [\_](/dev-tools/bundles/deployment-modes.md). + - + +- - `node_count` + - Integer + - + +- - `parent_instance_ref` + - Map + - See [\_](#targetsnameresourcesdatabase_instancesnameparent_instance_ref). - - `permissions` - Sequence - - The permissions for deploying and running the bundle in the target. See [\_](#targetsnamepermissions). + - See [\_](#targetsnameresourcesdatabase_instancesnamepermissions). -- - `presets` - - Map - - The deployment presets for the target. See [\_](#targetsnamepresets). +- - `pg_version` + - String + - -- - `resources` - - Map - - The resource definitions for the target. See [\_](#targetsnameresources). +- - `read_only_dns` + - String + - -- - `run_as` - - Map - - The identity to use to run the bundle, see [\_](/dev-tools/bundles/run-as.md). See [\_](#targetsnamerun_as). +- - `read_write_dns` + - String + - -- - `sync` - - Map - - The local paths to sync to the target workspace when a bundle is run or deployed. See [\_](#targetsnamesync). +- - `retention_window_in_days` + - Integer + - -- - `variables` - - Map - - The custom variable definitions for the target. See [\_](#targetsnamevariables). +- - `state` + - String + - -- - `workspace` - - Map - - The Databricks workspace for the target. See [\_](#targetsnameworkspace). +- - `stopped` + - Boolean + - + +- - `uid` + - String + - + +- - `usage_policy_id` + - String + - ::: -### targets._name_.artifacts +### targets._name_.resources.database_instances._name_.lifecycle **`Type: Map`** -The artifacts to include in the target deployment. + -```yaml -artifacts: - : - : -``` :::list-table @@ -829,38 +3409,18 @@ artifacts: - Type - Description -- - `build` - - String - - An optional set of build commands to run locally before deployment. - -- - `dynamic_version` +- - `prevent_destroy` - Boolean - - Whether to patch the wheel version dynamically based on the timestamp of the whl file. If this is set to `true`, new code can be deployed without having to update the version in `setup.py` or `pyproject.toml`. This setting is only valid when `type` is set to `whl`. See [\_](/dev-tools/bundles/settings.md#bundle-syntax-mappings-artifacts). - -- - `executable` - - String - - The executable type. Valid values are `bash`, `sh`, and `cmd`. - -- - `files` - - Sequence - - The relative or absolute path to the built artifact files. See [\_](#targetsnameartifactsnamefiles). - -- - `path` - - String - - The local path of the directory for the artifact. - -- - `type` - - String - - Required if the artifact is a Python wheel. The type of the artifact. Valid values are `whl` and `jar`. + - Lifecycle setting to prevent the resource from being destroyed. ::: -### targets._name_.artifacts._name_.files +### targets._name_.resources.database_instances._name_.permissions **`Type: Sequence`** -The relative or absolute path to the built artifact files. + @@ -870,19 +3430,36 @@ The relative or absolute path to the built artifact files. - Type - Description -- - `source` +- - `group_name` - String - - Required. The artifact source file. + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. ::: -### targets._name_.bundle +### targets._name_.resources.external_locations **`Type: Map`** -The bundle attributes when deploying to this target. + +```yaml +external_locations: + : + : +``` :::list-table @@ -891,42 +3468,70 @@ The bundle attributes when deploying to this target. - Type - Description -- - `cluster_id` +- - `comment` - String - - The ID of a cluster to use to run the bundle. See [\_](/dev-tools/bundles/settings.md#cluster_id). + - -- - `compute_id` +- - `credential_name` - String - - Deprecated. The ID of the compute to use to run the bundle. + - -- - `databricks_cli_version` - - String - - The Databricks CLI version to use for the bundle. See [\_](/dev-tools/bundles/settings.md#databricks_cli_version). +- - `effective_enable_file_events` + - Boolean + - -- - `deployment` +- - `effective_file_event_queue` - Map - - The definition of the bundle deployment. For supported attributes see [\_](/dev-tools/bundles/deployment-modes.md). See [\_](#targetsnamebundledeployment). + - See [\_](#targetsnameresourcesexternal_locationsnameeffective_file_event_queue). -- - `git` +- - `enable_file_events` + - Boolean + - + +- - `encryption_details` - Map - - The Git version control details that are associated with your bundle. For supported attributes see [\_](/dev-tools/bundles/settings.md#git). See [\_](#targetsnamebundlegit). + - See [\_](#targetsnameresourcesexternal_locationsnameencryption_details). + +- - `fallback` + - Boolean + - + +- - `file_event_queue` + - Map + - See [\_](#targetsnameresourcesexternal_locationsnamefile_event_queue). + +- - `grants` + - Sequence + - See [\_](#targetsnameresourcesexternal_locationsnamegrants). + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcesexternal_locationsnamelifecycle). - - `name` - String - - The name of the bundle. + - -- - `uuid` +- - `read_only` + - Boolean + - + +- - `skip_validation` + - Boolean + - + +- - `url` - String - - Reserved. A Universally Unique Identifier (UUID) for the bundle that uniquely identifies the bundle in internal Databricks systems. This is generated when a bundle project is initialized using a Databricks template (using the `databricks bundle init` command). + - ::: -### targets._name_.bundle.deployment +### targets._name_.resources.external_locations._name_.lifecycle **`Type: Map`** -The definition of the bundle deployment. For supported attributes see [\_](/dev-tools/bundles/deployment-modes.md). + @@ -936,23 +3541,24 @@ The definition of the bundle deployment. For supported attributes see [\_](/dev- - Type - Description -- - `fail_on_active_runs` +- - `prevent_destroy` - Boolean - - Whether to fail on active runs. If this is set to true a deployment that is running can be interrupted. - -- - `lock` - - Map - - The deployment lock attributes. See [\_](#targetsnamebundledeploymentlock). + - Lifecycle setting to prevent the resource from being destroyed. ::: -### targets._name_.bundle.deployment.lock +### targets._name_.resources.postgres_branches **`Type: Map`** -The deployment lock attributes. + +```yaml +postgres_branches: + : + : +``` :::list-table @@ -961,22 +3567,54 @@ The deployment lock attributes. - Type - Description -- - `enabled` +- - `branch_id` + - String + - + +- - `expire_time` + - Map + - + +- - `is_protected` - Boolean - - Whether this lock is enabled. + - -- - `force` +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcespostgres_branchesnamelifecycle). + +- - `no_expiry` - Boolean - - Whether to force this lock if it is enabled. + - + +- - `parent` + - String + - + +- - `source_branch` + - String + - + +- - `source_branch_lsn` + - String + - + +- - `source_branch_time` + - Map + - + +- - `ttl` + - String + - ::: -### targets._name_.bundle.git +### targets._name_.resources.postgres_branches._name_.lifecycle **`Type: Map`** -The Git version control details that are associated with your bundle. For supported attributes see [\_](/dev-tools/bundles/settings.md#git). + @@ -986,23 +3624,24 @@ The Git version control details that are associated with your bundle. For suppor - Type - Description -- - `branch` - - String - - The Git branch name. See [\_](/dev-tools/bundles/settings.md#git). - -- - `origin_url` - - String - - The origin URL of the repository. See [\_](/dev-tools/bundles/settings.md#git). +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### targets._name_.git +### targets._name_.resources.postgres_endpoints **`Type: Map`** -The Git version control settings for the target. + +```yaml +postgres_endpoints: + : + : +``` :::list-table @@ -1011,22 +3650,58 @@ The Git version control settings for the target. - Type - Description -- - `branch` +- - `autoscaling_limit_max_cu` + - Any + - + +- - `autoscaling_limit_min_cu` + - Any + - + +- - `disabled` + - Boolean + - + +- - `endpoint_id` - String - - The Git branch name. See [\_](/dev-tools/bundles/settings.md#git). + - + +- - `endpoint_type` + - String + - + +- - `group` + - Map + - See [\_](#targetsnameresourcespostgres_endpointsnamegroup). + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcespostgres_endpointsnamelifecycle). + +- - `no_suspension` + - Boolean + - + +- - `parent` + - String + - + +- - `settings` + - Map + - See [\_](#targetsnameresourcespostgres_endpointsnamesettings). -- - `origin_url` +- - `suspend_timeout_duration` - String - - The origin URL of the repository. See [\_](/dev-tools/bundles/settings.md#git). + - ::: -### targets._name_.permissions +### targets._name_.resources.postgres_endpoints._name_.lifecycle -**`Type: Sequence`** +**`Type: Map`** -The permissions for deploying and running the bundle in the target. + @@ -1036,31 +3711,24 @@ The permissions for deploying and running the bundle in the target. - Type - Description -- - `group_name` - - String - - The name of the group that has the permission set in level. - -- - `level` - - String - - The allowed permission for user, group, service principal defined for this permission. - -- - `service_principal_name` - - String - - The name of the service principal that has the permission set in level. - -- - `user_name` - - String - - The name of the user that has the permission set in level. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### targets._name_.presets +### targets._name_.resources.postgres_projects **`Type: Map`** -The deployment presets for the target. + +```yaml +postgres_projects: + : + : +``` :::list-table @@ -1069,42 +3737,58 @@ The deployment presets for the target. - Type - Description -- - `artifacts_dynamic_version` - - Boolean - - Whether to enable dynamic_version on all artifacts. +- - `budget_policy_id` + - String + - -- - `jobs_max_concurrent_runs` - - Integer - - The maximum concurrent runs for a job. +- - `custom_tags` + - Sequence + - See [\_](#targetsnameresourcespostgres_projectsnamecustom_tags). -- - `name_prefix` +- - `default_branch` - String - - The prefix for job runs of the bundle. + - -- - `pipelines_development` - - Boolean - - Whether pipeline deployments should be locked in development mode. +- - `default_endpoint_settings` + - Map + - See [\_](#targetsnameresourcespostgres_projectsnamedefault_endpoint_settings). -- - `source_linked_deployment` +- - `display_name` + - String + - + +- - `enable_pg_native_login` - Boolean - - Whether to link the deployment to the bundle source. + - -- - `tags` +- - `history_retention_duration` + - String + - + +- - `lifecycle` - Map - - The tags for the bundle deployment. + - See [\_](#targetsnameresourcespostgres_projectsnamelifecycle). -- - `trigger_pause_status` +- - `permissions` + - Sequence + - See [\_](#targetsnameresourcespostgres_projectsnamepermissions). + +- - `pg_version` + - Integer + - + +- - `project_id` - String - - A pause status to apply to all job triggers and schedules. Valid values are PAUSED or UNPAUSED. + - ::: -### targets._name_.resources +### targets._name_.resources.postgres_projects._name_.lifecycle **`Type: Map`** -The resource definitions for the target. + @@ -1114,73 +3798,42 @@ The resource definitions for the target. - Type - Description -- - `apps` - - Map - - The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). - -- - `clusters` - - Map - - The cluster definitions for the bundle, where each key is the name of a cluster. See [\_](/dev-tools/bundles/resources.md#clusters). - -- - `dashboards` - - Map - - The dashboard definitions for the bundle, where each key is the name of the dashboard. See [\_](/dev-tools/bundles/resources.md#dashboards). - -- - `database_catalogs` - - Map - - +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. -- - `database_instances` - - Map - - +::: -- - `experiments` - - Map - - The experiment definitions for the bundle, where each key is the name of the experiment. See [\_](/dev-tools/bundles/resources.md#experiments). -- - `jobs` - - Map - - The job definitions for the bundle, where each key is the name of the job. See [\_](/dev-tools/bundles/resources.md#jobs). +### targets._name_.resources.postgres_projects._name_.permissions -- - `model_serving_endpoints` - - Map - - The model serving endpoint definitions for the bundle, where each key is the name of the model serving endpoint. See [\_](/dev-tools/bundles/resources.md#model_serving_endpoints). +**`Type: Sequence`** -- - `models` - - Map - - The model definitions for the bundle, where each key is the name of the model. See [\_](/dev-tools/bundles/resources.md#models). + -- - `pipelines` - - Map - - The pipeline definitions for the bundle, where each key is the name of the pipeline. See [\_](/dev-tools/bundles/resources.md#pipelines). -- - `quality_monitors` - - Map - - The quality monitor definitions for the bundle, where each key is the name of the quality monitor. See [\_](/dev-tools/bundles/resources.md#quality_monitors). -- - `registered_models` - - Map - - The registered model definitions for the bundle, where each key is the name of the Unity Catalog registered model. See [\_](/dev-tools/bundles/resources.md#registered_models) +:::list-table -- - `schemas` - - Map - - The schema definitions for the bundle, where each key is the name of the schema. See [\_](/dev-tools/bundles/resources.md#schemas). +- - Key + - Type + - Description -- - `secret_scopes` - - Map - - The secret scope definitions for the bundle, where each key is the name of the secret scope. See [\_](/dev-tools/bundles/resources.md#secret_scopes). See [\_](#targetsnameresourcessecret_scopes). +- - `group_name` + - String + - The name of the group that has the permission set in level. -- - `sql_warehouses` - - Map - - The SQL warehouse definitions for the bundle, where each key is the name of the warehouse. See [\_](/dev-tools/bundles/resources.md#sql_warehouses). +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. -- - `synced_database_tables` - - Map - - See [\_](#targetsnameresourcessynced_database_tables). +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. -- - `volumes` - - Map - - The volume definitions for the bundle, where each key is the name of the volume. See [\_](/dev-tools/bundles/resources.md#volumes). +- - `user_name` + - String + - The name of the user that has the permission set in level. ::: @@ -1318,7 +3971,7 @@ synced_database_tables: - - `lifecycle` - Map - - 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. See [\_](#targetsnameresourcessynced_database_tablesnamelifecycle). + - See [\_](#targetsnameresourcessynced_database_tablesnamelifecycle). - - `logical_database_name` - String @@ -1343,7 +3996,78 @@ synced_database_tables: **`Type: Map`** -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. + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### targets._name_.resources.vector_search_endpoints + +**`Type: Map`** + + + +```yaml +vector_search_endpoints: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `budget_policy_id` + - String + - + +- - `endpoint_type` + - String + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcesvector_search_endpointsnamelifecycle). + +- - `min_qps` + - Integer + - + +- - `name` + - String + - + +- - `permissions` + - Sequence + - See [\_](#targetsnameresourcesvector_search_endpointsnamepermissions). + +- - `usage_policy_id` + - String + - + +::: + + +### targets._name_.resources.vector_search_endpoints._name_.lifecycle + +**`Type: Map`** + + @@ -1360,6 +4084,39 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co ::: +### targets._name_.resources.vector_search_endpoints._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + ### targets._name_.run_as **`Type: Map`** @@ -1374,6 +4131,10 @@ The identity to use to run the bundle, see [\_](/dev-tools/bundles/run-as.md). - Type - Description +- - `group_name` + - String + - + - - `service_principal_name` - String - The application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. @@ -1531,9 +4292,13 @@ The Databricks workspace for the target. - Type - Description +- - `account_id` + - String + - The Databricks account ID. + - - `artifact_path` - String - - The artifact path to use within the workspace for both deployments and workflow runs + - The artifact path to use within the workspace for both deployments and job runs - - `auth_type` - String @@ -1567,9 +4332,13 @@ The Databricks workspace for the target. - String - The client ID for the workspace +- - `experimental_is_unified_host` + - Boolean + - Deprecated: no-op. Unified hosts are now detected automatically from /.well-known/databricks-config. Retained for schema compatibility with existing databricks.yml files. + - - `file_path` - String - - The file path to use within the workspace for both deployments and workflow runs + - The file path to use within the workspace for both deployments and job runs - - `google_service_account` - String @@ -1595,6 +4364,10 @@ The Databricks workspace for the target. - String - The workspace state path +- - `workspace_id` + - String + - The Databricks workspace ID + ::: @@ -1715,9 +4488,13 @@ Defines the Databricks workspace for the bundle. See [\_](/dev-tools/bundles/set - Type - Description +- - `account_id` + - String + - The Databricks account ID. + - - `artifact_path` - String - - The artifact path to use within the workspace for both deployments and workflow runs + - The artifact path to use within the workspace for both deployments and job runs - - `auth_type` - String @@ -1751,9 +4528,13 @@ Defines the Databricks workspace for the bundle. See [\_](/dev-tools/bundles/set - String - The client ID for the workspace +- - `experimental_is_unified_host` + - Boolean + - Deprecated: no-op. Unified hosts are now detected automatically from /.well-known/databricks-config. Retained for schema compatibility with existing databricks.yml files. + - - `file_path` - String - - The file path to use within the workspace for both deployments and workflow runs + - The file path to use within the workspace for both deployments and job runs - - `google_service_account` - String @@ -1779,5 +4560,9 @@ Defines the Databricks workspace for the bundle. See [\_](/dev-tools/bundles/set - String - The workspace state path +- - `workspace_id` + - String + - The Databricks workspace ID + ::: \ No newline at end of file diff --git a/bundle/docsgen/output/resources.md b/bundle/docsgen/output/resources.md index 2075ceae55c..e262f4620b6 100644 --- a/bundle/docsgen/output/resources.md +++ b/bundle/docsgen/output/resources.md @@ -1,7 +1,7 @@ --- description: 'Learn about resources supported by Declarative Automation Bundles and how to configure them.' last_update: - date: 2025-09-13 + date: 2026-04-23 --- @@ -124,16 +124,16 @@ The `databricks bundle validate` command returns warnings if unknown resource pr :::: -## apps +## alerts **`Type: Map`** -The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). + ```yaml -apps: - : - : +alerts: + : + : ``` @@ -143,50 +143,66 @@ apps: - Type - Description -- - `budget_policy_id` +- - `custom_description` - String - -- - `config` - - Map +- - `custom_summary` + - String - -- - `description` +- - `display_name` - String - - The description of the app. + - + +- - `evaluation` + - Map + - See [\_](#alertsnameevaluation). + +- - `file_path` + - String + - - - `lifecycle` - Map - - 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. See [\_](#appsnamelifecycle). + - See [\_](#alertsnamelifecycle). -- - `name` +- - `parent_path` - String - - The name of the app. The name must contain only lowercase alphanumeric characters and hyphens. It must be unique within the workspace. + - - - `permissions` - Sequence - - See [\_](#appsnamepermissions). - -- - `resources` - - Sequence - - Resources for the app. See [\_](#appsnameresources). + - See [\_](#alertsnamepermissions). -- - `source_code_path` +- - `query_text` - String - -- - `user_api_scopes` - - Sequence +- - `run_as` + - Map + - See [\_](#alertsnamerun_as). + +- - `run_as_user_name` + - String + - This field is deprecated + +- - `schedule` + - Map + - See [\_](#alertsnameschedule). + +- - `warehouse_id` + - String - ::: -### apps._name_.lifecycle +### alerts._name_.evaluation **`Type: Map`** -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. + @@ -196,14 +212,59 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` +- - `comparison_operator` + - String + - Operator used for comparison in alert evaluation. + +- - `empty_result_state` + - String + - Alert state if result is empty. Please avoid setting this field to be `UNKNOWN` because `UNKNOWN` state is planned to be deprecated. + +- - `notification` + - Map + - User or Notification Destination to notify when alert is triggered. See [\_](#alertsnameevaluationnotification). + +- - `source` + - Map + - Source column from result to use to evaluate alert. See [\_](#alertsnameevaluationsource). + +- - `threshold` + - Map + - Threshold to user for alert evaluation, can be a column or a value. See [\_](#alertsnameevaluationthreshold). + +::: + + +### alerts._name_.evaluation.notification + +**`Type: Map`** + +User or Notification Destination to notify when alert is triggered. + + + +:::list-table + +- - Key + - Type + - Description + +- - `notify_on_ok` - Boolean - - Lifecycle setting to prevent the resource from being destroyed. + - Whether to notify alert subscribers when alert returns back to normal. + +- - `retrigger_seconds` + - Integer + - Number of seconds an alert waits after being triggered before it is allowed to send another notification. If set to 0 or omitted, the alert will not send any further notifications after the first trigger Setting this value to 1 allows the alert to send a notification on every evaluation where the condition is met, effectively making it always retrigger for notification purposes. + +- - `subscriptions` + - Sequence + - See [\_](#alertsnameevaluationnotificationsubscriptions). ::: -### apps._name_.permissions +### alerts._name_.evaluation.notification.subscriptions **`Type: Sequence`** @@ -217,30 +278,22 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `group_name` - - String - - - -- - `level` - - String - - - -- - `service_principal_name` +- - `destination_id` - String - -- - `user_name` +- - `user_email` - String - ::: -### apps._name_.resources +### alerts._name_.evaluation.source -**`Type: Sequence`** +**`Type: Map`** -Resources for the app. +Source column from result to use to evaluate alert @@ -250,42 +303,47 @@ Resources for the app. - Type - Description -- - `database` - - Map - - See [\_](#appsnameresourcesdatabase). - -- - `description` +- - `aggregation` - String - - Description of the App Resource. + - -- - `job` - - Map - - See [\_](#appsnameresourcesjob). +- - `display` + - String + - - - `name` - String - - Name of the App Resource. + - -- - `secret` - - Map - - See [\_](#appsnameresourcessecret). +::: -- - `serving_endpoint` - - Map - - See [\_](#appsnameresourcesserving_endpoint). -- - `sql_warehouse` +### alerts._name_.evaluation.threshold + +**`Type: Map`** + +Threshold to user for alert evaluation, can be a column or a value. + + + +:::list-table + +- - Key + - Type + - Description + +- - `column` - Map - - See [\_](#appsnameresourcessql_warehouse). + - See [\_](#alertsnameevaluationthresholdcolumn). -- - `uc_securable` +- - `value` - Map - - See [\_](#appsnameresourcesuc_securable). + - See [\_](#alertsnameevaluationthresholdvalue). ::: -### apps._name_.resources.database +### alerts._name_.evaluation.threshold.column **`Type: Map`** @@ -299,22 +357,22 @@ Resources for the app. - Type - Description -- - `database_name` +- - `aggregation` - String - -- - `instance_name` +- - `display` - String - -- - `permission` +- - `name` - String - ::: -### apps._name_.resources.job +### alerts._name_.evaluation.threshold.value **`Type: Map`** @@ -328,18 +386,22 @@ Resources for the app. - Type - Description -- - `id` - - String +- - `bool_value` + - Boolean - -- - `permission` +- - `double_value` + - Any + - + +- - `string_value` - String - ::: -### apps._name_.resources.secret +### alerts._name_.lifecycle **`Type: Map`** @@ -353,24 +415,16 @@ Resources for the app. - Type - Description -- - `key` - - String - - - -- - `permission` - - String - - Permission to grant on the secret scope. Supported permissions are: "READ", "WRITE", "MANAGE". - -- - `scope` - - String - - +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### apps._name_.resources.serving_endpoint +### alerts._name_.permissions -**`Type: Map`** +**`Type: Sequence`** @@ -382,18 +436,26 @@ Resources for the app. - Type - Description -- - `name` +- - `group_name` - String - - + - The name of the group that has the permission set in level. -- - `permission` +- - `level` - String - - + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. ::: -### apps._name_.resources.sql_warehouse +### alerts._name_.run_as **`Type: Map`** @@ -407,18 +469,18 @@ Resources for the app. - Type - Description -- - `id` +- - `service_principal_name` - String - - + - Application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. -- - `permission` +- - `user_name` - String - - + - The email of an active workspace user. Can only set this field to their own email. ::: -### apps._name_.resources.uc_securable +### alerts._name_.schedule **`Type: Map`** @@ -432,31 +494,31 @@ Resources for the app. - Type - Description -- - `permission` +- - `pause_status` - String - - + - Indicate whether this schedule is paused or not. -- - `securable_full_name` +- - `quartz_cron_schedule` - String - - + - A cron expression using quartz syntax that specifies the schedule for this pipeline. Should use the quartz format described here: http://www.quartz-scheduler.org/documentation/quartz-2.1.7/tutorials/tutorial-lesson-06.html -- - `securable_type` +- - `timezone_id` - String - - + - A Java timezone id. The schedule will be resolved using this timezone. This will be combined with the quartz_cron_schedule to determine the schedule. See https://docs.databricks.com/sql/language-manual/sql-ref-syntax-aux-conf-mgmt-set-timezone.html for details. ::: -## clusters +## apps **`Type: Map`** -The cluster resource defines an [all-purpose cluster](/api/workspace/clusters/create). +The app resource defines a [Databricks app](/api/workspace/apps/create). For information about Databricks Apps, see [\_](/dev-tools/databricks-apps/index.md). ```yaml -clusters: - : - : +apps: + : + : ``` @@ -466,179 +528,91 @@ clusters: - Type - Description -- - `apply_policy_default_values` - - Boolean - - When set to true, fixed and default values from the policy will be used for fields that are omitted. When set to false, only fixed values from the policy will be applied. - -- - `autoscale` - - Map - - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#clustersnameautoscale). +- - `budget_policy_id` + - String + - -- - `autotermination_minutes` - - Integer - - Automatically terminates the cluster after it is inactive for this time in minutes. If not set, this cluster will not be automatically terminated. If specified, the threshold must be between 10 and 10000 minutes. Users can also set this value to 0 to explicitly disable automatic termination. +- - `compute_size` + - String + - -- - `aws_attributes` +- - `config` - Map - - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnameaws_attributes). + - See [\_](#appsnameconfig). -- - `azure_attributes` +- - `description` + - String + - The description of the app. + +- - `git_source` - Map - - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnameazure_attributes). + - 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. See [\_](#appsnamegit_source). -- - `cluster_log_conf` +- - `lifecycle` - Map - - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#clustersnamecluster_log_conf). + - 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. See [\_](#appsnamelifecycle). -- - `cluster_name` +- - `name` - String - - Cluster name requested by the user. This doesn't have to be unique. If not specified at creation, the cluster name will be an empty string. For job clusters, the cluster name is automatically set based on the job and job run IDs. + - The name of the app. The name must contain only lowercase alphanumeric characters and hyphens. It must be unique within the workspace. -- - `custom_tags` - - Map - - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags +- - `permissions` + - Sequence + - See [\_](#appsnamepermissions). -- - `data_security_mode` +- - `resources` + - Sequence + - Resources for the app. See [\_](#appsnameresources). + +- - `source_code_path` - String - - Data security mode decides what data governance model to use when accessing data from a cluster. The following modes can only be used when `kind = CLASSIC_PREVIEW`. * `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration. * `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`. * `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`. The following modes can be used regardless of `kind`. * `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode. * `SINGLE_USER`: A secure cluster that can only be exclusively used by a single user specified in `single_user_name`. Most programming languages, cluster features and data governance features are available in this mode. * `USER_ISOLATION`: A secure cluster that can be shared by multiple users. Cluster users are fully isolated so that they cannot see each other's data and credentials. Most data governance features are supported in this mode. But programming languages and cluster features might be limited. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: * `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters. * `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters. * `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters. * `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled. + - -- - `docker_image` - - Map - - See [\_](#clustersnamedocker_image). +- - `telemetry_export_destinations` + - Sequence + - See [\_](#appsnametelemetry_export_destinations). -- - `driver_instance_pool_id` - - String - - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. - -- - `driver_node_type_id` +- - `usage_policy_id` - String - - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. This field, along with node_type_id, should not be set if virtual_cluster_size is set. If both driver_node_type_id, node_type_id, and virtual_cluster_size are specified, driver_node_type_id and node_type_id take precedence. - -- - `enable_elastic_disk` - - Boolean - - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. This feature requires specific AWS permissions to function correctly - refer to the User Guide for more details. - -- - `enable_local_disk_encryption` - - Boolean - - Whether to enable LUKS on cluster VMs' local disks - -- - `gcp_attributes` - - Map - - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnamegcp_attributes). + - -- - `init_scripts` +- - `user_api_scopes` - Sequence - - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#clustersnameinit_scripts). - -- - `instance_pool_id` - - String - - The optional ID of the instance pool to which the cluster belongs. - -- - `is_single_node` - - Boolean - - This field can only be used when `kind = CLASSIC_PREVIEW`. When set to true, Databricks will automatically set single node related `custom_tags`, `spark_conf`, and `num_workers` - -- - `kind` - - String - -- - `lifecycle` - - Map - - 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. See [\_](#clustersnamelifecycle). - -- - `node_type_id` - - String - - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. - -- - `num_workers` - - Integer - - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. +::: -- - `permissions` - - Sequence - - See [\_](#clustersnamepermissions). -- - `policy_id` - - String - - The ID of the cluster policy used to create the cluster if applicable. +### apps._name_.config -- - `remote_disk_throughput` - - Integer - - If set, what the configurable throughput (in Mb/s) for the remote disk is. Currently only supported for GCP HYPERDISK_BALANCED disks. +**`Type: Map`** -- - `runtime_engine` - - String - - + -- - `single_user_name` - - String - - Single user name if data_security_mode is `SINGLE_USER` -- - `spark_conf` - - Map - - An object containing a set of optional, user-specified Spark configuration key-value pairs. Users can also pass in a string of extra JVM options to the driver and the executors via `spark.driver.extraJavaOptions` and `spark.executor.extraJavaOptions` respectively. -- - `spark_env_vars` - - Map - - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` +:::list-table -- - `spark_version` - - String - - The Spark version of the cluster, e.g. `3.3.x-scala2.11`. A list of available Spark versions can be retrieved by using the :method:clusters/sparkVersions API call. +- - Key + - Type + - Description -- - `ssh_public_keys` +- - `command` - Sequence - - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. - -- - `total_initial_remote_disk_size` - - Integer - - If set, what the total initial volume size (in GB) of the remote disks should be. Currently only supported for GCP HYPERDISK_BALANCED disks. - -- - `use_ml_runtime` - - Boolean - - This field can only be used when `kind = CLASSIC_PREVIEW`. `effective_spark_version` is determined by `spark_version` (DBR release), this field `use_ml_runtime`, and whether `node_type_id` is gpu node or not. + - -- - `workload_type` - - Map - - Cluster Attributes showing for clusters workload types. See [\_](#clustersnameworkload_type). +- - `env` + - Sequence + - See [\_](#appsnameconfigenv). ::: -**Example** - -The following example creates a cluster named `my_cluster` and sets that as the cluster to use to run the notebook in `my_job`: - -```yaml -bundle: - name: clusters - -resources: - clusters: - my_cluster: - num_workers: 2 - node_type_id: "i3.xlarge" - autoscale: - min_workers: 2 - max_workers: 7 - spark_version: "13.3.x-scala2.12" - spark_conf: - "spark.executor.memory": "2g" - - jobs: - my_job: - tasks: - - task_key: test_task - notebook_task: - notebook_path: "./src/my_notebook.py" -``` - -### clusters._name_.autoscale +### apps._name_.config.env -**`Type: Map`** +**`Type: Sequence`** -Parameters needed in order to automatically scale clusters up and down based on load. -Note: autoscaling works best with DB runtime versions 3.0 or later. + @@ -648,23 +622,28 @@ Note: autoscaling works best with DB runtime versions 3.0 or later. - Type - Description -- - `max_workers` - - Integer - - The maximum number of workers to which the cluster can scale up when overloaded. Note that `max_workers` must be strictly greater than `min_workers`. +- - `name` + - String + - -- - `min_workers` - - Integer - - The minimum number of workers to which the cluster can scale down when underutilized. It is also the initial number of workers the cluster will have after creation. +- - `value` + - String + - + +- - `value_from` + - String + - ::: -### clusters._name_.aws_attributes +### apps._name_.git_source **`Type: Map`** -Attributes related to clusters running on Amazon Web Services. -If not specified at cluster creation, a set of default values will be used. +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. @@ -674,55 +653,30 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` +- - `branch` - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. - -- - `ebs_volume_count` - - Integer - - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. - -- - `ebs_volume_iops` - - Integer - - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. - -- - `ebs_volume_size` - - Integer - - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. - -- - `ebs_volume_throughput` - - Integer - - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + - Git branch to checkout. -- - `ebs_volume_type` +- - `commit` - String - - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + - Git commit SHA to checkout. -- - `instance_profile_arn` +- - `source_code_path` - String - - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. + - Relative path to the app source code within the Git repository. If not specified, the root of the repository is used. -- - `spot_bid_price_percent` - - Integer - - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. - -- - `zone_id` +- - `tag` - String - - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, a default zone will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. + - Git tag to checkout. ::: -### clusters._name_.azure_attributes +### apps._name_.lifecycle **`Type: Map`** -Attributes related to clusters running on Microsoft Azure. -If not specified at cluster creation, a set of default values will be used. +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. @@ -732,30 +686,22 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` - - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. - -- - `log_analytics_info` - - Map - - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#clustersnameazure_attributeslog_analytics_info). +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. -- - `spot_bid_max_price` - - Any - - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. +- - `started` + - Boolean + - Lifecycle setting to deploy the resource in started mode. Only supported for apps, clusters, and sql_warehouses in direct deployment mode. ::: -### clusters._name_.azure_attributes.log_analytics_info +### apps._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -Defines values necessary to configure and run Azure Log Analytics agent + @@ -765,26 +711,30 @@ Defines values necessary to configure and run Azure Log Analytics agent - Type - Description -- - `log_analytics_primary_key` +- - `group_name` - String - - The primary key for the Azure Log Analytics agent configuration + - -- - `log_analytics_workspace_id` +- - `level` - String - - The workspace ID for the Azure Log Analytics agent configuration + - Permission level + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - ::: -### clusters._name_.cluster_log_conf +### apps._name_.resources -**`Type: Map`** +**`Type: Sequence`** -The configuration for delivering spark logs to a long-term storage destination. -Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified -for one cluster. If the conf is given, the logs will be delivered to the destination every -`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while -the destination of executor logs is `$destination/$clusterId/executor`. +Resources for the app. @@ -794,51 +744,62 @@ the destination of executor logs is `$destination/$clusterId/executor`. - Type - Description -- - `dbfs` +- - `app` - Map - - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#clustersnamecluster_log_confdbfs). + - See [\_](#appsnameresourcesapp). -- - `s3` +- - `database` - Map - - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#clustersnamecluster_log_confs3). + - See [\_](#appsnameresourcesdatabase). -- - `volumes` - - Map - - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#clustersnamecluster_log_confvolumes). +- - `description` + - String + - Description of the App Resource. -::: +- - `experiment` + - Map + - See [\_](#appsnameresourcesexperiment). +- - `genie_space` + - Map + - See [\_](#appsnameresourcesgenie_space). -### clusters._name_.cluster_log_conf.dbfs +- - `job` + - Map + - See [\_](#appsnameresourcesjob). -**`Type: Map`** +- - `name` + - String + - Name of the App Resource. -destination needs to be provided. e.g. -`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` +- - `postgres` + - Map + - See [\_](#appsnameresourcespostgres). +- - `secret` + - Map + - See [\_](#appsnameresourcessecret). +- - `serving_endpoint` + - Map + - See [\_](#appsnameresourcesserving_endpoint). -:::list-table - -- - Key - - Type - - Description +- - `sql_warehouse` + - Map + - See [\_](#appsnameresourcessql_warehouse). -- - `destination` - - String - - dbfs destination, e.g. `dbfs:/my/path` +- - `uc_securable` + - Map + - See [\_](#appsnameresourcesuc_securable). ::: -### clusters._name_.cluster_log_conf.s3 +### apps._name_.resources.app **`Type: Map`** -destination and either the region or endpoint need to be provided. e.g. -`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. + @@ -848,43 +809,22 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` - - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. - -- - `destination` - - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. - -- - `enable_encryption` - - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. - -- - `encryption_type` - - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. - -- - `endpoint` - - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. - -- - `kms_key` +- - `name` - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + - -- - `region` +- - `permission` - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - ::: -### clusters._name_.cluster_log_conf.volumes +### apps._name_.resources.database **`Type: Map`** -destination needs to be provided, e.g. -`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` + @@ -894,14 +834,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `database_name` - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` + - + +- - `instance_name` + - String + - + +- - `permission` + - String + - ::: -### clusters._name_.docker_image +### apps._name_.resources.experiment **`Type: Map`** @@ -915,18 +863,18 @@ destination needs to be provided, e.g. - Type - Description -- - `basic_auth` - - Map - - See [\_](#clustersnamedocker_imagebasic_auth). +- - `experiment_id` + - String + - -- - `url` +- - `permission` - String - - URL of the docker image. + - ::: -### clusters._name_.docker_image.basic_auth +### apps._name_.resources.genie_space **`Type: Map`** @@ -940,23 +888,26 @@ destination needs to be provided, e.g. - Type - Description -- - `password` +- - `name` - String - - Password of the user + - -- - `username` +- - `permission` - String - - Name of the user + - + +- - `space_id` + - String + - ::: -### clusters._name_.gcp_attributes +### apps._name_.resources.job **`Type: Map`** -Attributes related to clusters running on Google Cloud Platform. -If not specified at cluster creation, a set of default values will be used. + @@ -966,44 +917,22 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` - - String - - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. - -- - `boot_disk_size` - - Integer - - Boot disk size in GB - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. - -- - `google_service_account` +- - `id` - String - - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. - -- - `local_ssd_count` - - Integer - - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. - -- - `use_preemptible_executors` - - Boolean - - This field is deprecated + - -- - `zone_id` +- - `permission` - String - - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. + - ::: -### clusters._name_.init_scripts +### apps._name_.resources.postgres -**`Type: Sequence`** +**`Type: Map`** -The configuration for storing init scripts. Any number of destinations can be specified. -The scripts are executed sequentially in the order provided. -If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. + @@ -1013,42 +942,26 @@ If `cluster_log_conf` is specified, init script logs are sent to `/ - Type - Description -- - `abfss` - - Map - - Contains the Azure Data Lake Storage destination path. See [\_](#clustersnameinit_scriptsabfss). - -- - `dbfs` - - Map - - This field is deprecated - -- - `file` - - Map - - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#clustersnameinit_scriptsfile). - -- - `gcs` - - Map - - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#clustersnameinit_scriptsgcs). - -- - `s3` - - Map - - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#clustersnameinit_scriptss3). +- - `branch` + - String + - -- - `volumes` - - Map - - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#clustersnameinit_scriptsvolumes). +- - `database` + - String + - -- - `workspace` - - Map - - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#clustersnameinit_scriptsworkspace). +- - `permission` + - String + - ::: -### clusters._name_.init_scripts.abfss +### apps._name_.resources.secret **`Type: Map`** -Contains the Azure Data Lake Storage destination path + @@ -1058,19 +971,26 @@ Contains the Azure Data Lake Storage destination path - Type - Description -- - `destination` +- - `key` - String - - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. + - + +- - `permission` + - String + - Permission to grant on the secret scope. Supported permissions are: "READ", "WRITE", "MANAGE". + +- - `scope` + - String + - ::: -### clusters._name_.init_scripts.file +### apps._name_.resources.serving_endpoint **`Type: Map`** -destination needs to be provided, e.g. -`{ "file": { "destination": "file:/my/local/file.sh" } }` + @@ -1080,19 +1000,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `name` - String - - local file destination, e.g. `file:/my/local/file.sh` + - + +- - `permission` + - String + - ::: -### clusters._name_.init_scripts.gcs +### apps._name_.resources.sql_warehouse **`Type: Map`** -destination needs to be provided, e.g. -`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` + @@ -1102,21 +1025,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `id` - String - - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` + - + +- - `permission` + - String + - ::: -### clusters._name_.init_scripts.s3 +### apps._name_.resources.uc_securable **`Type: Map`** -destination and either the region or endpoint need to be provided. e.g. -`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. + @@ -1126,43 +1050,26 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` - - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. - -- - `destination` - - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. - -- - `enable_encryption` - - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. - -- - `encryption_type` - - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. - -- - `endpoint` +- - `permission` - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - -- - `kms_key` +- - `securable_full_name` - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + - -- - `region` +- - `securable_type` - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - ::: -### clusters._name_.init_scripts.volumes +### apps._name_.telemetry_export_destinations -**`Type: Map`** +**`Type: Sequence`** -destination needs to be provided. e.g. -`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` + @@ -1172,19 +1079,18 @@ destination needs to be provided. e.g. - Type - Description -- - `destination` - - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` +- - `unity_catalog` + - Map + - Unity Catalog Destinations for OTEL telemetry export. See [\_](#appsnametelemetry_export_destinationsunity_catalog). ::: -### clusters._name_.init_scripts.workspace +### apps._name_.telemetry_export_destinations.unity_catalog **`Type: Map`** -destination needs to be provided, e.g. -`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` +Unity Catalog Destinations for OTEL telemetry export. @@ -1194,19 +1100,32 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `logs_table` - String - - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` + - Unity Catalog table for OTEL logs. + +- - `metrics_table` + - String + - Unity Catalog table for OTEL metrics. + +- - `traces_table` + - String + - Unity Catalog table for OTEL traces (spans). ::: -### clusters._name_.lifecycle +## catalogs **`Type: Map`** -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. + +```yaml +catalogs: + : + : +``` :::list-table @@ -1215,51 +1134,54 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. - -::: - - -### clusters._name_.permissions - -**`Type: Sequence`** +- - `comment` + - String + - - +- - `connection_name` + - String + - +- - `grants` + - Sequence + - See [\_](#catalogsnamegrants). +- - `lifecycle` + - Map + - See [\_](#catalogsnamelifecycle). -:::list-table +- - `name` + - String + - -- - Key - - Type - - Description +- - `options` + - Map + - -- - `group_name` - - String +- - `properties` + - Map - -- - `level` +- - `provider_name` - String - -- - `service_principal_name` +- - `share_name` - String - -- - `user_name` +- - `storage_root` - String - ::: -### clusters._name_.workload_type +### catalogs._name_.grants -**`Type: Map`** +**`Type: Sequence`** -Cluster Attributes showing for clusters workload types. + @@ -1269,18 +1191,29 @@ Cluster Attributes showing for clusters workload types. - Type - Description -- - `clients` - - Map - - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#clustersnameworkload_typeclients). +- - `principal` + - String + - The principal (user email address or group name). For deleted principals, `principal` is empty while `principal_id` is populated. + +- - `privileges` + - Sequence + - The privileges assigned to the principal. ::: -### clusters._name_.workload_type.clients +### catalogs._name_.grants.privileges + +**`Type: Sequence`** + +The privileges assigned to the principal. + + +### catalogs._name_.lifecycle **`Type: Map`** -defined what type of clients can use the cluster. E.g. Notebooks, Jobs + @@ -1290,27 +1223,23 @@ defined what type of clients can use the cluster. E.g. Notebooks, Jobs - Type - Description -- - `jobs` - - Boolean - - With jobs set, the cluster can be used for jobs - -- - `notebooks` +- - `prevent_destroy` - Boolean - - With notebooks set, this cluster can be used for notebooks + - Lifecycle setting to prevent the resource from being destroyed. ::: -## dashboards +## clusters **`Type: Map`** -The dashboard resource allows you to manage [AI/BI dashboards](/api/workspace/lakeview/create) in a bundle. For information about AI/BI dashboards, see [_](/dashboards/index.md). +The cluster resource defines an [all-purpose cluster](/api/workspace/clusters/create). ```yaml -dashboards: - : - : +clusters: + : + : ``` @@ -1320,146 +1249,188 @@ dashboards: - Type - Description -- - `create_time` - - String - - The timestamp of when the dashboard was created. +- - `apply_policy_default_values` + - Boolean + - When set to true, fixed and default values from the policy will be used for fields that are omitted. When set to false, only fixed values from the policy will be applied. -- - `dashboard_id` - - String - - UUID identifying the dashboard. +- - `autoscale` + - Map + - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#clustersnameautoscale). -- - `display_name` - - String - - The display name of the dashboard. +- - `autotermination_minutes` + - Integer + - Automatically terminates the cluster after it is inactive for this time in minutes. If not set, this cluster will not be automatically terminated. If specified, the threshold must be between 10 and 10000 minutes. Users can also set this value to 0 to explicitly disable automatic termination. -- - `embed_credentials` - - Boolean - - +- - `aws_attributes` + - Map + - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnameaws_attributes). -- - `etag` - - String - - The etag for the dashboard. Can be optionally provided on updates to ensure that the dashboard has not been modified since the last read. This field is excluded in List Dashboards responses. +- - `azure_attributes` + - Map + - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnameazure_attributes). -- - `file_path` +- - `cluster_log_conf` + - Map + - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#clustersnamecluster_log_conf). + +- - `cluster_name` - String - - + - Cluster name requested by the user. This doesn't have to be unique. If not specified at creation, the cluster name will be an empty string. For job clusters, the cluster name is automatically set based on the job and job run IDs. -- - `lifecycle` +- - `custom_tags` - Map - - 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. See [\_](#dashboardsnamelifecycle). + - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags -- - `lifecycle_state` +- - `data_security_mode` - String - - The state of the dashboard resource. Used for tracking trashed status. + - Data security mode decides what data governance model to use when accessing data from a cluster. The following modes can only be used when `kind = CLASSIC_PREVIEW`. * `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration. * `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`. * `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`. The following modes can be used regardless of `kind`. * `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode. * `SINGLE_USER`: A secure cluster that can only be exclusively used by a single user specified in `single_user_name`. Most programming languages, cluster features and data governance features are available in this mode. * `USER_ISOLATION`: A secure cluster that can be shared by multiple users. Cluster users are fully isolated so that they cannot see each other's data and credentials. Most data governance features are supported in this mode. But programming languages and cluster features might be limited. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: * `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters. * `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters. * `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters. * `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled. -- - `parent_path` +- - `docker_image` + - Map + - See [\_](#clustersnamedocker_image). + +- - `driver_instance_pool_id` - String - - The workspace path of the folder containing the dashboard. Includes leading slash and no trailing slash. This field is excluded in List Dashboards responses. + - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. -- - `path` +- - `driver_node_type_flexibility` + - Map + - Flexible node type configuration for the driver node. See [\_](#clustersnamedriver_node_type_flexibility). + +- - `driver_node_type_id` - String - - The workspace path of the dashboard asset, including the file name. Exported dashboards always have the file extension `.lvdash.json`. This field is excluded in List Dashboards responses. + - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. This field, along with node_type_id, should not be set if virtual_cluster_size is set. If both driver_node_type_id, node_type_id, and virtual_cluster_size are specified, driver_node_type_id and node_type_id take precedence. -- - `permissions` - - Sequence - - See [\_](#dashboardsnamepermissions). +- - `enable_elastic_disk` + - Boolean + - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. -- - `serialized_dashboard` - - Any - - The contents of the dashboard in serialized string form. This field is excluded in List Dashboards responses. Use the [get dashboard API](https://docs.databricks.com/api/workspace/lakeview/get) to retrieve an example response, which includes the `serialized_dashboard` field. This field provides the structure of the JSON string that represents the dashboard's layout and components. +- - `enable_local_disk_encryption` + - Boolean + - Whether to enable LUKS on cluster VMs' local disks -- - `update_time` - - String - - The timestamp of when the dashboard was last updated by the user. This field is excluded in List Dashboards responses. +- - `gcp_attributes` + - Map + - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#clustersnamegcp_attributes). -- - `warehouse_id` +- - `init_scripts` + - Sequence + - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#clustersnameinit_scripts). + +- - `instance_pool_id` - String - - The warehouse ID used to run the dashboard. + - The optional ID of the instance pool to which the cluster belongs. -::: +- - `is_single_node` + - Boolean + - This field can only be used when `kind = CLASSIC_PREVIEW`. When set to true, Databricks will automatically set single node related `custom_tags`, `spark_conf`, and `num_workers` +- - `kind` + - String + - -**Example** +- - `lifecycle` + - Map + - 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. See [\_](#clustersnamelifecycle). -The following example includes and deploys the sample __NYC Taxi Trip Analysis__ dashboard to the Databricks workspace. - -``` yaml -resources: - dashboards: - nyc_taxi_trip_analysis: - display_name: "NYC Taxi Trip Analysis" - file_path: ../src/nyc_taxi_trip_analysis.lvdash.json - warehouse_id: ${var.warehouse_id} -``` -If you use the UI to modify the dashboard, modifications made through the UI are not applied to the dashboard JSON file in the local bundle unless you explicitly update it using `bundle generate`. You can use the `--watch` option to continuously poll and retrieve changes to the dashboard. See [_](/dev-tools/cli/bundle-commands.md#generate). - -In addition, if you attempt to deploy a bundle that contains a dashboard JSON file that is different than the one in the remote workspace, an error will occur. To force the deploy and overwrite the dashboard in the remote workspace with the local one, use the `--force` option. See [_](/dev-tools/cli/bundle-commands.md#deploy). +- - `node_type_id` + - String + - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. -### dashboards._name_.lifecycle +- - `num_workers` + - Integer + - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. -**`Type: Map`** +- - `permissions` + - Sequence + - See [\_](#clustersnamepermissions). -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. +- - `policy_id` + - String + - The ID of the cluster policy used to create the cluster if applicable. +- - `remote_disk_throughput` + - Integer + - If set, what the configurable throughput (in Mb/s) for the remote disk is. Currently only supported for GCP HYPERDISK_BALANCED disks. +- - `runtime_engine` + - String + - -:::list-table +- - `single_user_name` + - String + - Single user name if data_security_mode is `SINGLE_USER` -- - Key - - Type - - Description +- - `spark_conf` + - Map + - An object containing a set of optional, user-specified Spark configuration key-value pairs. Users can also pass in a string of extra JVM options to the driver and the executors via `spark.driver.extraJavaOptions` and `spark.executor.extraJavaOptions` respectively. -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `spark_env_vars` + - Map + - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` -::: +- - `spark_version` + - String + - The Spark version of the cluster, e.g. `3.3.x-scala2.11`. A list of available Spark versions can be retrieved by using the :method:clusters/sparkVersions API call. +- - `ssh_public_keys` + - Sequence + - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. -### dashboards._name_.permissions +- - `total_initial_remote_disk_size` + - Integer + - If set, what the total initial volume size (in GB) of the remote disks should be. Currently only supported for GCP HYPERDISK_BALANCED disks. -**`Type: Sequence`** +- - `use_ml_runtime` + - Boolean + - This field can only be used when `kind = CLASSIC_PREVIEW`. `effective_spark_version` is determined by `spark_version` (DBR release), this field `use_ml_runtime`, and whether `node_type_id` is gpu node or not. - +- - `worker_node_type_flexibility` + - Map + - Flexible node type configuration for worker nodes. See [\_](#clustersnameworker_node_type_flexibility). +- - `workload_type` + - Map + - Cluster Attributes showing for clusters workload types. See [\_](#clustersnameworkload_type). +::: -:::list-table -- - Key - - Type - - Description - -- - `group_name` - - String - - - -- - `level` - - String - - - -- - `service_principal_name` - - String - - - -- - `user_name` - - String - - - -::: +**Example** +The following example creates a cluster named `my_cluster` and sets that as the cluster to use to run the notebook in `my_job`: + +```yaml +bundle: + name: clusters + +resources: + clusters: + my_cluster: + num_workers: 2 + node_type_id: "i3.xlarge" + autoscale: + min_workers: 2 + max_workers: 7 + spark_version: "13.3.x-scala2.12" + spark_conf: + "spark.executor.memory": "2g" + + jobs: + my_job: + tasks: + - task_key: test_task + notebook_task: + notebook_path: "./src/my_notebook.py" +``` -## database_catalogs +### clusters._name_.autoscale **`Type: Map`** - +Parameters needed in order to automatically scale clusters up and down based on load. +Note: autoscaling works best with DB runtime versions 3.0 or later. -```yaml -database_catalogs: - : - : -``` :::list-table @@ -1468,34 +1439,23 @@ database_catalogs: - Type - Description -- - `create_database_if_not_exists` - - Boolean - - - -- - `database_instance_name` - - String - - The name of the DatabaseInstance housing the database. - -- - `database_name` - - String - - The name of the database (in a instance) associated with the catalog. - -- - `lifecycle` - - Map - - 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. See [\_](#database_catalogsnamelifecycle). +- - `max_workers` + - Integer + - The maximum number of workers to which the cluster can scale up when overloaded. Note that `max_workers` must be strictly greater than `min_workers`. -- - `name` - - String - - The name of the catalog in UC. +- - `min_workers` + - Integer + - The minimum number of workers to which the cluster can scale down when underutilized. It is also the initial number of workers the cluster will have after creation. ::: -### database_catalogs._name_.lifecycle +### clusters._name_.aws_attributes **`Type: Map`** -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. +Attributes related to clusters running on Amazon Web Services. +If not specified at cluster creation, a set of default values will be used. @@ -1505,24 +1465,56 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `availability` + - String + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. + +- - `ebs_volume_count` + - Integer + - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. + +- - `ebs_volume_iops` + - Integer + - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + +- - `ebs_volume_size` + - Integer + - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. + +- - `ebs_volume_throughput` + - Integer + - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + +- - `ebs_volume_type` + - String + - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. + +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + +- - `instance_profile_arn` + - String + - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. + +- - `spot_bid_price_percent` + - Integer + - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. + +- - `zone_id` + - String + - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, the zone "auto" will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. ::: -## database_instances +### clusters._name_.azure_attributes **`Type: Map`** -A DatabaseInstance represents a logical Postgres instance, comprised of both compute and storage. +Attributes related to clusters running on Microsoft Azure. +If not specified at cluster creation, a set of default values will be used. -```yaml -database_instances: - : - : -``` :::list-table @@ -1531,54 +1523,30 @@ database_instances: - Type - Description -- - `capacity` - - String - - The sku of the instance. Valid values are "CU_1", "CU_2", "CU_4", "CU_8". - -- - `enable_pg_native_login` - - Boolean - - Whether the instance has PG native password login enabled. Defaults to true. - -- - `enable_readable_secondaries` - - Boolean - - Whether to enable secondaries to serve read-only traffic. Defaults to false. - -- - `lifecycle` - - Map - - 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. See [\_](#database_instancesnamelifecycle). - -- - `name` +- - `availability` - String - - The name of the instance. This is the unique identifier for the instance. + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. -- - `node_count` +- - `first_on_demand` - Integer - - The number of nodes in the instance, composed of 1 primary and 0 or more secondaries. Defaults to 1 primary and 0 secondaries. + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. -- - `parent_instance_ref` +- - `log_analytics_info` - Map - - The ref of the parent instance. This is only available if the instance is child instance. Input: For specifying the parent instance to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. See [\_](#database_instancesnameparent_instance_ref). - -- - `permissions` - - Sequence - - See [\_](#database_instancesnamepermissions). - -- - `retention_window_in_days` - - Integer - - The retention window for the instance. This is the time window in days for which the historical data is retained. The default value is 7 days. Valid values are 2 to 35 days. + - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#clustersnameazure_attributeslog_analytics_info). -- - `stopped` - - Boolean - - Whether the instance is stopped. +- - `spot_bid_max_price` + - Any + - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. ::: -### database_instances._name_.lifecycle +### clusters._name_.azure_attributes.log_analytics_info **`Type: Map`** -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. +Defines values necessary to configure and run Azure Log Analytics agent @@ -1588,21 +1556,26 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `log_analytics_primary_key` + - String + - The primary key for the Azure Log Analytics agent configuration + +- - `log_analytics_workspace_id` + - String + - The workspace ID for the Azure Log Analytics agent configuration ::: -### database_instances._name_.parent_instance_ref +### clusters._name_.cluster_log_conf **`Type: Map`** -The ref of the parent instance. This is only available if the instance is -child instance. -Input: For specifying the parent instance to create a child instance. Optional. -Output: Only populated if provided as input to create a child instance. +The configuration for delivering spark logs to a long-term storage destination. +Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified +for one cluster. If the conf is given, the logs will be delivered to the destination every +`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while +the destination of executor logs is `$destination/$clusterId/executor`. @@ -1612,26 +1585,27 @@ Output: Only populated if provided as input to create a child instance. - Type - Description -- - `branch_time` - - String - - Branch time of the ref database instance. For a parent ref instance, this is the point in time on the parent instance from which the instance was created. For a child ref instance, this is the point in time on the instance from which the child instance was created. Input: For specifying the point in time to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. +- - `dbfs` + - Map + - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#clustersnamecluster_log_confdbfs). -- - `lsn` - - String - - User-specified WAL LSN of the ref database instance. Input: For specifying the WAL LSN to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#clustersnamecluster_log_confs3). -- - `name` - - String - - Name of the ref database instance. +- - `volumes` + - Map + - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#clustersnamecluster_log_confvolumes). ::: -### database_instances._name_.permissions +### clusters._name_.cluster_log_conf.dbfs -**`Type: Sequence`** +**`Type: Map`** - +destination needs to be provided. e.g. +`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` @@ -1641,36 +1615,22 @@ Output: Only populated if provided as input to create a child instance. - Type - Description -- - `group_name` - - String - - - -- - `level` - - String - - - -- - `service_principal_name` - - String - - - -- - `user_name` +- - `destination` - String - - + - dbfs destination, e.g. `dbfs:/my/path` ::: -## experiments +### clusters._name_.cluster_log_conf.s3 **`Type: Map`** -The experiment resource allows you to define [MLflow experiments](/api/workspace/experiments/createexperiment) in a bundle. For information about MLflow experiments, see [_](/mlflow/experiments.md). +destination and either the region or endpoint need to be provided. e.g. +`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. -```yaml -experiments: - : - : -``` :::list-table @@ -1679,65 +1639,43 @@ experiments: - Type - Description -- - `artifact_location` +- - `canned_acl` - String - - Location where artifacts for the experiment are stored. + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. -- - `creation_time` - - Integer - - Creation time - -- - `experiment_id` +- - `destination` - String - - Unique identifier for the experiment. - -- - `last_update_time` - - Integer - - Last update time + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. -- - `lifecycle` - - Map - - 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. See [\_](#experimentsnamelifecycle). +- - `enable_encryption` + - Boolean + - (Optional) Flag to enable server side encryption, `false` by default. -- - `lifecycle_stage` +- - `encryption_type` - String - - Current life cycle stage of the experiment: "active" or "deleted". Deleted experiments are not returned by APIs. + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. -- - `name` +- - `endpoint` - String - - Human readable name that identifies the experiment. + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. -- - `permissions` - - Sequence - - See [\_](#experimentsnamepermissions). +- - `kms_key` + - String + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. -- - `tags` - - Sequence - - Tags: Additional metadata key-value pairs. See [\_](#experimentsnametags). +- - `region` + - String + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. ::: -**Example** - -The following example defines an experiment that all users can view: - -```yaml -resources: - experiments: - experiment: - name: my_ml_experiment - permissions: - - level: CAN_READ - group_name: users - description: MLflow experiment used to track runs -``` - -### experiments._name_.lifecycle +### clusters._name_.cluster_log_conf.volumes **`Type: Map`** -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. +destination needs to be provided, e.g. +`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` @@ -1747,16 +1685,16 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `destination` + - String + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` ::: -### experiments._name_.permissions +### clusters._name_.docker_image -**`Type: Sequence`** +**`Type: Map`** @@ -1768,30 +1706,22 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `group_name` - - String - - - -- - `level` - - String - - - -- - `service_principal_name` - - String - - +- - `basic_auth` + - Map + - See [\_](#clustersnamedocker_imagebasic_auth). -- - `user_name` +- - `url` - String - - + - URL of the docker image. ::: -### experiments._name_.tags +### clusters._name_.docker_image.basic_auth -**`Type: Sequence`** +**`Type: Map`** -Tags: Additional metadata key-value pairs. + @@ -1801,28 +1731,23 @@ Tags: Additional metadata key-value pairs. - Type - Description -- - `key` +- - `password` - String - - The tag key. + - Password of the user -- - `value` +- - `username` - String - - The tag value. + - Name of the user ::: -## jobs +### clusters._name_.driver_node_type_flexibility **`Type: Map`** -The job resource allows you to define [jobs and their corresponding tasks](/api/workspace/jobs/create) in your bundle. For information about jobs, see [_](/jobs/index.md). For a tutorial that uses a Declarative Automation Bundles template to create a job, see [_](/dev-tools/bundles/jobs-tutorial.md). +Flexible node type configuration for the driver node. -```yaml -jobs: - : - : -``` :::list-table @@ -1831,135 +1756,111 @@ jobs: - Type - Description -- - `budget_policy_id` - - String - - The id of the user specified budget policy to use for this job. If not specified, a default budget policy may be applied when creating or modifying the job. See `effective_budget_policy_id` for the budget policy used by this workload. +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. -- - `continuous` - - Map - - An optional continuous property for this job. The continuous property will ensure that there is always one run executing. Only one of `schedule` and `continuous` can be used. See [\_](#jobsnamecontinuous). +::: -- - `deployment` - - Map - - Deployment information for jobs managed by external sources. See [\_](#jobsnamedeployment). -- - `description` - - String - - An optional description for the job. The maximum length is 27700 characters in UTF-8 encoding. +### clusters._name_.gcp_attributes -- - `edit_mode` - - String - - Edit mode of the job. * `UI_LOCKED`: The job is in a locked UI state and cannot be modified. * `EDITABLE`: The job is in an editable state and can be modified. +**`Type: Map`** -- - `email_notifications` - - Map - - An optional set of email addresses that is notified when runs of this job begin or complete as well as when this job is deleted. See [\_](#jobsnameemail_notifications). +Attributes related to clusters running on Google Cloud Platform. +If not specified at cluster creation, a set of default values will be used. -- - `environments` - - Sequence - - A list of task execution environment specifications that can be referenced by serverless tasks of this job. An environment is required to be present for serverless tasks. For serverless notebook tasks, the environment is accessible in the notebook environment panel. For other serverless tasks, the task environment is required to be specified using environment_key in the task settings. See [\_](#jobsnameenvironments). -- - `format` - - String - - This field is deprecated -- - `git_source` - - Map - - An optional specification for a remote Git repository containing the source code used by tasks. Version-controlled source code is supported by notebook, dbt, Python script, and SQL File tasks. If `git_source` is set, these tasks retrieve the file from the remote repository by default. However, this behavior can be overridden by setting `source` to `WORKSPACE` on the task. Note: dbt and SQL File tasks support only version-controlled sources. If dbt or SQL File tasks are used, `git_source` must be defined on the job. See [\_](#jobsnamegit_source). +:::list-table -- - `health` - - Map - - An optional set of health rules that can be defined for this job. See [\_](#jobsnamehealth). +- - Key + - Type + - Description -- - `job_clusters` - - Sequence - - A list of job cluster specifications that can be shared and reused by tasks of this job. Libraries cannot be declared in a shared job cluster. You must declare dependent libraries in task settings. See [\_](#jobsnamejob_clusters). +- - `availability` + - String + - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. -- - `lifecycle` - - Map - - 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. See [\_](#jobsnamelifecycle). +- - `boot_disk_size` + - Integer + - Boot disk size in GB -- - `max_concurrent_runs` +- - `first_on_demand` - Integer - - An optional maximum allowed number of concurrent runs of the job. Set this value if you want to be able to execute multiple runs of the same job concurrently. This is useful for example if you trigger your job on a frequent schedule and want to allow consecutive runs to overlap with each other, or if you want to trigger multiple runs which differ by their input parameters. This setting affects only new runs. For example, suppose the job’s concurrency is 4 and there are 4 concurrent active runs. Then setting the concurrency to 3 won’t kill any of the active runs. However, from then on, new runs are skipped unless there are fewer than 3 active runs. This value cannot exceed 1000. Setting this value to `0` causes all new runs to be skipped. + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. -- - `name` +- - `google_service_account` - String - - An optional name for the job. The maximum length is 4096 bytes in UTF-8 encoding. + - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. -- - `notification_settings` - - Map - - Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this job. See [\_](#jobsnamenotification_settings). +- - `local_ssd_count` + - Integer + - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. -- - `parameters` - - Sequence - - Job-level parameter definitions. See [\_](#jobsnameparameters). +- - `use_preemptible_executors` + - Boolean + - This field is deprecated -- - `performance_target` +- - `zone_id` - String - - The performance mode on a serverless job. This field determines the level of compute performance or cost-efficiency for the run. * `STANDARD`: Enables cost-efficient execution of serverless workloads. * `PERFORMANCE_OPTIMIZED`: Prioritizes fast startup and execution times through rapid scaling and optimized cluster performance. + - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. -- - `permissions` - - Sequence - - See [\_](#jobsnamepermissions). +::: -- - `queue` - - Map - - The queue settings of the job. See [\_](#jobsnamequeue). -- - `run_as` +### clusters._name_.init_scripts + +**`Type: Sequence`** + +The configuration for storing init scripts. Any number of destinations can be specified. +The scripts are executed sequentially in the order provided. +If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. + + + +:::list-table + +- - Key + - Type + - Description + +- - `abfss` - Map - - Write-only setting. Specifies the user or service principal that the job runs as. If not specified, the job runs as the user who created the job. Either `user_name` or `service_principal_name` should be specified. If not, an error is thrown. See [\_](#jobsnamerun_as). + - Contains the Azure Data Lake Storage destination path. See [\_](#clustersnameinit_scriptsabfss). -- - `schedule` +- - `dbfs` - Map - - An optional periodic schedule for this job. The default behavior is that the job only runs when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. See [\_](#jobsnameschedule). + - This field is deprecated -- - `tags` +- - `file` - Map - - A map of tags associated with the job. These are forwarded to the cluster as cluster tags for jobs clusters, and are subject to the same limitations as cluster tags. A maximum of 25 tags can be added to the job. + - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#clustersnameinit_scriptsfile). -- - `tasks` - - Sequence - - A list of task specifications to be executed by this job. It supports up to 1000 elements in write endpoints (:method:jobs/create, :method:jobs/reset, :method:jobs/update, :method:jobs/submit). Read endpoints return only 100 tasks. If more than 100 tasks are available, you can paginate through them using :method:jobs/get. Use the `next_page_token` field at the object root to determine if more results are available. See [\_](#jobsnametasks). +- - `gcs` + - Map + - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#clustersnameinit_scriptsgcs). -- - `timeout_seconds` - - Integer - - An optional timeout applied to each run of this job. A value of `0` means no timeout. +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#clustersnameinit_scriptss3). -- - `trigger` +- - `volumes` - Map - - A configuration to trigger a run when certain conditions are met. The default behavior is that the job runs only when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. See [\_](#jobsnametrigger). + - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#clustersnameinit_scriptsvolumes). -- - `webhook_notifications` +- - `workspace` - Map - - A collection of system notification IDs to notify when runs of this job begin or complete. See [\_](#jobsnamewebhook_notifications). + - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#clustersnameinit_scriptsworkspace). ::: -**Example** - -The following example defines a job with the resource key `hello-job` with one notebook task: - -```yaml -resources: - jobs: - hello-job: - name: hello-job - tasks: - - task_key: hello-task - notebook_task: - notebook_path: ./hello.py -``` - -For information about defining job tasks and overriding job settings, see [_](/dev-tools/bundles/job-task-types.md), [_](/dev-tools/bundles/job-task-override.md), and [_](/dev-tools/bundles/cluster-override.md). - -### jobs._name_.continuous +### clusters._name_.init_scripts.abfss **`Type: Map`** -An optional continuous property for this job. The continuous property will ensure that there is always one run executing. Only one of `schedule` and `continuous` can be used. +Contains the Azure Data Lake Storage destination path @@ -1969,22 +1870,19 @@ An optional continuous property for this job. The continuous property will ensur - Type - Description -- - `pause_status` - - String - - Indicate whether the continuous execution of the job is paused or not. Defaults to UNPAUSED. - -- - `task_retry_mode` +- - `destination` - String - - Indicate whether the continuous job is applying task level retries or not. Defaults to NEVER. + - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. ::: -### jobs._name_.deployment +### clusters._name_.init_scripts.file **`Type: Map`** -Deployment information for jobs managed by external sources. +destination needs to be provided, e.g. +`{ "file": { "destination": "file:/my/local/file.sh" } }` @@ -1994,22 +1892,19 @@ Deployment information for jobs managed by external sources. - Type - Description -- - `kind` - - String - - The kind of deployment that manages the job. * `BUNDLE`: The job is managed by Databricks Asset Bundle. - -- - `metadata_file_path` +- - `destination` - String - - Path of the file that contains deployment metadata. + - local file destination, e.g. `file:/my/local/file.sh` ::: -### jobs._name_.email_notifications +### clusters._name_.init_scripts.gcs **`Type: Map`** -An optional set of email addresses that is notified when runs of this job begin or complete as well as when this job is deleted. +destination needs to be provided, e.g. +`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` @@ -2019,41 +1914,21 @@ An optional set of email addresses that is notified when runs of this job begin - Type - Description -- - `no_alert_for_skipped_runs` - - Boolean - - This field is deprecated - -- - `on_duration_warning_threshold_exceeded` - - Sequence - - A list of email addresses to be notified when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. If no rule for the `RUN_DURATION_SECONDS` metric is specified in the `health` field for the job, notifications are not sent. - -- - `on_failure` - - Sequence - - A list of email addresses to be notified when a run unsuccessfully completes. A run is considered to have completed unsuccessfully if it ends with an `INTERNAL_ERROR` `life_cycle_state` or a `FAILED`, or `TIMED_OUT` result_state. If this is not specified on job creation, reset, or update the list is empty, and notifications are not sent. - -- - `on_start` - - Sequence - - A list of email addresses to be notified when a run begins. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. - -- - `on_streaming_backlog_exceeded` - - Sequence - - A list of email addresses to notify when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. - -- - `on_success` - - Sequence - - A list of email addresses to be notified when a run successfully completes. A run is considered to have completed successfully if it ends with a `TERMINATED` `life_cycle_state` and a `SUCCESS` result_state. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. +- - `destination` + - String + - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` ::: -### jobs._name_.environments +### clusters._name_.init_scripts.s3 -**`Type: Sequence`** +**`Type: Map`** -A list of task execution environment specifications that can be referenced by serverless tasks of this job. -An environment is required to be present for serverless tasks. -For serverless notebook tasks, the environment is accessible in the notebook environment panel. -For other serverless tasks, the task environment is required to be specified using environment_key in the task settings. +destination and either the region or endpoint need to be provided. e.g. +`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. @@ -2063,56 +1938,43 @@ For other serverless tasks, the task environment is required to be specified usi - Type - Description -- - `environment_key` +- - `canned_acl` - String - - The key of an environment. It has to be unique within a job. - -- - `spec` - - Map - - The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines. In this minimal environment spec, only pip dependencies are supported. See [\_](#jobsnameenvironmentsspec). - -::: - - -### jobs._name_.environments.spec - -**`Type: Map`** - -The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines. -In this minimal environment spec, only pip dependencies are supported. - + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. +- - `destination` + - String + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. -:::list-table +- - `enable_encryption` + - Boolean + - (Optional) Flag to enable server side encryption, `false` by default. -- - Key - - Type - - Description +- - `encryption_type` + - String + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. -- - `client` +- - `endpoint` - String - - This field is deprecated + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. -- - `dependencies` - - Sequence - - List of pip dependencies, as supported by the version of pip in this environment. +- - `kms_key` + - String + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. -- - `environment_version` +- - `region` - String - - Required. Environment version used by the environment. Each version comes with a specific Python version and a set of Python packages. The version is a string, consisting of an integer. + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. ::: -### jobs._name_.git_source +### clusters._name_.init_scripts.volumes **`Type: Map`** -An optional specification for a remote Git repository containing the source code used by tasks. Version-controlled source code is supported by notebook, dbt, Python script, and SQL File tasks. - -If `git_source` is set, these tasks retrieve the file from the remote repository by default. However, this behavior can be overridden by setting `source` to `WORKSPACE` on the task. - -Note: dbt and SQL File tasks support only version-controlled sources. If dbt or SQL File tasks are used, `git_source` must be defined on the job. +destination needs to be provided. e.g. +`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` @@ -2122,38 +1984,19 @@ Note: dbt and SQL File tasks support only version-controlled sources. If dbt or - Type - Description -- - `git_branch` - - String - - Name of the branch to be checked out and used by this job. This field cannot be specified in conjunction with git_tag or git_commit. - -- - `git_commit` - - String - - Commit to be checked out and used by this job. This field cannot be specified in conjunction with git_branch or git_tag. - -- - `git_provider` - - String - - Unique identifier of the service used to host the Git repository. The value is case insensitive. - -- - `git_snapshot` - - Map - - Read-only state of the remote repository at the time the job was run. This field is only included on job runs. See [\_](#jobsnamegit_sourcegit_snapshot). - -- - `git_tag` - - String - - Name of the tag to be checked out and used by this job. This field cannot be specified in conjunction with git_branch or git_commit. - -- - `git_url` +- - `destination` - String - - URL of the repository to be cloned by this job. + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` ::: -### jobs._name_.git_source.git_snapshot +### clusters._name_.init_scripts.workspace **`Type: Map`** -Read-only state of the remote repository at the time the job was run. This field is only included on job runs. +destination needs to be provided, e.g. +`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` @@ -2163,18 +2006,18 @@ Read-only state of the remote repository at the time the job was run. This field - Type - Description -- - `used_commit` +- - `destination` - String - - Commit that was used to execute the run. If git_branch was specified, this points to the HEAD of the branch at the time of the run; if git_tag was specified, this points to the commit the tag points to. + - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` ::: -### jobs._name_.health +### clusters._name_.lifecycle **`Type: Map`** -An optional set of health rules that can be defined for this job. +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. @@ -2184,14 +2027,14 @@ An optional set of health rules that can be defined for this job. - Type - Description -- - `rules` - - Sequence - - See [\_](#jobsnamehealthrules). +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.health.rules +### clusters._name_.permissions **`Type: Sequence`** @@ -2205,26 +2048,30 @@ An optional set of health rules that can be defined for this job. - Type - Description -- - `metric` +- - `group_name` - String - - Specifies the health metric that is being evaluated for a particular health rule. * `RUN_DURATION_SECONDS`: Expected total time for a run in seconds. * `STREAMING_BACKLOG_BYTES`: An estimate of the maximum bytes of data waiting to be consumed across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_RECORDS`: An estimate of the maximum offset lag across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_SECONDS`: An estimate of the maximum consumer delay across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_FILES`: An estimate of the maximum number of outstanding files across all streams. This metric is in Public Preview. + - -- - `op` +- - `level` - String - - Specifies the operator used to compare the health metric value with the specified threshold. + - Permission level -- - `value` - - Integer - - Specifies the threshold value that the health metric should obey to satisfy the health rule. +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - ::: -### jobs._name_.job_clusters +### clusters._name_.worker_node_type_flexibility -**`Type: Sequence`** +**`Type: Map`** -A list of job cluster specifications that can be shared and reused by tasks of this job. Libraries cannot be declared in a shared job cluster. You must declare dependent libraries in task settings. +Flexible node type configuration for worker nodes. @@ -2234,22 +2081,18 @@ A list of job cluster specifications that can be shared and reused by tasks of t - Type - Description -- - `job_cluster_key` - - String - - A unique name for the job cluster. This field is required and must be unique within the job. `JobTaskSettings` may refer to this field to determine which cluster to launch for the task execution. - -- - `new_cluster` - - Map - - If new_cluster, a description of a cluster that is created for each task. See [\_](#jobsnamejob_clustersnew_cluster). +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. ::: -### jobs._name_.job_clusters.new_cluster +### clusters._name_.workload_type **`Type: Map`** -If new_cluster, a description of a cluster that is created for each task. +Cluster Attributes showing for clusters workload types. @@ -2259,143 +2102,145 @@ If new_cluster, a description of a cluster that is created for each task. - Type - Description -- - `apply_policy_default_values` - - Boolean - - When set to true, fixed and default values from the policy will be used for fields that are omitted. When set to false, only fixed values from the policy will be applied. - -- - `autoscale` +- - `clients` - Map - - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#jobsnamejob_clustersnew_clusterautoscale). - -- - `autotermination_minutes` - - Integer - - Automatically terminates the cluster after it is inactive for this time in minutes. If not set, this cluster will not be automatically terminated. If specified, the threshold must be between 10 and 10000 minutes. Users can also set this value to 0 to explicitly disable automatic termination. + - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#clustersnameworkload_typeclients). -- - `aws_attributes` - - Map - - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clusteraws_attributes). +::: -- - `azure_attributes` - - Map - - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clusterazure_attributes). -- - `cluster_log_conf` - - Map - - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_conf). +### clusters._name_.workload_type.clients -- - `cluster_name` - - String - - Cluster name requested by the user. This doesn't have to be unique. If not specified at creation, the cluster name will be an empty string. For job clusters, the cluster name is automatically set based on the job and job run IDs. +**`Type: Map`** -- - `custom_tags` - - Map - - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags +defined what type of clients can use the cluster. E.g. Notebooks, Jobs -- - `data_security_mode` - - String - - Data security mode decides what data governance model to use when accessing data from a cluster. The following modes can only be used when `kind = CLASSIC_PREVIEW`. * `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration. * `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`. * `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`. The following modes can be used regardless of `kind`. * `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode. * `SINGLE_USER`: A secure cluster that can only be exclusively used by a single user specified in `single_user_name`. Most programming languages, cluster features and data governance features are available in this mode. * `USER_ISOLATION`: A secure cluster that can be shared by multiple users. Cluster users are fully isolated so that they cannot see each other's data and credentials. Most data governance features are supported in this mode. But programming languages and cluster features might be limited. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: * `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters. * `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters. * `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters. * `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled. -- - `docker_image` - - Map - - See [\_](#jobsnamejob_clustersnew_clusterdocker_image). -- - `driver_instance_pool_id` - - String - - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. +:::list-table -- - `driver_node_type_id` - - String - - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. This field, along with node_type_id, should not be set if virtual_cluster_size is set. If both driver_node_type_id, node_type_id, and virtual_cluster_size are specified, driver_node_type_id and node_type_id take precedence. +- - Key + - Type + - Description -- - `enable_elastic_disk` +- - `jobs` - Boolean - - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. This feature requires specific AWS permissions to function correctly - refer to the User Guide for more details. + - With jobs set, the cluster can be used for jobs -- - `enable_local_disk_encryption` +- - `notebooks` - Boolean - - Whether to enable LUKS on cluster VMs' local disks + - With notebooks set, this cluster can be used for notebooks -- - `gcp_attributes` - - Map - - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clustergcp_attributes). +::: -- - `init_scripts` - - Sequence - - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#jobsnamejob_clustersnew_clusterinit_scripts). -- - `instance_pool_id` - - String - - The optional ID of the instance pool to which the cluster belongs. +## dashboards -- - `is_single_node` - - Boolean - - This field can only be used when `kind = CLASSIC_PREVIEW`. When set to true, Databricks will automatically set single node related `custom_tags`, `spark_conf`, and `num_workers` +**`Type: Map`** -- - `kind` - - String - - +The dashboard resource allows you to manage [AI/BI dashboards](/api/workspace/lakeview/create) in a bundle. For information about AI/BI dashboards, see [_](/dashboards/index.md). -- - `node_type_id` - - String - - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. +```yaml +dashboards: + : + : +``` -- - `num_workers` - - Integer - - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. -- - `policy_id` +:::list-table + +- - Key + - Type + - Description + +- - `create_time` - String - - The ID of the cluster policy used to create the cluster if applicable. + - The timestamp of when the dashboard was created. -- - `remote_disk_throughput` - - Integer - - If set, what the configurable throughput (in Mb/s) for the remote disk is. Currently only supported for GCP HYPERDISK_BALANCED disks. +- - `dashboard_id` + - String + - UUID identifying the dashboard. -- - `runtime_engine` +- - `dataset_catalog` + - String + - Sets the default catalog for all datasets in this dashboard. When set, this overrides the catalog specified in individual dataset definitions. + +- - `dataset_schema` - String + - Sets the default schema for all datasets in this dashboard. When set, this overrides the schema specified in individual dataset definitions. + +- - `display_name` + - String + - The display name of the dashboard. + +- - `embed_credentials` + - Boolean - -- - `single_user_name` +- - `etag` - String - - Single user name if data_security_mode is `SINGLE_USER` + - The etag for the dashboard. Can be optionally provided on updates to ensure that the dashboard has not been modified since the last read. This field is excluded in List Dashboards responses. -- - `spark_conf` - - Map - - An object containing a set of optional, user-specified Spark configuration key-value pairs. Users can also pass in a string of extra JVM options to the driver and the executors via `spark.driver.extraJavaOptions` and `spark.executor.extraJavaOptions` respectively. +- - `file_path` + - String + - -- - `spark_env_vars` +- - `lifecycle` - Map - - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` + - 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. See [\_](#dashboardsnamelifecycle). -- - `spark_version` +- - `lifecycle_state` - String - - The Spark version of the cluster, e.g. `3.3.x-scala2.11`. A list of available Spark versions can be retrieved by using the :method:clusters/sparkVersions API call. + - The state of the dashboard resource. Used for tracking trashed status. -- - `ssh_public_keys` +- - `parent_path` + - String + - The workspace path of the folder containing the dashboard. Includes leading slash and no trailing slash. This field is excluded in List Dashboards responses. + +- - `path` + - String + - The workspace path of the dashboard asset, including the file name. Exported dashboards always have the file extension `.lvdash.json`. This field is excluded in List Dashboards responses. + +- - `permissions` - Sequence - - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. + - See [\_](#dashboardsnamepermissions). -- - `total_initial_remote_disk_size` - - Integer - - If set, what the total initial volume size (in GB) of the remote disks should be. Currently only supported for GCP HYPERDISK_BALANCED disks. +- - `serialized_dashboard` + - Any + - The contents of the dashboard in serialized string form. This field is excluded in List Dashboards responses. Use the [get dashboard API](https://docs.databricks.com/api/workspace/lakeview/get) to retrieve an example response, which includes the `serialized_dashboard` field. This field provides the structure of the JSON string that represents the dashboard's layout and components. -- - `use_ml_runtime` - - Boolean - - This field can only be used when `kind = CLASSIC_PREVIEW`. `effective_spark_version` is determined by `spark_version` (DBR release), this field `use_ml_runtime`, and whether `node_type_id` is gpu node or not. +- - `update_time` + - String + - The timestamp of when the dashboard was last updated by the user. This field is excluded in List Dashboards responses. -- - `workload_type` - - Map - - Cluster Attributes showing for clusters workload types. See [\_](#jobsnamejob_clustersnew_clusterworkload_type). +- - `warehouse_id` + - String + - The warehouse ID used to run the dashboard. ::: -### jobs._name_.job_clusters.new_cluster.autoscale +**Example** + +The following example includes and deploys the sample __NYC Taxi Trip Analysis__ dashboard to the Databricks workspace. + +``` yaml +resources: + dashboards: + nyc_taxi_trip_analysis: + display_name: "NYC Taxi Trip Analysis" + file_path: ../src/nyc_taxi_trip_analysis.lvdash.json + warehouse_id: ${var.warehouse_id} +``` +If you use the UI to modify the dashboard, modifications made through the UI are not applied to the dashboard JSON file in the local bundle unless you explicitly update it using `bundle generate`. You can use the `--watch` option to continuously poll and retrieve changes to the dashboard. See [_](/dev-tools/cli/bundle-commands.md#generate). + +In addition, if you attempt to deploy a bundle that contains a dashboard JSON file that is different than the one in the remote workspace, an error will occur. To force the deploy and overwrite the dashboard in the remote workspace with the local one, use the `--force` option. See [_](/dev-tools/cli/bundle-commands.md#deploy). + +### dashboards._name_.lifecycle **`Type: Map`** -Parameters needed in order to automatically scale clusters up and down based on load. -Note: autoscaling works best with DB runtime versions 3.0 or later. +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. @@ -2405,23 +2250,18 @@ Note: autoscaling works best with DB runtime versions 3.0 or later. - Type - Description -- - `max_workers` - - Integer - - The maximum number of workers to which the cluster can scale up when overloaded. Note that `max_workers` must be strictly greater than `min_workers`. - -- - `min_workers` - - Integer - - The minimum number of workers to which the cluster can scale down when underutilized. It is also the initial number of workers the cluster will have after creation. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.job_clusters.new_cluster.aws_attributes +### dashboards._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -Attributes related to clusters running on Amazon Web Services. -If not specified at cluster creation, a set of default values will be used. + @@ -2431,56 +2271,36 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` +- - `group_name` - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. - -- - `ebs_volume_count` - - Integer - - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. + - The name of the group that has the permission set in level. -- - `ebs_volume_iops` - - Integer - - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. - -- - `ebs_volume_size` - - Integer - - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. - -- - `ebs_volume_throughput` - - Integer - - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. - -- - `ebs_volume_type` +- - `level` - String - - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + - The allowed permission for user, group, service principal defined for this permission. -- - `instance_profile_arn` +- - `service_principal_name` - String - - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. - -- - `spot_bid_price_percent` - - Integer - - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. + - The name of the service principal that has the permission set in level. -- - `zone_id` +- - `user_name` - String - - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, a default zone will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. + - The name of the user that has the permission set in level. ::: -### jobs._name_.job_clusters.new_cluster.azure_attributes +## database_catalogs **`Type: Map`** -Attributes related to clusters running on Microsoft Azure. -If not specified at cluster creation, a set of default values will be used. + +```yaml +database_catalogs: + : + : +``` :::list-table @@ -2489,30 +2309,34 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` +- - `create_database_if_not_exists` + - Boolean + - + +- - `database_instance_name` - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. + - The name of the DatabaseInstance housing the database. -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. +- - `database_name` + - String + - The name of the database (in a instance) associated with the catalog. -- - `log_analytics_info` +- - `lifecycle` - Map - - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#jobsnamejob_clustersnew_clusterazure_attributeslog_analytics_info). + - 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. See [\_](#database_catalogsnamelifecycle). -- - `spot_bid_max_price` - - Any - - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. +- - `name` + - String + - The name of the catalog in UC. ::: -### jobs._name_.job_clusters.new_cluster.azure_attributes.log_analytics_info +### database_catalogs._name_.lifecycle **`Type: Map`** -Defines values necessary to configure and run Azure Log Analytics agent +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. @@ -2522,27 +2346,24 @@ Defines values necessary to configure and run Azure Log Analytics agent - Type - Description -- - `log_analytics_primary_key` - - String - - The primary key for the Azure Log Analytics agent configuration - -- - `log_analytics_workspace_id` - - String - - The workspace ID for the Azure Log Analytics agent configuration +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.job_clusters.new_cluster.cluster_log_conf +## database_instances **`Type: Map`** -The configuration for delivering spark logs to a long-term storage destination. -Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified -for one cluster. If the conf is given, the logs will be delivered to the destination every -`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while -the destination of executor logs is `$destination/$clusterId/executor`. +A DatabaseInstance represents a logical Postgres instance, comprised of both compute and storage. +```yaml +database_instances: + : + : +``` :::list-table @@ -2551,51 +2372,62 @@ the destination of executor logs is `$destination/$clusterId/executor`. - Type - Description -- - `dbfs` - - Map - - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confdbfs). +- - `capacity` + - String + - The sku of the instance. Valid values are "CU_1", "CU_2", "CU_4", "CU_8". -- - `s3` - - Map - - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confs3). +- - `custom_tags` + - Sequence + - Custom tags associated with the instance. This field is only included on create and update responses. See [\_](#database_instancesnamecustom_tags). -- - `volumes` - - Map - - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confvolumes). +- - `enable_pg_native_login` + - Boolean + - Whether to enable PG native password login on the instance. Defaults to false. -::: +- - `enable_readable_secondaries` + - Boolean + - Whether to enable secondaries to serve read-only traffic. Defaults to false. +- - `lifecycle` + - Map + - 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. See [\_](#database_instancesnamelifecycle). -### jobs._name_.job_clusters.new_cluster.cluster_log_conf.dbfs +- - `name` + - String + - The name of the instance. This is the unique identifier for the instance. -**`Type: Map`** +- - `node_count` + - Integer + - The number of nodes in the instance, composed of 1 primary and 0 or more secondaries. Defaults to 1 primary and 0 secondaries. This field is input only, see effective_node_count for the output. -destination needs to be provided. e.g. -`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` +- - `parent_instance_ref` + - Map + - The ref of the parent instance. This is only available if the instance is child instance. Input: For specifying the parent instance to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. See [\_](#database_instancesnameparent_instance_ref). +- - `permissions` + - Sequence + - See [\_](#database_instancesnamepermissions). +- - `retention_window_in_days` + - Integer + - The retention window for the instance. This is the time window in days for which the historical data is retained. The default value is 7 days. Valid values are 2 to 35 days. -:::list-table - -- - Key - - Type - - Description +- - `stopped` + - Boolean + - Whether to stop the instance. An input only param, see effective_stopped for the output. -- - `destination` +- - `usage_policy_id` - String - - dbfs destination, e.g. `dbfs:/my/path` + - The desired usage policy to associate with the instance. ::: -### jobs._name_.job_clusters.new_cluster.cluster_log_conf.s3 +### database_instances._name_.custom_tags -**`Type: Map`** +**`Type: Sequence`** -destination and either the region or endpoint need to be provided. e.g. -`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. +Custom tags associated with the instance. This field is only included on create and update responses. @@ -2605,43 +2437,22 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` - - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. - -- - `destination` - - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. - -- - `enable_encryption` - - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. - -- - `encryption_type` - - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. - -- - `endpoint` - - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. - -- - `kms_key` +- - `key` - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + - The key of the custom tag. -- - `region` +- - `value` - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - The value of the custom tag. ::: -### jobs._name_.job_clusters.new_cluster.cluster_log_conf.volumes +### database_instances._name_.lifecycle **`Type: Map`** -destination needs to be provided, e.g. -`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` +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. @@ -2651,18 +2462,21 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` - - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.job_clusters.new_cluster.docker_image +### database_instances._name_.parent_instance_ref **`Type: Map`** - +The ref of the parent instance. This is only available if the instance is +child instance. +Input: For specifying the parent instance to create a child instance. Optional. +Output: Only populated if provided as input to create a child instance. @@ -2672,48 +2486,26 @@ destination needs to be provided, e.g. - Type - Description -- - `basic_auth` - - Map - - See [\_](#jobsnamejob_clustersnew_clusterdocker_imagebasic_auth). - -- - `url` +- - `branch_time` - String - - URL of the docker image. - -::: - - -### jobs._name_.job_clusters.new_cluster.docker_image.basic_auth - -**`Type: Map`** - - - - - -:::list-table - -- - Key - - Type - - Description + - Branch time of the ref database instance. For a parent ref instance, this is the point in time on the parent instance from which the instance was created. For a child ref instance, this is the point in time on the instance from which the child instance was created. Input: For specifying the point in time to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. -- - `password` +- - `lsn` - String - - Password of the user + - User-specified WAL LSN of the ref database instance. Input: For specifying the WAL LSN to create a child instance. Optional. Output: Only populated if provided as input to create a child instance. -- - `username` +- - `name` - String - - Name of the user + - Name of the ref database instance. ::: -### jobs._name_.job_clusters.new_cluster.gcp_attributes +### database_instances._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -Attributes related to clusters running on Google Cloud Platform. -If not specified at cluster creation, a set of default values will be used. + @@ -2723,45 +2515,36 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` +- - `group_name` - String - - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. - -- - `boot_disk_size` - - Integer - - Boot disk size in GB - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + - The name of the group that has the permission set in level. -- - `google_service_account` +- - `level` - String - - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. - -- - `local_ssd_count` - - Integer - - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. + - The allowed permission for user, group, service principal defined for this permission. -- - `use_preemptible_executors` - - Boolean - - This field is deprecated +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. -- - `zone_id` +- - `user_name` - String - - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. + - The name of the user that has the permission set in level. ::: -### jobs._name_.job_clusters.new_cluster.init_scripts +## experiments -**`Type: Sequence`** +**`Type: Map`** -The configuration for storing init scripts. Any number of destinations can be specified. -The scripts are executed sequentially in the order provided. -If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. +The experiment resource allows you to define [MLflow experiments](/api/workspace/experiments/createexperiment) in a bundle. For information about MLflow experiments, see [_](/mlflow/experiments.md). +```yaml +experiments: + : + : +``` :::list-table @@ -2770,42 +2553,49 @@ If `cluster_log_conf` is specified, init script logs are sent to `/ - Type - Description -- - `abfss` - - Map - - Contains the Azure Data Lake Storage destination path. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsabfss). +- - `artifact_location` + - String + - Location where all artifacts for the experiment are stored. If not provided, the remote server will select an appropriate default. -- - `dbfs` +- - `lifecycle` - Map - - This field is deprecated + - 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. See [\_](#experimentsnamelifecycle). -- - `file` - - Map - - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsfile). +- - `name` + - String + - Experiment name. -- - `gcs` - - Map - - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsgcs). +- - `permissions` + - Sequence + - See [\_](#experimentsnamepermissions). -- - `s3` - - Map - - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptss3). +- - `tags` + - Sequence + - A collection of tags to set on the experiment. Maximum tag size and number of tags per request depends on the storage backend. All storage backends are guaranteed to support tag keys up to 250 bytes in size and tag values up to 5000 bytes in size. All storage backends are also guaranteed to support up to 20 tags per request. See [\_](#experimentsnametags). -- - `volumes` - - Map - - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsvolumes). +::: -- - `workspace` - - Map - - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsworkspace). -::: +**Example** +The following example defines an experiment that all users can view: + +```yaml +resources: + experiments: + experiment: + name: my_ml_experiment + permissions: + - level: CAN_READ + group_name: users + description: MLflow experiment used to track runs +``` -### jobs._name_.job_clusters.new_cluster.init_scripts.abfss +### experiments._name_.lifecycle **`Type: Map`** -Contains the Azure Data Lake Storage destination path +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. @@ -2815,19 +2605,18 @@ Contains the Azure Data Lake Storage destination path - Type - Description -- - `destination` - - String - - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.job_clusters.new_cluster.init_scripts.file +### experiments._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -destination needs to be provided, e.g. -`{ "file": { "destination": "file:/my/local/file.sh" } }` + @@ -2837,19 +2626,33 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `group_name` - String - - local file destination, e.g. `file:/my/local/file.sh` + - + +- - `level` + - String + - Permission level + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - ::: -### jobs._name_.job_clusters.new_cluster.init_scripts.gcs +### experiments._name_.tags -**`Type: Map`** +**`Type: Sequence`** -destination needs to be provided, e.g. -`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` +A collection of tags to set on the experiment. Maximum tag size and number of tags per request +depends on the storage backend. All storage backends are guaranteed to support tag keys up +to 250 bytes in size and tag values up to 5000 bytes in size. All storage backends are also +guaranteed to support up to 20 tags per request. @@ -2859,22 +2662,28 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `key` - String - - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` + - The tag key. + +- - `value` + - String + - The tag value. ::: -### jobs._name_.job_clusters.new_cluster.init_scripts.s3 +## external_locations **`Type: Map`** -destination and either the region or endpoint need to be provided. e.g. -`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. + +```yaml +external_locations: + : + : +``` :::list-table @@ -2883,43 +2692,62 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` +- - `comment` - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. + - -- - `destination` +- - `credential_name` - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. + - -- - `enable_encryption` +- - `enable_file_events` - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. + - -- - `encryption_type` - - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. +- - `encryption_details` + - Map + - Encryption options that apply to clients connecting to cloud storage. See [\_](#external_locationsnameencryption_details). -- - `endpoint` - - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. +- - `fallback` + - Boolean + - -- - `kms_key` +- - `file_event_queue` + - Map + - See [\_](#external_locationsnamefile_event_queue). + +- - `grants` + - Sequence + - See [\_](#external_locationsnamegrants). + +- - `lifecycle` + - Map + - See [\_](#external_locationsnamelifecycle). + +- - `name` - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + - -- - `region` +- - `read_only` + - Boolean + - + +- - `skip_validation` + - Boolean + - + +- - `url` - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - ::: -### jobs._name_.job_clusters.new_cluster.init_scripts.volumes +### external_locations._name_.encryption_details **`Type: Map`** -destination needs to be provided. e.g. -`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` +Encryption options that apply to clients connecting to cloud storage. @@ -2929,19 +2757,18 @@ destination needs to be provided. e.g. - Type - Description -- - `destination` - - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` +- - `sse_encryption_details` + - Map + - Server-Side Encryption properties for clients communicating with AWS s3. See [\_](#external_locationsnameencryption_detailssse_encryption_details). ::: -### jobs._name_.job_clusters.new_cluster.init_scripts.workspace +### external_locations._name_.encryption_details.sse_encryption_details **`Type: Map`** -destination needs to be provided, e.g. -`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` +Server-Side Encryption properties for clients communicating with AWS s3. @@ -2951,18 +2778,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `algorithm` - String - - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` + - SSE algorithm to use for encrypting S3 objects + +- - `aws_kms_key_arn` + - String + - ::: -### jobs._name_.job_clusters.new_cluster.workload_type +### external_locations._name_.file_event_queue **`Type: Map`** -Cluster Attributes showing for clusters workload types. + @@ -2972,18 +2803,38 @@ Cluster Attributes showing for clusters workload types. - Type - Description -- - `clients` +- - `managed_aqs` - Map - - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#jobsnamejob_clustersnew_clusterworkload_typeclients). + - See [\_](#external_locationsnamefile_event_queuemanaged_aqs). + +- - `managed_pubsub` + - Map + - See [\_](#external_locationsnamefile_event_queuemanaged_pubsub). + +- - `managed_sqs` + - Map + - See [\_](#external_locationsnamefile_event_queuemanaged_sqs). + +- - `provided_aqs` + - Map + - See [\_](#external_locationsnamefile_event_queueprovided_aqs). + +- - `provided_pubsub` + - Map + - See [\_](#external_locationsnamefile_event_queueprovided_pubsub). + +- - `provided_sqs` + - Map + - See [\_](#external_locationsnamefile_event_queueprovided_sqs). ::: -### jobs._name_.job_clusters.new_cluster.workload_type.clients +### external_locations._name_.file_event_queue.managed_aqs **`Type: Map`** -defined what type of clients can use the cluster. E.g. Notebooks, Jobs + @@ -2993,22 +2844,26 @@ defined what type of clients can use the cluster. E.g. Notebooks, Jobs - Type - Description -- - `jobs` - - Boolean - - With jobs set, the cluster can be used for jobs +- - `queue_url` + - String + - -- - `notebooks` - - Boolean - - With notebooks set, this cluster can be used for notebooks +- - `resource_group` + - String + - + +- - `subscription_id` + - String + - ::: -### jobs._name_.lifecycle +### external_locations._name_.file_event_queue.managed_pubsub **`Type: Map`** -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. + @@ -3018,18 +2873,18 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `subscription_name` + - String + - ::: -### jobs._name_.notification_settings +### external_locations._name_.file_event_queue.managed_sqs **`Type: Map`** -Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this job. + @@ -3039,22 +2894,18 @@ Optional notification settings that are used when sending notifications to each - Type - Description -- - `no_alert_for_canceled_runs` - - Boolean - - If true, do not send notifications to recipients specified in `on_failure` if the run is canceled. - -- - `no_alert_for_skipped_runs` - - Boolean - - If true, do not send notifications to recipients specified in `on_failure` if the run is skipped. +- - `queue_url` + - String + - ::: -### jobs._name_.parameters +### external_locations._name_.file_event_queue.provided_aqs -**`Type: Sequence`** +**`Type: Map`** -Job-level parameter definitions + @@ -3064,20 +2915,24 @@ Job-level parameter definitions - Type - Description -- - `default` +- - `queue_url` - String - - Default value of the parameter. + - -- - `name` +- - `resource_group` - String - - The name of the defined parameter. May only contain alphanumeric characters, `_`, `-`, and `.` + - + +- - `subscription_id` + - String + - ::: -### jobs._name_.permissions +### external_locations._name_.file_event_queue.provided_pubsub -**`Type: Sequence`** +**`Type: Map`** @@ -3089,30 +2944,18 @@ Job-level parameter definitions - Type - Description -- - `group_name` - - String - - - -- - `level` - - String - - - -- - `service_principal_name` - - String - - - -- - `user_name` +- - `subscription_name` - String - ::: -### jobs._name_.queue +### external_locations._name_.file_event_queue.provided_sqs **`Type: Map`** -The queue settings of the job. + @@ -3122,20 +2965,18 @@ The queue settings of the job. - Type - Description -- - `enabled` - - Boolean - - If true, enable queueing for the job. This is a required field. +- - `queue_url` + - String + - ::: -### jobs._name_.run_as +### external_locations._name_.grants -**`Type: Map`** +**`Type: Sequence`** -Write-only setting. Specifies the user or service principal that the job runs as. If not specified, the job runs as the user who created the job. -Either `user_name` or `service_principal_name` should be specified. If not, an error is thrown. @@ -3145,22 +2986,29 @@ Either `user_name` or `service_principal_name` should be specified. If not, an e - Type - Description -- - `service_principal_name` +- - `principal` - String - - The application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. + - The principal (user email address or group name). For deleted principals, `principal` is empty while `principal_id` is populated. -- - `user_name` - - String - - The email of an active workspace user. Non-admin users can only set this field to their own email. +- - `privileges` + - Sequence + - The privileges assigned to the principal. ::: -### jobs._name_.schedule +### external_locations._name_.grants.privileges + +**`Type: Sequence`** + +The privileges assigned to the principal. + + +### external_locations._name_.lifecycle **`Type: Map`** -An optional periodic schedule for this job. The default behavior is that the job only runs when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. + @@ -3170,29 +3018,24 @@ An optional periodic schedule for this job. The default behavior is that the job - Type - Description -- - `pause_status` - - String - - Indicate whether this schedule is paused or not. - -- - `quartz_cron_expression` - - String - - A Cron expression using Quartz syntax that describes the schedule for a job. See [Cron Trigger](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) for details. This field is required. - -- - `timezone_id` - - String - - A Java timezone ID. The schedule for a job is resolved with respect to this timezone. See [Java TimeZone](https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html) for details. This field is required. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.tasks +## jobs -**`Type: Sequence`** +**`Type: Map`** -A list of task specifications to be executed by this job. -It supports up to 1000 elements in write endpoints (:method:jobs/create, :method:jobs/reset, :method:jobs/update, :method:jobs/submit). -Read endpoints return only 100 tasks. If more than 100 tasks are available, you can paginate through them using :method:jobs/get. Use the `next_page_token` field at the object root to determine if more results are available. +The job resource allows you to define [jobs and their corresponding tasks](/api/workspace/jobs/create) in your bundle. For information about jobs, see [_](/jobs/index.md). For a tutorial that uses a Declarative Automation Bundles template to create a job, see [_](/dev-tools/bundles/jobs-tutorial.md). +```yaml +jobs: + : + : +``` :::list-table @@ -3201,143 +3044,135 @@ Read endpoints return only 100 tasks. If more than 100 tasks are available, you - Type - Description -- - `clean_rooms_notebook_task` - - Map - - The task runs a [clean rooms](https://docs.databricks.com/en/clean-rooms/index.html) notebook when the `clean_rooms_notebook_task` field is present. See [\_](#jobsnametasksclean_rooms_notebook_task). - -- - `condition_task` - - Map - - The task evaluates a condition that can be used to control the execution of other tasks when the `condition_task` field is present. The condition task does not require a cluster to execute and does not support retries or notifications. See [\_](#jobsnametaskscondition_task). +- - `budget_policy_id` + - String + - The id of the user specified budget policy to use for this job. If not specified, a default budget policy may be applied when creating or modifying the job. See `effective_budget_policy_id` for the budget policy used by this workload. -- - `dashboard_task` +- - `continuous` - Map - - The task refreshes a dashboard and sends a snapshot to subscribers. See [\_](#jobsnametasksdashboard_task). + - An optional continuous property for this job. The continuous property will ensure that there is always one run executing. Only one of `schedule` and `continuous` can be used. See [\_](#jobsnamecontinuous). -- - `dbt_task` +- - `deployment` - Map - - The task runs one or more dbt commands when the `dbt_task` field is present. The dbt task requires both Databricks SQL and the ability to use a serverless or a pro SQL warehouse. See [\_](#jobsnametasksdbt_task). - -- - `depends_on` - - Sequence - - An optional array of objects specifying the dependency graph of the task. All tasks specified in this field must complete before executing this task. The task will run only if the `run_if` condition is true. The key is `task_key`, and the value is the name assigned to the dependent task. See [\_](#jobsnametasksdepends_on). + - Deployment information for jobs managed by external sources. See [\_](#jobsnamedeployment). - - `description` - String - - An optional description for this task. + - An optional description for the job. The maximum length is 27700 characters in UTF-8 encoding. -- - `disable_auto_optimization` - - Boolean - - An option to disable auto optimization in serverless +- - `edit_mode` + - String + - Edit mode of the job. * `UI_LOCKED`: The job is in a locked UI state and cannot be modified. * `EDITABLE`: The job is in an editable state and can be modified. - - `email_notifications` - Map - - An optional set of email addresses that is notified when runs of this task begin or complete as well as when this task is deleted. The default behavior is to not send any emails. See [\_](#jobsnametasksemail_notifications). + - An optional set of email addresses that is notified when runs of this job begin or complete as well as when this job is deleted. See [\_](#jobsnameemail_notifications). -- - `environment_key` - - String - - The key that references an environment spec in a job. This field is required for Python script, Python wheel and dbt tasks when using serverless compute. +- - `environments` + - Sequence + - A list of task execution environment specifications that can be referenced by serverless tasks of this job. For serverless notebook tasks, if the environment_key is not specified, the notebook environment will be used if present. If a jobs environment is specified, it will override the notebook environment. For other serverless tasks, the task environment is required to be specified using environment_key in the task settings. See [\_](#jobsnameenvironments). -- - `existing_cluster_id` +- - `format` - String - - If existing_cluster_id, the ID of an existing cluster that is used for all runs. When running jobs or tasks on an existing cluster, you may need to manually restart the cluster if it stops responding. We suggest running jobs and tasks on new clusters for greater reliability + - This field is deprecated -- - `for_each_task` +- - `git_source` - Map - - The task executes a nested task for every input provided when the `for_each_task` field is present. See [\_](#jobsnametasksfor_each_task). + - An optional specification for a remote Git repository containing the source code used by tasks. Version-controlled source code is supported by notebook, dbt, Python script, and SQL File tasks. If `git_source` is set, these tasks retrieve the file from the remote repository by default. However, this behavior can be overridden by setting `source` to `WORKSPACE` on the task. Note: dbt and SQL File tasks support only version-controlled sources. If dbt or SQL File tasks are used, `git_source` must be defined on the job. See [\_](#jobsnamegit_source). - - `health` - Map - - An optional set of health rules that can be defined for this job. See [\_](#jobsnametaskshealth). - -- - `job_cluster_key` - - String - - If job_cluster_key, this task is executed reusing the cluster specified in `job.settings.job_clusters`. + - An optional set of health rules that can be defined for this job. See [\_](#jobsnamehealth). -- - `libraries` +- - `job_clusters` - Sequence - - An optional list of libraries to be installed on the cluster. The default value is an empty list. See [\_](#jobsnametaskslibraries). + - A list of job cluster specifications that can be shared and reused by tasks of this job. Libraries cannot be declared in a shared job cluster. You must declare dependent libraries in task settings. See [\_](#jobsnamejob_clusters). -- - `max_retries` - - Integer - - An optional maximum number of times to retry an unsuccessful run. A run is considered to be unsuccessful if it completes with the `FAILED` result_state or `INTERNAL_ERROR` `life_cycle_state`. The value `-1` means to retry indefinitely and the value `0` means to never retry. +- - `lifecycle` + - Map + - 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. See [\_](#jobsnamelifecycle). -- - `min_retry_interval_millis` +- - `max_concurrent_runs` - Integer - - An optional minimal interval in milliseconds between the start of the failed run and the subsequent retry run. The default behavior is that unsuccessful runs are immediately retried. - -- - `new_cluster` - - Map - - If new_cluster, a description of a new cluster that is created for each run. See [\_](#jobsnametasksnew_cluster). + - An optional maximum allowed number of concurrent runs of the job. Set this value if you want to be able to execute multiple runs of the same job concurrently. This is useful for example if you trigger your job on a frequent schedule and want to allow consecutive runs to overlap with each other, or if you want to trigger multiple runs which differ by their input parameters. This setting affects only new runs. For example, suppose the job’s concurrency is 4 and there are 4 concurrent active runs. Then setting the concurrency to 3 won’t kill any of the active runs. However, from then on, new runs are skipped unless there are fewer than 3 active runs. This value cannot exceed 1000. Setting this value to `0` causes all new runs to be skipped. -- - `notebook_task` - - Map - - The task runs a notebook when the `notebook_task` field is present. See [\_](#jobsnametasksnotebook_task). +- - `name` + - String + - An optional name for the job. The maximum length is 4096 bytes in UTF-8 encoding. - - `notification_settings` - Map - - Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this task. See [\_](#jobsnametasksnotification_settings). + - Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this job. See [\_](#jobsnamenotification_settings). -- - `pipeline_task` - - Map - - The task triggers a pipeline update when the `pipeline_task` field is present. Only pipelines configured to use triggered more are supported. See [\_](#jobsnametaskspipeline_task). - -- - `power_bi_task` - - Map - - The task triggers a Power BI semantic model update when the `power_bi_task` field is present. See [\_](#jobsnametaskspower_bi_task). - -- - `python_wheel_task` - - Map - - The task runs a Python wheel when the `python_wheel_task` field is present. See [\_](#jobsnametaskspython_wheel_task). - -- - `retry_on_timeout` - - Boolean - - An optional policy to specify whether to retry a job when it times out. The default behavior is to not retry on timeout. +- - `parameters` + - Sequence + - Job-level parameter definitions. See [\_](#jobsnameparameters). -- - `run_if` +- - `performance_target` - String - - An optional value specifying the condition determining whether the task is run once its dependencies have been completed. * `ALL_SUCCESS`: All dependencies have executed and succeeded * `AT_LEAST_ONE_SUCCESS`: At least one dependency has succeeded * `NONE_FAILED`: None of the dependencies have failed and at least one was executed * `ALL_DONE`: All dependencies have been completed * `AT_LEAST_ONE_FAILED`: At least one dependency failed * `ALL_FAILED`: ALl dependencies have failed + - The performance mode on a serverless job. This field determines the level of compute performance or cost-efficiency for the run. The performance target does not apply to tasks that run on Serverless GPU compute. * `STANDARD`: Enables cost-efficient execution of serverless workloads. * `PERFORMANCE_OPTIMIZED`: Prioritizes fast startup and execution times through rapid scaling and optimized cluster performance. -- - `run_job_task` - - Map - - The task triggers another job when the `run_job_task` field is present. See [\_](#jobsnametasksrun_job_task). +- - `permissions` + - Sequence + - See [\_](#jobsnamepermissions). -- - `spark_jar_task` +- - `queue` - Map - - The task runs a JAR when the `spark_jar_task` field is present. See [\_](#jobsnametasksspark_jar_task). + - The queue settings of the job. See [\_](#jobsnamequeue). -- - `spark_python_task` +- - `run_as` - Map - - The task runs a Python file when the `spark_python_task` field is present. See [\_](#jobsnametasksspark_python_task). + - Write-only setting. Specifies the user or service principal that the job runs as. If not specified, the job runs as the user who created the job. Either `user_name` or `service_principal_name` should be specified. If not, an error is thrown. See [\_](#jobsnamerun_as). -- - `spark_submit_task` +- - `schedule` - Map - - (Legacy) The task runs the spark-submit script when the `spark_submit_task` field is present. This task can run only on new clusters and is not compatible with serverless compute. In the `new_cluster` specification, `libraries` and `spark_conf` are not supported. Instead, use `--jars` and `--py-files` to add Java and Python libraries and `--conf` to set the Spark configurations. `master`, `deploy-mode`, and `executor-cores` are automatically configured by Databricks; you _cannot_ specify them in parameters. By default, the Spark submit job uses all available memory (excluding reserved memory for Databricks services). You can set `--driver-memory`, and `--executor-memory` to a smaller value to leave some room for off-heap usage. The `--jars`, `--py-files`, `--files` arguments support DBFS and S3 paths. See [\_](#jobsnametasksspark_submit_task). + - An optional periodic schedule for this job. The default behavior is that the job only runs when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. See [\_](#jobsnameschedule). -- - `sql_task` +- - `tags` - Map - - The task runs a SQL query or file, or it refreshes a SQL alert or a legacy SQL dashboard when the `sql_task` field is present. See [\_](#jobsnametaskssql_task). + - A map of tags associated with the job. These are forwarded to the cluster as cluster tags for jobs clusters, and are subject to the same limitations as cluster tags. A maximum of 25 tags can be added to the job. -- - `task_key` - - String - - A unique name for the task. This field is used to refer to this task from other tasks. This field is required and must be unique within its parent job. On Update or Reset, this field is used to reference the tasks to be updated or reset. +- - `tasks` + - Sequence + - A list of task specifications to be executed by this job. It supports up to 1000 elements in write endpoints (:method:jobs/create, :method:jobs/reset, :method:jobs/update, :method:jobs/submit). Read endpoints return only 100 tasks. If more than 100 tasks are available, you can paginate through them using :method:jobs/get. Use the `next_page_token` field at the object root to determine if more results are available. See [\_](#jobsnametasks). - - `timeout_seconds` - Integer - - An optional timeout applied to each run of this job task. A value of `0` means no timeout. + - An optional timeout applied to each run of this job. A value of `0` means no timeout. + +- - `trigger` + - Map + - A configuration to trigger a run when certain conditions are met. The default behavior is that the job runs only when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. See [\_](#jobsnametrigger). - - `webhook_notifications` - Map - - A collection of system notification IDs to notify when runs of this task begin or complete. The default behavior is to not send any system notifications. See [\_](#jobsnametaskswebhook_notifications). + - A collection of system notification IDs to notify when runs of this job begin or complete. See [\_](#jobsnamewebhook_notifications). ::: -### jobs._name_.tasks.clean_rooms_notebook_task +**Example** + +The following example defines a job with the resource key `hello-job` with one notebook task: + +```yaml +resources: + jobs: + hello-job: + name: hello-job + tasks: + - task_key: hello-task + notebook_task: + notebook_path: ./hello.py +``` + +For information about defining job tasks and overriding job settings, see [_](/dev-tools/bundles/job-task-types.md), [_](/dev-tools/bundles/job-task-override.md), and [_](/dev-tools/bundles/cluster-override.md). + +### jobs._name_.continuous **`Type: Map`** -The task runs a [clean rooms](https://docs.databricks.com/en/clean-rooms/index.html) notebook -when the `clean_rooms_notebook_task` field is present. +An optional continuous property for this job. The continuous property will ensure that there is always one run executing. Only one of `schedule` and `continuous` can be used. @@ -3347,31 +3182,22 @@ when the `clean_rooms_notebook_task` field is present. - Type - Description -- - `clean_room_name` - - String - - The clean room that the notebook belongs to. - -- - `etag` +- - `pause_status` - String - - Checksum to validate the freshness of the notebook resource (i.e. the notebook being run is the latest version). It can be fetched by calling the :method:cleanroomassets/get API. - -- - `notebook_base_parameters` - - Map - - Base parameters to be used for the clean room notebook job. + - Indicate whether the continuous execution of the job is paused or not. Defaults to UNPAUSED. -- - `notebook_name` +- - `task_retry_mode` - String - - Name of the notebook being run. + - Indicate whether the continuous job is applying task level retries or not. Defaults to NEVER. ::: -### jobs._name_.tasks.condition_task +### jobs._name_.deployment **`Type: Map`** -The task evaluates a condition that can be used to control the execution of other tasks when the `condition_task` field is present. -The condition task does not require a cluster to execute and does not support retries or notifications. +Deployment information for jobs managed by external sources. @@ -3381,26 +3207,22 @@ The condition task does not require a cluster to execute and does not support re - Type - Description -- - `left` - - String - - The left operand of the condition task. Can be either a string value or a job state or parameter reference. - -- - `op` +- - `kind` - String - - * `EQUAL_TO`, `NOT_EQUAL` operators perform string comparison of their operands. This means that `“12.0” == “12”` will evaluate to `false`. * `GREATER_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN`, `LESS_THAN_OR_EQUAL` operators perform numeric comparison of their operands. `“12.0” >= “12”` will evaluate to `true`, `“10.0” >= “12”` will evaluate to `false`. The boolean comparison to task values can be implemented with operators `EQUAL_TO`, `NOT_EQUAL`. If a task value was set to a boolean value, it will be serialized to `“true”` or `“false”` for the comparison. + - The kind of deployment that manages the job. * `BUNDLE`: The job is managed by Databricks Asset Bundle. * `SYSTEM_MANAGED`: The job is managed by Databricks and is read-only. -- - `right` +- - `metadata_file_path` - String - - The right operand of the condition task. Can be either a string value or a job state or parameter reference. + - Path of the file that contains deployment metadata. ::: -### jobs._name_.tasks.dashboard_task +### jobs._name_.email_notifications **`Type: Map`** -The task refreshes a dashboard and sends a snapshot to subscribers. +An optional set of email addresses that is notified when runs of this job begin or complete as well as when this job is deleted. @@ -3410,26 +3232,40 @@ The task refreshes a dashboard and sends a snapshot to subscribers. - Type - Description -- - `dashboard_id` - - String - - +- - `no_alert_for_skipped_runs` + - Boolean + - This field is deprecated -- - `subscription` - - Map - - See [\_](#jobsnametasksdashboard_tasksubscription). +- - `on_duration_warning_threshold_exceeded` + - Sequence + - A list of email addresses to be notified when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. If no rule for the `RUN_DURATION_SECONDS` metric is specified in the `health` field for the job, notifications are not sent. -- - `warehouse_id` - - String - - Optional: The warehouse id to execute the dashboard with for the schedule. If not specified, the default warehouse of the dashboard will be used. +- - `on_failure` + - Sequence + - A list of email addresses to be notified when a run unsuccessfully completes. A run is considered to have completed unsuccessfully if it ends with an `INTERNAL_ERROR` `life_cycle_state` or a `FAILED`, or `TIMED_OUT` result_state. If this is not specified on job creation, reset, or update the list is empty, and notifications are not sent. + +- - `on_start` + - Sequence + - A list of email addresses to be notified when a run begins. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. + +- - `on_streaming_backlog_exceeded` + - Sequence + - A list of email addresses to notify when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. + +- - `on_success` + - Sequence + - A list of email addresses to be notified when a run successfully completes. A run is considered to have completed successfully if it ends with a `TERMINATED` `life_cycle_state` and a `SUCCESS` result_state. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. ::: -### jobs._name_.tasks.dashboard_task.subscription +### jobs._name_.environments -**`Type: Map`** +**`Type: Sequence`** - +A list of task execution environment specifications that can be referenced by serverless tasks of this job. +For serverless notebook tasks, if the environment_key is not specified, the notebook environment will be used if present. If a jobs environment is specified, it will override the notebook environment. +For other serverless tasks, the task environment is required to be specified using environment_key in the task settings. @@ -3439,26 +3275,23 @@ The task refreshes a dashboard and sends a snapshot to subscribers. - Type - Description -- - `custom_subject` +- - `environment_key` - String - - Optional: Allows users to specify a custom subject line on the email sent to subscribers. - -- - `paused` - - Boolean - - When true, the subscription will not send emails. + - The key of an environment. It has to be unique within a job. -- - `subscribers` - - Sequence - - See [\_](#jobsnametasksdashboard_tasksubscriptionsubscribers). +- - `spec` + - Map + - The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported. See [\_](#jobsnameenvironmentsspec). ::: -### jobs._name_.tasks.dashboard_task.subscription.subscribers +### jobs._name_.environments.spec -**`Type: Sequence`** +**`Type: Map`** - +The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines. +In this minimal environment spec, only pip and java dependencies are supported. @@ -3468,22 +3301,38 @@ The task refreshes a dashboard and sends a snapshot to subscribers. - Type - Description -- - `destination_id` +- - `base_environment` - String - - + - The base environment this environment is built on top of. A base environment defines the environment version and a list of dependencies for serverless compute. The value can be a file path to a custom `env.yaml` file (e.g., `/Workspace/path/to/env.yaml`). Support for a Databricks-provided base environment ID (e.g., `workspace-base-environments/databricks_ai_v4`) and workspace base environment ID (e.g., `workspace-base-environments/dbe_b849b66e-b31a-4cb5-b161-1f2b10877fb7`) is in Beta. Either `environment_version` or `base_environment` can be provided. For more information, see -- - `user_name` +- - `client` + - String + - This field is deprecated + +- - `dependencies` + - Sequence + - List of pip dependencies, as supported by the version of pip in this environment. + +- - `environment_version` - String + - Either `environment_version` or `base_environment` needs to be provided. Environment version used by the environment. Each version comes with a specific Python version and a set of Python packages. The version is a string, consisting of an integer. + +- - `java_dependencies` + - Sequence - ::: -### jobs._name_.tasks.dbt_task +### jobs._name_.git_source **`Type: Map`** -The task runs one or more dbt commands when the `dbt_task` field is present. The dbt task requires both Databricks SQL and the ability to use a serverless or a pro SQL warehouse. +An optional specification for a remote Git repository containing the source code used by tasks. Version-controlled source code is supported by notebook, dbt, Python script, and SQL File tasks. + +If `git_source` is set, these tasks retrieve the file from the remote repository by default. However, this behavior can be overridden by setting `source` to `WORKSPACE` on the task. + +Note: dbt and SQL File tasks support only version-controlled sources. If dbt or SQL File tasks are used, `git_source` must be defined on the job. @@ -3493,43 +3342,42 @@ The task runs one or more dbt commands when the `dbt_task` field is present. The - Type - Description -- - `catalog` +- - `git_branch` - String - - Optional name of the catalog to use. The value is the top level in the 3-level namespace of Unity Catalog (catalog / schema / relation). The catalog value can only be specified if a warehouse_id is specified. Requires dbt-databricks >= 1.1.1. - -- - `commands` - - Sequence - - A list of dbt commands to execute. All commands must start with `dbt`. This parameter must not be empty. A maximum of up to 10 commands can be provided. + - Name of the branch to be checked out and used by this job. This field cannot be specified in conjunction with git_tag or git_commit. -- - `profiles_directory` +- - `git_commit` - String - - Optional (relative) path to the profiles directory. Can only be specified if no warehouse_id is specified. If no warehouse_id is specified and this folder is unset, the root directory is used. + - Commit to be checked out and used by this job. This field cannot be specified in conjunction with git_branch or git_tag. -- - `project_directory` +- - `git_provider` - String - - Path to the project directory. Optional for Git sourced tasks, in which case if no value is provided, the root of the Git repository is used. + - Unique identifier of the service used to host the Git repository. The value is case insensitive. -- - `schema` - - String - - Optional schema to write to. This parameter is only used when a warehouse_id is also provided. If not provided, the `default` schema is used. +- - `git_snapshot` + - Map + - Read-only state of the remote repository at the time the job was run. This field is only included on job runs. See [\_](#jobsnamegit_sourcegit_snapshot). -- - `source` +- - `git_tag` - String - - Optional location type of the project directory. When set to `WORKSPACE`, the project will be retrieved from the local Databricks workspace. When set to `GIT`, the project will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: Project is located in Databricks workspace. * `GIT`: Project is located in cloud Git provider. + - Name of the tag to be checked out and used by this job. This field cannot be specified in conjunction with git_branch or git_commit. -- - `warehouse_id` +- - `git_url` - String - - ID of the SQL warehouse to connect to. If provided, we automatically generate and provide the profile and connection details to dbt. It can be overridden on a per-command basis by using the `--profiles-dir` command line argument. + - URL of the repository to be cloned by this job. + +- - `sparse_checkout` + - Map + - See [\_](#jobsnamegit_sourcesparse_checkout). ::: -### jobs._name_.tasks.depends_on +### jobs._name_.git_source.git_snapshot -**`Type: Sequence`** +**`Type: Map`** -An optional array of objects specifying the dependency graph of the task. All tasks specified in this field must complete before executing this task. The task will run only if the `run_if` condition is true. -The key is `task_key`, and the value is the name assigned to the dependent task. +Read-only state of the remote repository at the time the job was run. This field is only included on job runs. @@ -3539,22 +3387,18 @@ The key is `task_key`, and the value is the name assigned to the dependent task. - Type - Description -- - `outcome` - - String - - Can only be specified on condition task dependencies. The outcome of the dependent task that must be met for this task to run. - -- - `task_key` +- - `used_commit` - String - - The name of the task this task depends on. + - Commit that was used to execute the run. If git_branch was specified, this points to the HEAD of the branch at the time of the run; if git_tag was specified, this points to the commit the tag points to. ::: -### jobs._name_.tasks.email_notifications +### jobs._name_.git_source.sparse_checkout **`Type: Map`** -An optional set of email addresses that is notified when runs of this task begin or complete as well as when this task is deleted. The default behavior is to not send any emails. + @@ -3564,63 +3408,14 @@ An optional set of email addresses that is notified when runs of this task begin - Type - Description -- - `no_alert_for_skipped_runs` - - Boolean - - This field is deprecated - -- - `on_duration_warning_threshold_exceeded` +- - `patterns` - Sequence - - A list of email addresses to be notified when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. If no rule for the `RUN_DURATION_SECONDS` metric is specified in the `health` field for the job, notifications are not sent. + - List of patterns to include for sparse checkout. -- - `on_failure` - - Sequence - - A list of email addresses to be notified when a run unsuccessfully completes. A run is considered to have completed unsuccessfully if it ends with an `INTERNAL_ERROR` `life_cycle_state` or a `FAILED`, or `TIMED_OUT` result_state. If this is not specified on job creation, reset, or update the list is empty, and notifications are not sent. +::: -- - `on_start` - - Sequence - - A list of email addresses to be notified when a run begins. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. -- - `on_streaming_backlog_exceeded` - - Sequence - - A list of email addresses to notify when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. - -- - `on_success` - - Sequence - - A list of email addresses to be notified when a run successfully completes. A run is considered to have completed successfully if it ends with a `TERMINATED` `life_cycle_state` and a `SUCCESS` result_state. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. - -::: - - -### jobs._name_.tasks.for_each_task - -**`Type: Map`** - -The task executes a nested task for every input provided when the `for_each_task` field is present. - - - -:::list-table - -- - Key - - Type - - Description - -- - `concurrency` - - Integer - - An optional maximum allowed number of concurrent runs of the task. Set this value if you want to be able to execute multiple runs of the task concurrently. - -- - `inputs` - - String - - Array for task to iterate on. This can be a JSON string or a reference to an array parameter. - -- - `task` - - Map - - Configuration for the task that will be run for each element in the array - -::: - - -### jobs._name_.tasks.health +### jobs._name_.health **`Type: Map`** @@ -3636,12 +3431,12 @@ An optional set of health rules that can be defined for this job. - - `rules` - Sequence - - See [\_](#jobsnametaskshealthrules). + - See [\_](#jobsnamehealthrules). ::: -### jobs._name_.tasks.health.rules +### jobs._name_.health.rules **`Type: Sequence`** @@ -3670,12 +3465,11 @@ An optional set of health rules that can be defined for this job. ::: -### jobs._name_.tasks.libraries +### jobs._name_.job_clusters **`Type: Sequence`** -An optional list of libraries to be installed on the cluster. -The default value is an empty list. +A list of job cluster specifications that can be shared and reused by tasks of this job. Libraries cannot be declared in a shared job cluster. You must declare dependent libraries in task settings. @@ -3685,123 +3479,22 @@ The default value is an empty list. - Type - Description -- - `cran` - - Map - - Specification of a CRAN library to be installed as part of the library. See [\_](#jobsnametaskslibrariescran). - -- - `egg` - - String - - This field is deprecated - -- - `jar` +- - `job_cluster_key` - String - - URI of the JAR library to install. Supported URIs include Workspace paths, Unity Catalog Volumes paths, and S3 URIs. For example: `{ "jar": "/Workspace/path/to/library.jar" }`, `{ "jar" : "/Volumes/path/to/library.jar" }` or `{ "jar": "s3://my-bucket/library.jar" }`. If S3 is used, please make sure the cluster has read access on the library. You may need to launch the cluster with an IAM role to access the S3 URI. - -- - `maven` - - Map - - Specification of a maven library to be installed. For example: `{ "coordinates": "org.jsoup:jsoup:1.7.2" }`. See [\_](#jobsnametaskslibrariesmaven). + - A unique name for the job cluster. This field is required and must be unique within the job. `JobTaskSettings` may refer to this field to determine which cluster to launch for the task execution. -- - `pypi` +- - `new_cluster` - Map - - Specification of a PyPi library to be installed. For example: `{ "package": "simplejson" }`. See [\_](#jobsnametaskslibrariespypi). - -- - `requirements` - - String - - URI of the requirements.txt file to install. Only Workspace paths and Unity Catalog Volumes paths are supported. For example: `{ "requirements": "/Workspace/path/to/requirements.txt" }` or `{ "requirements" : "/Volumes/path/to/requirements.txt" }` - -- - `whl` - - String - - URI of the wheel library to install. Supported URIs include Workspace paths, Unity Catalog Volumes paths, and S3 URIs. For example: `{ "whl": "/Workspace/path/to/library.whl" }`, `{ "whl" : "/Volumes/path/to/library.whl" }` or `{ "whl": "s3://my-bucket/library.whl" }`. If S3 is used, please make sure the cluster has read access on the library. You may need to launch the cluster with an IAM role to access the S3 URI. - -::: - - -### jobs._name_.tasks.libraries.cran - -**`Type: Map`** - -Specification of a CRAN library to be installed as part of the library - - - -:::list-table - -- - Key - - Type - - Description - -- - `package` - - String - - The name of the CRAN package to install. - -- - `repo` - - String - - The repository where the package can be found. If not specified, the default CRAN repo is used. - -::: - - -### jobs._name_.tasks.libraries.maven - -**`Type: Map`** - -Specification of a maven library to be installed. For example: -`{ "coordinates": "org.jsoup:jsoup:1.7.2" }` - - - -:::list-table - -- - Key - - Type - - Description - -- - `coordinates` - - String - - Gradle-style maven coordinates. For example: "org.jsoup:jsoup:1.7.2". - -- - `exclusions` - - Sequence - - List of dependences to exclude. For example: `["slf4j:slf4j", "*:hadoop-client"]`. Maven dependency exclusions: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html. - -- - `repo` - - String - - Maven repo to install the Maven package from. If omitted, both Maven Central Repository and Spark Packages are searched. - -::: - - -### jobs._name_.tasks.libraries.pypi - -**`Type: Map`** - -Specification of a PyPi library to be installed. For example: -`{ "package": "simplejson" }` - - - -:::list-table - -- - Key - - Type - - Description - -- - `package` - - String - - The name of the pypi package to install. An optional exact version specification is also supported. Examples: "simplejson" and "simplejson==3.8.0". - -- - `repo` - - String - - The repository where the package can be found. If not specified, the default pip index is used. + - If new_cluster, a description of a cluster that is created for each task. See [\_](#jobsnamejob_clustersnew_cluster). ::: -### jobs._name_.tasks.new_cluster +### jobs._name_.job_clusters.new_cluster **`Type: Map`** -If new_cluster, a description of a new cluster that is created for each run. +If new_cluster, a description of a cluster that is created for each task. @@ -3817,7 +3510,7 @@ If new_cluster, a description of a new cluster that is created for each run. - - `autoscale` - Map - - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#jobsnametasksnew_clusterautoscale). + - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#jobsnamejob_clustersnew_clusterautoscale). - - `autotermination_minutes` - Integer @@ -3825,15 +3518,15 @@ If new_cluster, a description of a new cluster that is created for each run. - - `aws_attributes` - Map - - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clusteraws_attributes). + - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clusteraws_attributes). - - `azure_attributes` - Map - - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clusterazure_attributes). + - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clusterazure_attributes). - - `cluster_log_conf` - Map - - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#jobsnametasksnew_clustercluster_log_conf). + - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_conf). - - `cluster_name` - String @@ -3849,19 +3542,23 @@ If new_cluster, a description of a new cluster that is created for each run. - - `docker_image` - Map - - See [\_](#jobsnametasksnew_clusterdocker_image). + - See [\_](#jobsnamejob_clustersnew_clusterdocker_image). - - `driver_instance_pool_id` - String - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. +- - `driver_node_type_flexibility` + - Map + - Flexible node type configuration for the driver node. See [\_](#jobsnamejob_clustersnew_clusterdriver_node_type_flexibility). + - - `driver_node_type_id` - String - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. This field, along with node_type_id, should not be set if virtual_cluster_size is set. If both driver_node_type_id, node_type_id, and virtual_cluster_size are specified, driver_node_type_id and node_type_id take precedence. - - `enable_elastic_disk` - Boolean - - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. This feature requires specific AWS permissions to function correctly - refer to the User Guide for more details. + - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. - - `enable_local_disk_encryption` - Boolean @@ -3869,11 +3566,11 @@ If new_cluster, a description of a new cluster that is created for each run. - - `gcp_attributes` - Map - - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clustergcp_attributes). + - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnamejob_clustersnew_clustergcp_attributes). - - `init_scripts` - Sequence - - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#jobsnametasksnew_clusterinit_scripts). + - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#jobsnamejob_clustersnew_clusterinit_scripts). - - `instance_pool_id` - String @@ -3935,14 +3632,18 @@ If new_cluster, a description of a new cluster that is created for each run. - Boolean - This field can only be used when `kind = CLASSIC_PREVIEW`. `effective_spark_version` is determined by `spark_version` (DBR release), this field `use_ml_runtime`, and whether `node_type_id` is gpu node or not. +- - `worker_node_type_flexibility` + - Map + - Flexible node type configuration for worker nodes. See [\_](#jobsnamejob_clustersnew_clusterworker_node_type_flexibility). + - - `workload_type` - Map - - Cluster Attributes showing for clusters workload types. See [\_](#jobsnametasksnew_clusterworkload_type). + - Cluster Attributes showing for clusters workload types. See [\_](#jobsnamejob_clustersnew_clusterworkload_type). ::: -### jobs._name_.tasks.new_cluster.autoscale +### jobs._name_.job_clusters.new_cluster.autoscale **`Type: Map`** @@ -3968,7 +3669,7 @@ Note: autoscaling works best with DB runtime versions 3.0 or later. ::: -### jobs._name_.tasks.new_cluster.aws_attributes +### jobs._name_.job_clusters.new_cluster.aws_attributes **`Type: Map`** @@ -4021,12 +3722,12 @@ If not specified at cluster creation, a set of default values will be used. - - `zone_id` - String - - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, a default zone will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. + - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, the zone "auto" will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. ::: -### jobs._name_.tasks.new_cluster.azure_attributes +### jobs._name_.job_clusters.new_cluster.azure_attributes **`Type: Map`** @@ -4051,7 +3752,7 @@ If not specified at cluster creation, a set of default values will be used. - - `log_analytics_info` - Map - - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#jobsnametasksnew_clusterazure_attributeslog_analytics_info). + - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#jobsnamejob_clustersnew_clusterazure_attributeslog_analytics_info). - - `spot_bid_max_price` - Any @@ -4060,7 +3761,7 @@ If not specified at cluster creation, a set of default values will be used. ::: -### jobs._name_.tasks.new_cluster.azure_attributes.log_analytics_info +### jobs._name_.job_clusters.new_cluster.azure_attributes.log_analytics_info **`Type: Map`** @@ -4085,7 +3786,7 @@ Defines values necessary to configure and run Azure Log Analytics agent ::: -### jobs._name_.tasks.new_cluster.cluster_log_conf +### jobs._name_.job_clusters.new_cluster.cluster_log_conf **`Type: Map`** @@ -4105,20 +3806,20 @@ the destination of executor logs is `$destination/$clusterId/executor`. - - `dbfs` - Map - - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#jobsnametasksnew_clustercluster_log_confdbfs). + - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confdbfs). - - `s3` - Map - - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnametasksnew_clustercluster_log_confs3). + - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confs3). - - `volumes` - Map - - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#jobsnametasksnew_clustercluster_log_confvolumes). + - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#jobsnamejob_clustersnew_clustercluster_log_confvolumes). ::: -### jobs._name_.tasks.new_cluster.cluster_log_conf.dbfs +### jobs._name_.job_clusters.new_cluster.cluster_log_conf.dbfs **`Type: Map`** @@ -4140,7 +3841,7 @@ destination needs to be provided. e.g. ::: -### jobs._name_.tasks.new_cluster.cluster_log_conf.s3 +### jobs._name_.job_clusters.new_cluster.cluster_log_conf.s3 **`Type: Map`** @@ -4188,7 +3889,7 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in ::: -### jobs._name_.tasks.new_cluster.cluster_log_conf.volumes +### jobs._name_.job_clusters.new_cluster.cluster_log_conf.volumes **`Type: Map`** @@ -4210,7 +3911,7 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.docker_image +### jobs._name_.job_clusters.new_cluster.docker_image **`Type: Map`** @@ -4226,7 +3927,7 @@ destination needs to be provided, e.g. - - `basic_auth` - Map - - See [\_](#jobsnametasksnew_clusterdocker_imagebasic_auth). + - See [\_](#jobsnamejob_clustersnew_clusterdocker_imagebasic_auth). - - `url` - String @@ -4235,7 +3936,7 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.docker_image.basic_auth +### jobs._name_.job_clusters.new_cluster.docker_image.basic_auth **`Type: Map`** @@ -4260,7 +3961,28 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.gcp_attributes +### jobs._name_.job_clusters.new_cluster.driver_node_type_flexibility + +**`Type: Map`** + +Flexible node type configuration for the driver node. + + + +:::list-table + +- - Key + - Type + - Description + +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. + +::: + + +### jobs._name_.job_clusters.new_cluster.gcp_attributes **`Type: Map`** @@ -4306,7 +4028,7 @@ If not specified at cluster creation, a set of default values will be used. ::: -### jobs._name_.tasks.new_cluster.init_scripts +### jobs._name_.job_clusters.new_cluster.init_scripts **`Type: Sequence`** @@ -4324,7 +4046,7 @@ If `cluster_log_conf` is specified, init script logs are sent to `/ - - `abfss` - Map - - Contains the Azure Data Lake Storage destination path. See [\_](#jobsnametasksnew_clusterinit_scriptsabfss). + - Contains the Azure Data Lake Storage destination path. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsabfss). - - `dbfs` - Map @@ -4332,28 +4054,28 @@ If `cluster_log_conf` is specified, init script logs are sent to `/ - - `file` - Map - - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsfile). + - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsfile). - - `gcs` - Map - - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsgcs). + - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsgcs). - - `s3` - Map - - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnametasksnew_clusterinit_scriptss3). + - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptss3). - - `volumes` - Map - - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsvolumes). + - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsvolumes). - - `workspace` - Map - - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsworkspace). + - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#jobsnamejob_clustersnew_clusterinit_scriptsworkspace). ::: -### jobs._name_.tasks.new_cluster.init_scripts.abfss +### jobs._name_.job_clusters.new_cluster.init_scripts.abfss **`Type: Map`** @@ -4374,7 +4096,7 @@ Contains the Azure Data Lake Storage destination path ::: -### jobs._name_.tasks.new_cluster.init_scripts.file +### jobs._name_.job_clusters.new_cluster.init_scripts.file **`Type: Map`** @@ -4396,7 +4118,7 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.init_scripts.gcs +### jobs._name_.job_clusters.new_cluster.init_scripts.gcs **`Type: Map`** @@ -4418,7 +4140,7 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.init_scripts.s3 +### jobs._name_.job_clusters.new_cluster.init_scripts.s3 **`Type: Map`** @@ -4466,7 +4188,7 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in ::: -### jobs._name_.tasks.new_cluster.init_scripts.volumes +### jobs._name_.job_clusters.new_cluster.init_scripts.volumes **`Type: Map`** @@ -4488,7 +4210,7 @@ destination needs to be provided. e.g. ::: -### jobs._name_.tasks.new_cluster.init_scripts.workspace +### jobs._name_.job_clusters.new_cluster.init_scripts.workspace **`Type: Map`** @@ -4510,7 +4232,28 @@ destination needs to be provided, e.g. ::: -### jobs._name_.tasks.new_cluster.workload_type +### jobs._name_.job_clusters.new_cluster.worker_node_type_flexibility + +**`Type: Map`** + +Flexible node type configuration for worker nodes. + + + +:::list-table + +- - Key + - Type + - Description + +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. + +::: + + +### jobs._name_.job_clusters.new_cluster.workload_type **`Type: Map`** @@ -4526,12 +4269,12 @@ Cluster Attributes showing for clusters workload types. - - `clients` - Map - - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#jobsnametasksnew_clusterworkload_typeclients). + - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#jobsnamejob_clustersnew_clusterworkload_typeclients). ::: -### jobs._name_.tasks.new_cluster.workload_type.clients +### jobs._name_.job_clusters.new_cluster.workload_type.clients **`Type: Map`** @@ -4556,11 +4299,11 @@ defined what type of clients can use the cluster. E.g. Notebooks, Jobs ::: -### jobs._name_.tasks.notebook_task +### jobs._name_.lifecycle **`Type: Map`** -The task runs a notebook when the `notebook_task` field is present. +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. @@ -4570,30 +4313,18 @@ The task runs a notebook when the `notebook_task` field is present. - Type - Description -- - `base_parameters` - - Map - - Base parameters to be used for each run of this job. If the run is initiated by a call to :method:jobs/run Now with parameters specified, the two parameters maps are merged. If the same key is specified in `base_parameters` and in `run-now`, the value from `run-now` is used. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. If the notebook takes a parameter that is not specified in the job’s `base_parameters` or the `run-now` override parameters, the default value from the notebook is used. Retrieve these parameters in a notebook using [dbutils.widgets.get](https://docs.databricks.com/dev-tools/databricks-utils.html#dbutils-widgets). The JSON representation of this field cannot exceed 1MB. - -- - `notebook_path` - - String - - The path of the notebook to be run in the Databricks workspace or remote repository. For notebooks stored in the Databricks workspace, the path must be absolute and begin with a slash. For notebooks stored in a remote repository, the path must be relative. This field is required. - -- - `source` - - String - - Optional location type of the notebook. When set to `WORKSPACE`, the notebook will be retrieved from the local Databricks workspace. When set to `GIT`, the notebook will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: Notebook is located in Databricks workspace. * `GIT`: Notebook is located in cloud Git provider. - -- - `warehouse_id` - - String - - Optional `warehouse_id` to run the notebook on a SQL warehouse. Classic SQL warehouses are NOT supported, please use serverless or pro SQL warehouses. Note that SQL warehouses only support SQL cells; if the notebook contains non-SQL cells, the run will fail. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### jobs._name_.tasks.notification_settings +### jobs._name_.notification_settings **`Type: Map`** -Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this task. +Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this job. @@ -4603,10 +4334,6 @@ Optional notification settings that are used when sending notifications to each - Type - Description -- - `alert_on_last_attempt` - - Boolean - - If true, do not send notifications to recipients specified in `on_start` for the retried runs and do not send notifications to recipients specified in `on_failure` until the last retry of the run. - - - `no_alert_for_canceled_runs` - Boolean - If true, do not send notifications to recipients specified in `on_failure` if the run is canceled. @@ -4618,11 +4345,11 @@ Optional notification settings that are used when sending notifications to each ::: -### jobs._name_.tasks.pipeline_task +### jobs._name_.parameters -**`Type: Map`** +**`Type: Sequence`** -The task triggers a pipeline update when the `pipeline_task` field is present. Only pipelines configured to use triggered more are supported. +Job-level parameter definitions @@ -4632,22 +4359,22 @@ The task triggers a pipeline update when the `pipeline_task` field is present. O - Type - Description -- - `full_refresh` - - Boolean - - If true, triggers a full refresh on the delta live table. +- - `default` + - String + - Default value of the parameter. -- - `pipeline_id` +- - `name` - String - - The full name of the pipeline task to execute. + - The name of the defined parameter. May only contain alphanumeric characters, `_`, `-`, and `.` ::: -### jobs._name_.tasks.power_bi_task +### jobs._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -The task triggers a Power BI semantic model update when the `power_bi_task` field is present. + @@ -4657,34 +4384,30 @@ The task triggers a Power BI semantic model update when the `power_bi_task` fiel - Type - Description -- - `connection_resource_name` +- - `group_name` - String - - The resource name of the UC connection to authenticate from Databricks to Power BI - -- - `power_bi_model` - - Map - - The semantic model to update. See [\_](#jobsnametaskspower_bi_taskpower_bi_model). + - -- - `refresh_after_update` - - Boolean - - Whether the model should be refreshed after the update +- - `level` + - String + - Permission level -- - `tables` - - Sequence - - The tables to be exported to Power BI. See [\_](#jobsnametaskspower_bi_tasktables). +- - `service_principal_name` + - String + - -- - `warehouse_id` +- - `user_name` - String - - The SQL warehouse ID to use as the Power BI data source + - ::: -### jobs._name_.tasks.power_bi_task.power_bi_model +### jobs._name_.queue **`Type: Map`** -The semantic model to update +The queue settings of the job. @@ -4694,34 +4417,20 @@ The semantic model to update - Type - Description -- - `authentication_method` - - String - - How the published Power BI model authenticates to Databricks - -- - `model_name` - - String - - The name of the Power BI model - -- - `overwrite_existing` +- - `enabled` - Boolean - - Whether to overwrite existing Power BI models - -- - `storage_mode` - - String - - The default storage mode of the Power BI model - -- - `workspace_name` - - String - - The name of the Power BI workspace of the model + - If true, enable queueing for the job. This is a required field. ::: -### jobs._name_.tasks.power_bi_task.tables +### jobs._name_.run_as -**`Type: Sequence`** +**`Type: Map`** -The tables to be exported to Power BI +Write-only setting. Specifies the user or service principal that the job runs as. If not specified, the job runs as the user who created the job. + +Either `user_name` or `service_principal_name` should be specified. If not, an error is thrown. @@ -4731,30 +4440,22 @@ The tables to be exported to Power BI - Type - Description -- - `catalog` - - String - - The catalog name in Databricks - -- - `name` - - String - - The table name in Databricks - -- - `schema` +- - `service_principal_name` - String - - The schema name in Databricks + - The application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. -- - `storage_mode` +- - `user_name` - String - - The Power BI storage mode of the table + - The email of an active workspace user. Non-admin users can only set this field to their own email. ::: -### jobs._name_.tasks.python_wheel_task +### jobs._name_.schedule **`Type: Map`** -The task runs a Python wheel when the `python_wheel_task` field is present. +An optional periodic schedule for this job. The default behavior is that the job only runs when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. @@ -4764,30 +4465,28 @@ The task runs a Python wheel when the `python_wheel_task` field is present. - Type - Description -- - `entry_point` +- - `pause_status` - String - - Named entry point to use, if it does not exist in the metadata of the package it executes the function from the package directly using `$packageName.$entryPoint()` - -- - `named_parameters` - - Map - - Command-line parameters passed to Python wheel task in the form of `["--name=task", "--data=dbfs:/path/to/data.json"]`. Leave it empty if `parameters` is not null. + - Indicate whether this schedule is paused or not. -- - `package_name` +- - `quartz_cron_expression` - String - - Name of the package to execute + - A Cron expression using Quartz syntax that describes the schedule for a job. See [Cron Trigger](http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html) for details. This field is required. -- - `parameters` - - Sequence - - Command-line parameters passed to Python wheel task. Leave it empty if `named_parameters` is not null. +- - `timezone_id` + - String + - A Java timezone ID. The schedule for a job is resolved with respect to this timezone. See [Java TimeZone](https://docs.oracle.com/javase/7/docs/api/java/util/TimeZone.html) for details. This field is required. ::: -### jobs._name_.tasks.run_job_task +### jobs._name_.tasks -**`Type: Map`** +**`Type: Sequence`** -The task triggers another job when the `run_job_task` field is present. +A list of task specifications to be executed by this job. +It supports up to 1000 elements in write endpoints (:method:jobs/create, :method:jobs/reset, :method:jobs/update, :method:jobs/submit). +Read endpoints return only 100 tasks. If more than 100 tasks are available, you can paginate through them using :method:jobs/get. Use the `next_page_token` field at the object root to determine if more results are available. @@ -4797,80 +4496,151 @@ The task triggers another job when the `run_job_task` field is present. - Type - Description -- - `job_id` - - Integer - - ID of the job to trigger. +- - `alert_task` + - Map + - The task evaluates a Databricks alert and sends notifications to subscribers when the `alert_task` field is present. See [\_](#jobsnametasksalert_task). -- - `job_parameters` +- - `clean_rooms_notebook_task` - Map - - Job-level parameters used to trigger the job. + - The task runs a [clean rooms](https://docs.databricks.com/clean-rooms/index.html) notebook when the `clean_rooms_notebook_task` field is present. See [\_](#jobsnametasksclean_rooms_notebook_task). -- - `pipeline_params` +- - `compute` - Map - - Controls whether the pipeline should perform a full refresh. See [\_](#jobsnametasksrun_job_taskpipeline_params). + - Task level compute configuration. See [\_](#jobsnametaskscompute). -::: +- - `condition_task` + - Map + - The task evaluates a condition that can be used to control the execution of other tasks when the `condition_task` field is present. The condition task does not require a cluster to execute and does not support retries or notifications. See [\_](#jobsnametaskscondition_task). +- - `dashboard_task` + - Map + - The task refreshes a dashboard and sends a snapshot to subscribers. See [\_](#jobsnametasksdashboard_task). -### jobs._name_.tasks.run_job_task.pipeline_params +- - `dbt_task` + - Map + - The task runs one or more dbt commands when the `dbt_task` field is present. The dbt task requires both Databricks SQL and the ability to use a serverless or a pro SQL warehouse. See [\_](#jobsnametasksdbt_task). -**`Type: Map`** +- - `depends_on` + - Sequence + - An optional array of objects specifying the dependency graph of the task. All tasks specified in this field must complete before executing this task. The task will run only if the `run_if` condition is true. The key is `task_key`, and the value is the name assigned to the dependent task. See [\_](#jobsnametasksdepends_on). -Controls whether the pipeline should perform a full refresh +- - `description` + - String + - An optional description for this task. +- - `disable_auto_optimization` + - Boolean + - An option to disable auto optimization in serverless +- - `email_notifications` + - Map + - An optional set of email addresses that is notified when runs of this task begin or complete as well as when this task is deleted. The default behavior is to not send any emails. See [\_](#jobsnametasksemail_notifications). -:::list-table +- - `environment_key` + - String + - The key that references an environment spec in a job. This field is required for Python script, Python wheel and dbt tasks when using serverless compute. -- - Key - - Type - - Description +- - `existing_cluster_id` + - String + - If existing_cluster_id, the ID of an existing cluster that is used for all runs. When running jobs or tasks on an existing cluster, you may need to manually restart the cluster if it stops responding. We suggest running jobs and tasks on new clusters for greater reliability -- - `full_refresh` - - Boolean - - If true, triggers a full refresh on the delta live table. +- - `for_each_task` + - Map + - The task executes a nested task for every input provided when the `for_each_task` field is present. See [\_](#jobsnametasksfor_each_task). -::: +- - `health` + - Map + - An optional set of health rules that can be defined for this job. See [\_](#jobsnametaskshealth). +- - `job_cluster_key` + - String + - If job_cluster_key, this task is executed reusing the cluster specified in `job.settings.job_clusters`. -### jobs._name_.tasks.spark_jar_task +- - `libraries` + - Sequence + - An optional list of libraries to be installed on the cluster. The default value is an empty list. See [\_](#jobsnametaskslibraries). -**`Type: Map`** +- - `max_retries` + - Integer + - An optional maximum number of times to retry an unsuccessful run. A run is considered to be unsuccessful if it completes with the `FAILED` result_state or `INTERNAL_ERROR` `life_cycle_state`. The value `-1` means to retry indefinitely and the value `0` means to never retry. -The task runs a JAR when the `spark_jar_task` field is present. +- - `min_retry_interval_millis` + - Integer + - An optional minimal interval in milliseconds between the start of the failed run and the subsequent retry run. The default behavior is that unsuccessful runs are immediately retried. +- - `new_cluster` + - Map + - If new_cluster, a description of a new cluster that is created for each run. See [\_](#jobsnametasksnew_cluster). +- - `notebook_task` + - Map + - The task runs a notebook when the `notebook_task` field is present. See [\_](#jobsnametasksnotebook_task). -:::list-table +- - `notification_settings` + - Map + - Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this task. See [\_](#jobsnametasksnotification_settings). -- - Key - - Type - - Description +- - `pipeline_task` + - Map + - The task triggers a pipeline update when the `pipeline_task` field is present. Only pipelines configured to use triggered more are supported. See [\_](#jobsnametaskspipeline_task). -- - `jar_uri` +- - `power_bi_task` + - Map + - The task triggers a Power BI semantic model update when the `power_bi_task` field is present. See [\_](#jobsnametaskspower_bi_task). + +- - `python_wheel_task` + - Map + - The task runs a Python wheel when the `python_wheel_task` field is present. See [\_](#jobsnametaskspython_wheel_task). + +- - `retry_on_timeout` + - Boolean + - An optional policy to specify whether to retry a job when it times out. The default behavior is to not retry on timeout. + +- - `run_if` - String + - An optional value specifying the condition determining whether the task is run once its dependencies have been completed. * `ALL_SUCCESS`: All dependencies have executed and succeeded * `AT_LEAST_ONE_SUCCESS`: At least one dependency has succeeded * `NONE_FAILED`: None of the dependencies have failed and at least one was executed * `ALL_DONE`: All dependencies have been completed * `AT_LEAST_ONE_FAILED`: At least one dependency failed * `ALL_FAILED`: ALl dependencies have failed + +- - `run_job_task` + - Map + - The task triggers another job when the `run_job_task` field is present. See [\_](#jobsnametasksrun_job_task). + +- - `spark_jar_task` + - Map + - The task runs a JAR when the `spark_jar_task` field is present. See [\_](#jobsnametasksspark_jar_task). + +- - `spark_python_task` + - Map + - The task runs a Python file when the `spark_python_task` field is present. See [\_](#jobsnametasksspark_python_task). + +- - `spark_submit_task` + - Map - This field is deprecated -- - `main_class_name` +- - `sql_task` + - Map + - The task runs a SQL query or file, or it refreshes a SQL alert or a legacy SQL dashboard when the `sql_task` field is present. See [\_](#jobsnametaskssql_task). + +- - `task_key` - String - - The full name of the class containing the main method to be executed. This class must be contained in a JAR provided as a library. The code must use `SparkContext.getOrCreate` to obtain a Spark context; otherwise, runs of the job fail. + - A unique name for the task. This field is used to refer to this task from other tasks. This field is required and must be unique within its parent job. On Update or Reset, this field is used to reference the tasks to be updated or reset. -- - `parameters` - - Sequence - - Parameters passed to the main method. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. +- - `timeout_seconds` + - Integer + - An optional timeout applied to each run of this job task. A value of `0` means no timeout. -- - `run_as_repl` - - Boolean - - This field is deprecated +- - `webhook_notifications` + - Map + - A collection of system notification IDs to notify when runs of this task begin or complete. The default behavior is to not send any system notifications. See [\_](#jobsnametaskswebhook_notifications). ::: -### jobs._name_.tasks.spark_python_task +### jobs._name_.tasks.alert_task **`Type: Map`** -The task runs a Python file when the `spark_python_task` field is present. +The task evaluates a Databricks alert and sends notifications to subscribers +when the `alert_task` field is present. @@ -4880,34 +4650,31 @@ The task runs a Python file when the `spark_python_task` field is present. - Type - Description -- - `parameters` +- - `alert_id` + - String + - The alert_id is the canonical identifier of the alert. + +- - `subscribers` - Sequence - - Command line parameters passed to the Python file. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. + - The subscribers receive alert evaluation result notifications after the alert task is completed. The number of subscriptions is limited to 100. See [\_](#jobsnametasksalert_tasksubscribers). -- - `python_file` +- - `warehouse_id` - String - - The Python file to be executed. Cloud file URIs (such as dbfs:/, s3:/, adls:/, gcs:/) and workspace paths are supported. For python files stored in the Databricks workspace, the path must be absolute and begin with `/`. For files stored in a remote repository, the path must be relative. This field is required. + - The warehouse_id identifies the warehouse settings used by the alert task. -- - `source` +- - `workspace_path` - String - - Optional location type of the Python file. When set to `WORKSPACE` or not specified, the file will be retrieved from the local Databricks workspace or cloud location (if the `python_file` has a URI format). When set to `GIT`, the Python file will be retrieved from a Git repository defined in `git_source`. * `WORKSPACE`: The Python file is located in a Databricks workspace or at a cloud filesystem URI. * `GIT`: The Python file is located in a remote Git repository. + - The workspace_path is the path to the alert file in the workspace. The path: * must start with "/Workspace" * must be a normalized path. User has to select only one of alert_id or workspace_path to identify the alert. ::: -### jobs._name_.tasks.spark_submit_task +### jobs._name_.tasks.alert_task.subscribers -**`Type: Map`** +**`Type: Sequence`** -(Legacy) The task runs the spark-submit script when the `spark_submit_task` field is present. This task can run only on new clusters and is not compatible with serverless compute. - -In the `new_cluster` specification, `libraries` and `spark_conf` are not supported. Instead, use `--jars` and `--py-files` to add Java and Python libraries and `--conf` to set the Spark configurations. - -`master`, `deploy-mode`, and `executor-cores` are automatically configured by Databricks; you _cannot_ specify them in parameters. - -By default, the Spark submit job uses all available memory (excluding reserved memory for Databricks services). You can set `--driver-memory`, and `--executor-memory` to a smaller value to leave some room for off-heap usage. - -The `--jars`, `--py-files`, `--files` arguments support DBFS and S3 paths. +The subscribers receive alert evaluation result notifications after the alert task is completed. +The number of subscriptions is limited to 100. @@ -4917,18 +4684,23 @@ The `--jars`, `--py-files`, `--files` arguments support DBFS and S3 paths. - Type - Description -- - `parameters` - - Sequence - - Command-line parameters passed to spark submit. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. +- - `destination_id` + - String + - + +- - `user_name` + - String + - A valid workspace email address. ::: -### jobs._name_.tasks.sql_task +### jobs._name_.tasks.clean_rooms_notebook_task **`Type: Map`** -The task runs a SQL query or file, or it refreshes a SQL alert or a legacy SQL dashboard when the `sql_task` field is present. +The task runs a [clean rooms](https://docs.databricks.com/clean-rooms/index.html) notebook +when the `clean_rooms_notebook_task` field is present. @@ -4938,38 +4710,52 @@ The task runs a SQL query or file, or it refreshes a SQL alert or a legacy SQL d - Type - Description -- - `alert` - - Map - - If alert, indicates that this job must refresh a SQL alert. See [\_](#jobsnametaskssql_taskalert). +- - `clean_room_name` + - String + - The clean room that the notebook belongs to. -- - `dashboard` - - Map - - If dashboard, indicates that this job must refresh a SQL dashboard. See [\_](#jobsnametaskssql_taskdashboard). +- - `etag` + - String + - Checksum to validate the freshness of the notebook resource (i.e. the notebook being run is the latest version). It can be fetched by calling the :method:cleanroomassets/get API. -- - `file` +- - `notebook_base_parameters` - Map - - If file, indicates that this job runs a SQL file in a remote Git repository. See [\_](#jobsnametaskssql_taskfile). + - Base parameters to be used for the clean room notebook job. -- - `parameters` - - Map - - Parameters to be used for each run of this job. The SQL alert task does not support custom parameters. +- - `notebook_name` + - String + - Name of the notebook being run. -- - `query` - - Map - - If query, indicates that this job must execute a SQL query. See [\_](#jobsnametaskssql_taskquery). +::: -- - `warehouse_id` + +### jobs._name_.tasks.compute + +**`Type: Map`** + +Task level compute configuration. + + + +:::list-table + +- - Key + - Type + - Description + +- - `hardware_accelerator` - String - - The canonical identifier of the SQL warehouse. Recommended to use with serverless or pro SQL warehouses. Classic SQL warehouses are only supported for SQL alert, dashboard and query tasks and are limited to scheduled single-task jobs. + - Hardware accelerator configuration for Serverless GPU workloads. ::: -### jobs._name_.tasks.sql_task.alert +### jobs._name_.tasks.condition_task **`Type: Map`** -If alert, indicates that this job must refresh a SQL alert. +The task evaluates a condition that can be used to control the execution of other tasks when the `condition_task` field is present. +The condition task does not require a cluster to execute and does not support retries or notifications. @@ -4979,26 +4765,26 @@ If alert, indicates that this job must refresh a SQL alert. - Type - Description -- - `alert_id` +- - `left` - String - - The canonical identifier of the SQL alert. + - The left operand of the condition task. Can be either a string value or a job state or parameter reference. -- - `pause_subscriptions` - - Boolean - - If true, the alert notifications are not sent to subscribers. +- - `op` + - String + - * `EQUAL_TO`, `NOT_EQUAL` operators perform string comparison of their operands. This means that `“12.0” == “12”` will evaluate to `false`. * `GREATER_THAN`, `GREATER_THAN_OR_EQUAL`, `LESS_THAN`, `LESS_THAN_OR_EQUAL` operators perform numeric comparison of their operands. `“12.0” >= “12”` will evaluate to `true`, `“10.0” >= “12”` will evaluate to `false`. The boolean comparison to task values can be implemented with operators `EQUAL_TO`, `NOT_EQUAL`. If a task value was set to a boolean value, it will be serialized to `“true”` or `“false”` for the comparison. -- - `subscriptions` - - Sequence - - If specified, alert notifications are sent to subscribers. See [\_](#jobsnametaskssql_taskalertsubscriptions). +- - `right` + - String + - The right operand of the condition task. Can be either a string value or a job state or parameter reference. ::: -### jobs._name_.tasks.sql_task.alert.subscriptions +### jobs._name_.tasks.dashboard_task -**`Type: Sequence`** +**`Type: Map`** -If specified, alert notifications are sent to subscribers. +The task refreshes a dashboard and sends a snapshot to subscribers. @@ -5008,22 +4794,26 @@ If specified, alert notifications are sent to subscribers. - Type - Description -- - `destination_id` +- - `dashboard_id` - String - - The canonical identifier of the destination to receive email notification. This parameter is mutually exclusive with user_name. You cannot set both destination_id and user_name for subscription notifications. + - -- - `user_name` +- - `subscription` + - Map + - See [\_](#jobsnametasksdashboard_tasksubscription). + +- - `warehouse_id` - String - - The user name to receive the subscription email. This parameter is mutually exclusive with destination_id. You cannot set both destination_id and user_name for subscription notifications. + - Optional: The warehouse id to execute the dashboard with for the schedule. If not specified, the default warehouse of the dashboard will be used. ::: -### jobs._name_.tasks.sql_task.dashboard +### jobs._name_.tasks.dashboard_task.subscription **`Type: Map`** -If dashboard, indicates that this job must refresh a SQL dashboard. + @@ -5035,28 +4825,24 @@ If dashboard, indicates that this job must refresh a SQL dashboard. - - `custom_subject` - String - - Subject of the email sent to subscribers of this task. - -- - `dashboard_id` - - String - - The canonical identifier of the SQL dashboard. + - Optional: Allows users to specify a custom subject line on the email sent to subscribers. -- - `pause_subscriptions` +- - `paused` - Boolean - - If true, the dashboard snapshot is not taken, and emails are not sent to subscribers. + - When true, the subscription will not send emails. -- - `subscriptions` +- - `subscribers` - Sequence - - If specified, dashboard snapshots are sent to subscriptions. See [\_](#jobsnametaskssql_taskdashboardsubscriptions). + - See [\_](#jobsnametasksdashboard_tasksubscriptionsubscribers). ::: -### jobs._name_.tasks.sql_task.dashboard.subscriptions +### jobs._name_.tasks.dashboard_task.subscription.subscribers **`Type: Sequence`** -If specified, dashboard snapshots are sent to subscriptions. + @@ -5068,20 +4854,20 @@ If specified, dashboard snapshots are sent to subscriptions. - - `destination_id` - String - - The canonical identifier of the destination to receive email notification. This parameter is mutually exclusive with user_name. You cannot set both destination_id and user_name for subscription notifications. + - - - `user_name` - String - - The user name to receive the subscription email. This parameter is mutually exclusive with destination_id. You cannot set both destination_id and user_name for subscription notifications. + - ::: -### jobs._name_.tasks.sql_task.file +### jobs._name_.tasks.dbt_task **`Type: Map`** -If file, indicates that this job runs a SQL file in a remote Git repository. +The task runs one or more dbt commands when the `dbt_task` field is present. The dbt task requires both Databricks SQL and the ability to use a serverless or a pro SQL warehouse. @@ -5091,22 +4877,43 @@ If file, indicates that this job runs a SQL file in a remote Git repository. - Type - Description -- - `path` +- - `catalog` - String - - Path of the SQL file. Must be relative if the source is a remote Git repository and absolute for workspace paths. + - Optional name of the catalog to use. The value is the top level in the 3-level namespace of Unity Catalog (catalog / schema / relation). The catalog value can only be specified if a warehouse_id is specified. Requires dbt-databricks >= 1.1.1. + +- - `commands` + - Sequence + - A list of dbt commands to execute. All commands must start with `dbt`. This parameter must not be empty. A maximum of up to 10 commands can be provided. + +- - `profiles_directory` + - String + - Optional (relative) path to the profiles directory. Can only be specified if no warehouse_id is specified. If no warehouse_id is specified and this folder is unset, the root directory is used. + +- - `project_directory` + - String + - Path to the project directory. Optional for Git sourced tasks, in which case if no value is provided, the root of the Git repository is used. + +- - `schema` + - String + - Optional schema to write to. This parameter is only used when a warehouse_id is also provided. If not provided, the `default` schema is used. - - `source` - String - - Optional location type of the SQL file. When set to `WORKSPACE`, the SQL file will be retrieved from the local Databricks workspace. When set to `GIT`, the SQL file will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: SQL file is located in Databricks workspace. * `GIT`: SQL file is located in cloud Git provider. + - Optional location type of the project directory. When set to `WORKSPACE`, the project will be retrieved from the local Databricks workspace. When set to `GIT`, the project will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: Project is located in Databricks workspace. * `GIT`: Project is located in cloud Git provider. + +- - `warehouse_id` + - String + - ID of the SQL warehouse to connect to. If provided, we automatically generate and provide the profile and connection details to dbt. It can be overridden on a per-command basis by using the `--profiles-dir` command line argument. ::: -### jobs._name_.tasks.sql_task.query +### jobs._name_.tasks.depends_on -**`Type: Map`** +**`Type: Sequence`** -If query, indicates that this job must execute a SQL query. +An optional array of objects specifying the dependency graph of the task. All tasks specified in this field must complete before executing this task. The task will run only if the `run_if` condition is true. +The key is `task_key`, and the value is the name assigned to the dependent task. @@ -5116,18 +4923,22 @@ If query, indicates that this job must execute a SQL query. - Type - Description -- - `query_id` +- - `outcome` - String - - The canonical identifier of the SQL query. + - Can only be specified on condition task dependencies. The outcome of the dependent task that must be met for this task to run. + +- - `task_key` + - String + - The name of the task this task depends on. ::: -### jobs._name_.tasks.webhook_notifications +### jobs._name_.tasks.email_notifications **`Type: Map`** -A collection of system notification IDs to notify when runs of this task begin or complete. The default behavior is to not send any system notifications. +An optional set of email addresses that is notified when runs of this task begin or complete as well as when this task is deleted. The default behavior is to not send any emails. @@ -5137,34 +4948,38 @@ A collection of system notification IDs to notify when runs of this task begin o - Type - Description +- - `no_alert_for_skipped_runs` + - Boolean + - This field is deprecated + - - `on_duration_warning_threshold_exceeded` - Sequence - - An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. See [\_](#jobsnametaskswebhook_notificationson_duration_warning_threshold_exceeded). + - A list of email addresses to be notified when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. If no rule for the `RUN_DURATION_SECONDS` metric is specified in the `health` field for the job, notifications are not sent. - - `on_failure` - Sequence - - An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. See [\_](#jobsnametaskswebhook_notificationson_failure). + - A list of email addresses to be notified when a run unsuccessfully completes. A run is considered to have completed unsuccessfully if it ends with an `INTERNAL_ERROR` `life_cycle_state` or a `FAILED`, or `TIMED_OUT` result_state. If this is not specified on job creation, reset, or update the list is empty, and notifications are not sent. - - `on_start` - Sequence - - An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. See [\_](#jobsnametaskswebhook_notificationson_start). + - A list of email addresses to be notified when a run begins. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. - - `on_streaming_backlog_exceeded` - Sequence - - An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. See [\_](#jobsnametaskswebhook_notificationson_streaming_backlog_exceeded). + - A list of email addresses to notify when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. - - `on_success` - Sequence - - An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. See [\_](#jobsnametaskswebhook_notificationson_success). + - A list of email addresses to be notified when a run successfully completes. A run is considered to have completed successfully if it ends with a `TERMINATED` `life_cycle_state` and a `SUCCESS` result_state. If not specified on job creation, reset, or update, the list is empty, and notifications are not sent. ::: -### jobs._name_.tasks.webhook_notifications.on_duration_warning_threshold_exceeded +### jobs._name_.tasks.for_each_task -**`Type: Sequence`** +**`Type: Map`** -An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. +The task executes a nested task for every input provided when the `for_each_task` field is present. @@ -5174,18 +4989,26 @@ An optional list of system notification IDs to call when the duration of a run e - Type - Description -- - `id` +- - `concurrency` + - Integer + - An optional maximum allowed number of concurrent runs of the task. Set this value if you want to be able to execute multiple runs of the task concurrently. + +- - `inputs` - String - - + - Array for task to iterate on. This can be a JSON string or a reference to an array parameter. + +- - `task` + - Map + - Configuration for the task that will be run for each element in the array ::: -### jobs._name_.tasks.webhook_notifications.on_failure +### jobs._name_.tasks.health -**`Type: Sequence`** +**`Type: Map`** -An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. +An optional set of health rules that can be defined for this job. @@ -5195,18 +5018,18 @@ An optional list of system notification IDs to call when the run fails. A maximu - Type - Description -- - `id` - - String - - +- - `rules` + - Sequence + - See [\_](#jobsnametaskshealthrules). ::: -### jobs._name_.tasks.webhook_notifications.on_start +### jobs._name_.tasks.health.rules **`Type: Sequence`** -An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. + @@ -5216,21 +5039,27 @@ An optional list of system notification IDs to call when the run starts. A maxim - Type - Description -- - `id` +- - `metric` - String - - + - Specifies the health metric that is being evaluated for a particular health rule. * `RUN_DURATION_SECONDS`: Expected total time for a run in seconds. * `STREAMING_BACKLOG_BYTES`: An estimate of the maximum bytes of data waiting to be consumed across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_RECORDS`: An estimate of the maximum offset lag across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_SECONDS`: An estimate of the maximum consumer delay across all streams. This metric is in Public Preview. * `STREAMING_BACKLOG_FILES`: An estimate of the maximum number of outstanding files across all streams. This metric is in Public Preview. + +- - `op` + - String + - Specifies the operator used to compare the health metric value with the specified threshold. + +- - `value` + - Integer + - Specifies the threshold value that the health metric should obey to satisfy the health rule. ::: -### jobs._name_.tasks.webhook_notifications.on_streaming_backlog_exceeded +### jobs._name_.tasks.libraries **`Type: Sequence`** -An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. -Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. -Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. -A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. +An optional list of libraries to be installed on the cluster. +The default value is an empty list. @@ -5240,39 +5069,42 @@ A maximum of 3 destinations can be specified for the `on_streaming_backlog_excee - Type - Description -- - `id` - - String - - - -::: - - -### jobs._name_.tasks.webhook_notifications.on_success - -**`Type: Sequence`** +- - `cran` + - Map + - Specification of a CRAN library to be installed as part of the library. See [\_](#jobsnametaskslibrariescran). -An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. +- - `egg` + - String + - This field is deprecated +- - `jar` + - String + - URI of the JAR library to install. Supported URIs include Workspace paths, Unity Catalog Volumes paths, and S3 URIs. For example: `{ "jar": "/Workspace/path/to/library.jar" }`, `{ "jar" : "/Volumes/path/to/library.jar" }` or `{ "jar": "s3://my-bucket/library.jar" }`. If S3 is used, please make sure the cluster has read access on the library. You may need to launch the cluster with an IAM role to access the S3 URI. +- - `maven` + - Map + - Specification of a maven library to be installed. For example: `{ "coordinates": "org.jsoup:jsoup:1.7.2" }`. See [\_](#jobsnametaskslibrariesmaven). -:::list-table +- - `pypi` + - Map + - Specification of a PyPi library to be installed. For example: `{ "package": "simplejson" }`. See [\_](#jobsnametaskslibrariespypi). -- - Key - - Type - - Description +- - `requirements` + - String + - URI of the requirements.txt file to install. Only Workspace paths and Unity Catalog Volumes paths are supported. For example: `{ "requirements": "/Workspace/path/to/requirements.txt" }` or `{ "requirements" : "/Volumes/path/to/requirements.txt" }` -- - `id` +- - `whl` - String - - + - URI of the wheel library to install. Supported URIs include Workspace paths, Unity Catalog Volumes paths, and S3 URIs. For example: `{ "whl": "/Workspace/path/to/library.whl" }`, `{ "whl" : "/Volumes/path/to/library.whl" }` or `{ "whl": "s3://my-bucket/library.whl" }`. If S3 is used, please make sure the cluster has read access on the library. You may need to launch the cluster with an IAM role to access the S3 URI. ::: -### jobs._name_.trigger +### jobs._name_.tasks.libraries.cran **`Type: Map`** -A configuration to trigger a run when certain conditions are met. The default behavior is that the job runs only when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. +Specification of a CRAN library to be installed as part of the library @@ -5282,26 +5114,23 @@ A configuration to trigger a run when certain conditions are met. The default be - Type - Description -- - `file_arrival` - - Map - - File arrival trigger settings. See [\_](#jobsnametriggerfile_arrival). - -- - `pause_status` +- - `package` - String - - Whether this trigger is paused or not. + - The name of the CRAN package to install. -- - `periodic` - - Map - - Periodic trigger settings. See [\_](#jobsnametriggerperiodic). +- - `repo` + - String + - The repository where the package can be found. If not specified, the default CRAN repo is used. ::: -### jobs._name_.trigger.file_arrival +### jobs._name_.tasks.libraries.maven **`Type: Map`** -File arrival trigger settings. +Specification of a maven library to be installed. For example: +`{ "coordinates": "org.jsoup:jsoup:1.7.2" }` @@ -5311,26 +5140,27 @@ File arrival trigger settings. - Type - Description -- - `min_time_between_triggers_seconds` - - Integer - - If set, the trigger starts a run only after the specified amount of time passed since the last time the trigger fired. The minimum allowed value is 60 seconds - -- - `url` +- - `coordinates` - String - - URL to be monitored for file arrivals. The path must point to the root or a subpath of the external location. + - Gradle-style maven coordinates. For example: "org.jsoup:jsoup:1.7.2". -- - `wait_after_last_change_seconds` - - Integer - - If set, the trigger starts a run only after no file activity has occurred for the specified amount of time. This makes it possible to wait for a batch of incoming files to arrive before triggering a run. The minimum allowed value is 60 seconds. +- - `exclusions` + - Sequence + - List of dependences to exclude. For example: `["slf4j:slf4j", "*:hadoop-client"]`. Maven dependency exclusions: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html. + +- - `repo` + - String + - Maven repo to install the Maven package from. If omitted, both Maven Central Repository and Spark Packages are searched. ::: -### jobs._name_.trigger.periodic +### jobs._name_.tasks.libraries.pypi **`Type: Map`** -Periodic trigger settings. +Specification of a PyPi library to be installed. For example: +`{ "package": "simplejson" }` @@ -5340,22 +5170,22 @@ Periodic trigger settings. - Type - Description -- - `interval` - - Integer - - The interval at which the trigger should run. +- - `package` + - String + - The name of the pypi package to install. An optional exact version specification is also supported. Examples: "simplejson" and "simplejson==3.8.0". -- - `unit` +- - `repo` - String - - The unit of time for the interval. + - The repository where the package can be found. If not specified, the default pip index is used. ::: -### jobs._name_.webhook_notifications +### jobs._name_.tasks.new_cluster **`Type: Map`** -A collection of system notification IDs to notify when runs of this job begin or complete. +If new_cluster, a description of a new cluster that is created for each run. @@ -5365,76 +5195,151 @@ A collection of system notification IDs to notify when runs of this job begin or - Type - Description -- - `on_duration_warning_threshold_exceeded` - - Sequence - - An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. See [\_](#jobsnamewebhook_notificationson_duration_warning_threshold_exceeded). +- - `apply_policy_default_values` + - Boolean + - When set to true, fixed and default values from the policy will be used for fields that are omitted. When set to false, only fixed values from the policy will be applied. -- - `on_failure` - - Sequence - - An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. See [\_](#jobsnamewebhook_notificationson_failure). +- - `autoscale` + - Map + - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#jobsnametasksnew_clusterautoscale). -- - `on_start` - - Sequence - - An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. See [\_](#jobsnamewebhook_notificationson_start). +- - `autotermination_minutes` + - Integer + - Automatically terminates the cluster after it is inactive for this time in minutes. If not set, this cluster will not be automatically terminated. If specified, the threshold must be between 10 and 10000 minutes. Users can also set this value to 0 to explicitly disable automatic termination. -- - `on_streaming_backlog_exceeded` - - Sequence - - An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. See [\_](#jobsnamewebhook_notificationson_streaming_backlog_exceeded). +- - `aws_attributes` + - Map + - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clusteraws_attributes). -- - `on_success` - - Sequence - - An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. See [\_](#jobsnamewebhook_notificationson_success). +- - `azure_attributes` + - Map + - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clusterazure_attributes). -::: +- - `cluster_log_conf` + - Map + - The configuration for delivering spark logs to a long-term storage destination. Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#jobsnametasksnew_clustercluster_log_conf). +- - `cluster_name` + - String + - Cluster name requested by the user. This doesn't have to be unique. If not specified at creation, the cluster name will be an empty string. For job clusters, the cluster name is automatically set based on the job and job run IDs. -### jobs._name_.webhook_notifications.on_duration_warning_threshold_exceeded +- - `custom_tags` + - Map + - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags -**`Type: Sequence`** +- - `data_security_mode` + - String + - Data security mode decides what data governance model to use when accessing data from a cluster. The following modes can only be used when `kind = CLASSIC_PREVIEW`. * `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration. * `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`. * `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`. The following modes can be used regardless of `kind`. * `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode. * `SINGLE_USER`: A secure cluster that can only be exclusively used by a single user specified in `single_user_name`. Most programming languages, cluster features and data governance features are available in this mode. * `USER_ISOLATION`: A secure cluster that can be shared by multiple users. Cluster users are fully isolated so that they cannot see each other's data and credentials. Most data governance features are supported in this mode. But programming languages and cluster features might be limited. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: * `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters. * `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters. * `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters. * `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled. -An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. +- - `docker_image` + - Map + - See [\_](#jobsnametasksnew_clusterdocker_image). +- - `driver_instance_pool_id` + - String + - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. +- - `driver_node_type_flexibility` + - Map + - Flexible node type configuration for the driver node. See [\_](#jobsnametasksnew_clusterdriver_node_type_flexibility). -:::list-table +- - `driver_node_type_id` + - String + - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. This field, along with node_type_id, should not be set if virtual_cluster_size is set. If both driver_node_type_id, node_type_id, and virtual_cluster_size are specified, driver_node_type_id and node_type_id take precedence. -- - Key - - Type - - Description +- - `enable_elastic_disk` + - Boolean + - Autoscaling Local Storage: when enabled, this cluster will dynamically acquire additional disk space when its Spark workers are running low on disk space. -- - `id` - - String - - +- - `enable_local_disk_encryption` + - Boolean + - Whether to enable LUKS on cluster VMs' local disks -::: +- - `gcp_attributes` + - Map + - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#jobsnametasksnew_clustergcp_attributes). +- - `init_scripts` + - Sequence + - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#jobsnametasksnew_clusterinit_scripts). -### jobs._name_.webhook_notifications.on_failure +- - `instance_pool_id` + - String + - The optional ID of the instance pool to which the cluster belongs. -**`Type: Sequence`** +- - `is_single_node` + - Boolean + - This field can only be used when `kind = CLASSIC_PREVIEW`. When set to true, Databricks will automatically set single node related `custom_tags`, `spark_conf`, and `num_workers` -An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. +- - `kind` + - String + - +- - `node_type_id` + - String + - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. +- - `num_workers` + - Integer + - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. -:::list-table +- - `policy_id` + - String + - The ID of the cluster policy used to create the cluster if applicable. -- - Key - - Type - - Description +- - `remote_disk_throughput` + - Integer + - If set, what the configurable throughput (in Mb/s) for the remote disk is. Currently only supported for GCP HYPERDISK_BALANCED disks. -- - `id` +- - `runtime_engine` - String - +- - `single_user_name` + - String + - Single user name if data_security_mode is `SINGLE_USER` + +- - `spark_conf` + - Map + - An object containing a set of optional, user-specified Spark configuration key-value pairs. Users can also pass in a string of extra JVM options to the driver and the executors via `spark.driver.extraJavaOptions` and `spark.executor.extraJavaOptions` respectively. + +- - `spark_env_vars` + - Map + - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` + +- - `spark_version` + - String + - The Spark version of the cluster, e.g. `3.3.x-scala2.11`. A list of available Spark versions can be retrieved by using the :method:clusters/sparkVersions API call. + +- - `ssh_public_keys` + - Sequence + - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. + +- - `total_initial_remote_disk_size` + - Integer + - If set, what the total initial volume size (in GB) of the remote disks should be. Currently only supported for GCP HYPERDISK_BALANCED disks. + +- - `use_ml_runtime` + - Boolean + - This field can only be used when `kind = CLASSIC_PREVIEW`. `effective_spark_version` is determined by `spark_version` (DBR release), this field `use_ml_runtime`, and whether `node_type_id` is gpu node or not. + +- - `worker_node_type_flexibility` + - Map + - Flexible node type configuration for worker nodes. See [\_](#jobsnametasksnew_clusterworker_node_type_flexibility). + +- - `workload_type` + - Map + - Cluster Attributes showing for clusters workload types. See [\_](#jobsnametasksnew_clusterworkload_type). + ::: -### jobs._name_.webhook_notifications.on_start +### jobs._name_.tasks.new_cluster.autoscale -**`Type: Sequence`** +**`Type: Map`** -An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. +Parameters needed in order to automatically scale clusters up and down based on load. +Note: autoscaling works best with DB runtime versions 3.0 or later. @@ -5444,21 +5349,23 @@ An optional list of system notification IDs to call when the run starts. A maxim - Type - Description -- - `id` - - String - - +- - `max_workers` + - Integer + - The maximum number of workers to which the cluster can scale up when overloaded. Note that `max_workers` must be strictly greater than `min_workers`. + +- - `min_workers` + - Integer + - The minimum number of workers to which the cluster can scale down when underutilized. It is also the initial number of workers the cluster will have after creation. ::: -### jobs._name_.webhook_notifications.on_streaming_backlog_exceeded +### jobs._name_.tasks.new_cluster.aws_attributes -**`Type: Sequence`** +**`Type: Map`** -An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. -Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. -Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. -A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. +Attributes related to clusters running on Amazon Web Services. +If not specified at cluster creation, a set of default values will be used. @@ -5468,45 +5375,56 @@ A maximum of 3 destinations can be specified for the `on_streaming_backlog_excee - Type - Description -- - `id` +- - `availability` - String - - - -::: + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. +- - `ebs_volume_count` + - Integer + - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. -### jobs._name_.webhook_notifications.on_success +- - `ebs_volume_iops` + - Integer + - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. -**`Type: Sequence`** +- - `ebs_volume_size` + - Integer + - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. -An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. +- - `ebs_volume_throughput` + - Integer + - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. +- - `ebs_volume_type` + - String + - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. -:::list-table +- - `instance_profile_arn` + - String + - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. -- - Key - - Type - - Description +- - `spot_bid_price_percent` + - Integer + - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. -- - `id` +- - `zone_id` - String - - + - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, the zone "auto" will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. ::: -## model_serving_endpoints +### jobs._name_.tasks.new_cluster.azure_attributes **`Type: Map`** -The model_serving_endpoint resource allows you to define [model serving endpoints](/api/workspace/servingendpoints/create). See [_](/machine-learning/model-serving/manage-serving-endpoints.md). +Attributes related to clusters running on Microsoft Azure. +If not specified at cluster creation, a set of default values will be used. -```yaml -model_serving_endpoints: - : - : -``` :::list-table @@ -5515,82 +5433,30 @@ model_serving_endpoints: - Type - Description -- - `ai_gateway` - - Map - - The AI Gateway configuration for the serving endpoint. NOTE: External model, provisioned throughput, and pay-per-token endpoints are fully supported; agent endpoints currently only support inference tables. See [\_](#model_serving_endpointsnameai_gateway). - -- - `budget_policy_id` - - String - - The budget policy to be applied to the serving endpoint. - -- - `config` - - Map - - The core config of the serving endpoint. See [\_](#model_serving_endpointsnameconfig). - -- - `description` +- - `availability` - String - - + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. -- - `email_notifications` - - Map - - Email notification settings. See [\_](#model_serving_endpointsnameemail_notifications). +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. -- - `lifecycle` +- - `log_analytics_info` - Map - - 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. See [\_](#model_serving_endpointsnamelifecycle). - -- - `name` - - String - - The name of the serving endpoint. This field is required and must be unique across a Databricks workspace. An endpoint name can consist of alphanumeric characters, dashes, and underscores. - -- - `permissions` - - Sequence - - See [\_](#model_serving_endpointsnamepermissions). - -- - `rate_limits` - - Sequence - - This field is deprecated - -- - `route_optimized` - - Boolean - - Enable route optimization for the serving endpoint. + - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#jobsnametasksnew_clusterazure_attributeslog_analytics_info). -- - `tags` - - Sequence - - Tags to be attached to the serving endpoint and automatically propagated to billing logs. See [\_](#model_serving_endpointsnametags). +- - `spot_bid_max_price` + - Any + - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. ::: -**Example** - -The following example defines a Unity Catalog model serving endpoint: - -```yaml -resources: - model_serving_endpoints: - uc_model_serving_endpoint: - name: "uc-model-endpoint" - config: - served_entities: - - entity_name: "myCatalog.mySchema.my-ads-model" - entity_version: "10" - workload_size: "Small" - scale_to_zero_enabled: "true" - traffic_config: - routes: - - served_model_name: "my-ads-model-10" - traffic_percentage: "100" - tags: - - key: "team" - value: "data science" -``` - -### model_serving_endpoints._name_.ai_gateway +### jobs._name_.tasks.new_cluster.azure_attributes.log_analytics_info **`Type: Map`** -The AI Gateway configuration for the serving endpoint. NOTE: External model, provisioned throughput, and pay-per-token endpoints are fully supported; agent endpoints currently only support inference tables. +Defines values necessary to configure and run Azure Log Analytics agent @@ -5600,35 +5466,26 @@ The AI Gateway configuration for the serving endpoint. NOTE: External model, pro - Type - Description -- - `fallback_config` - - Map - - Configuration for traffic fallback which auto fallbacks to other served entities if the request to a served entity fails with certain error codes, to increase availability. See [\_](#model_serving_endpointsnameai_gatewayfallback_config). - -- - `guardrails` - - Map - - Configuration for AI Guardrails to prevent unwanted data and unsafe data in requests and responses. See [\_](#model_serving_endpointsnameai_gatewayguardrails). - -- - `inference_table_config` - - Map - - Configuration for payload logging using inference tables. Use these tables to monitor and audit data being sent to and received from model APIs and to improve model quality. See [\_](#model_serving_endpointsnameai_gatewayinference_table_config). - -- - `rate_limits` - - Sequence - - Configuration for rate limits which can be set to limit endpoint traffic. See [\_](#model_serving_endpointsnameai_gatewayrate_limits). +- - `log_analytics_primary_key` + - String + - The primary key for the Azure Log Analytics agent configuration -- - `usage_tracking_config` - - Map - - Configuration to enable usage tracking using system tables. These tables allow you to monitor operational usage on endpoints and their associated costs. See [\_](#model_serving_endpointsnameai_gatewayusage_tracking_config). +- - `log_analytics_workspace_id` + - String + - The workspace ID for the Azure Log Analytics agent configuration ::: -### model_serving_endpoints._name_.ai_gateway.fallback_config +### jobs._name_.tasks.new_cluster.cluster_log_conf **`Type: Map`** -Configuration for traffic fallback which auto fallbacks to other served entities if the request to a served -entity fails with certain error codes, to increase availability. +The configuration for delivering spark logs to a long-term storage destination. +Three kinds of destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be specified +for one cluster. If the conf is given, the logs will be delivered to the destination every +`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while +the destination of executor logs is `$destination/$clusterId/executor`. @@ -5638,18 +5495,27 @@ entity fails with certain error codes, to increase availability. - Type - Description -- - `enabled` - - Boolean - - Whether to enable traffic fallback. When a served entity in the serving endpoint returns specific error codes (e.g. 500), the request will automatically be round-robin attempted with other served entities in the same endpoint, following the order of served entity list, until a successful response is returned. If all attempts fail, return the last response with the error code. +- - `dbfs` + - Map + - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#jobsnametasksnew_clustercluster_log_confdbfs). + +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnametasksnew_clustercluster_log_confs3). + +- - `volumes` + - Map + - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#jobsnametasksnew_clustercluster_log_confvolumes). ::: -### model_serving_endpoints._name_.ai_gateway.guardrails +### jobs._name_.tasks.new_cluster.cluster_log_conf.dbfs **`Type: Map`** -Configuration for AI Guardrails to prevent unwanted data and unsafe data in requests and responses. +destination needs to be provided. e.g. +`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` @@ -5659,22 +5525,21 @@ Configuration for AI Guardrails to prevent unwanted data and unsafe data in requ - Type - Description -- - `input` - - Map - - Configuration for input guardrail filters. See [\_](#model_serving_endpointsnameai_gatewayguardrailsinput). - -- - `output` - - Map - - Configuration for output guardrail filters. See [\_](#model_serving_endpointsnameai_gatewayguardrailsoutput). +- - `destination` + - String + - dbfs destination, e.g. `dbfs:/my/path` ::: -### model_serving_endpoints._name_.ai_gateway.guardrails.input +### jobs._name_.tasks.new_cluster.cluster_log_conf.s3 **`Type: Map`** -Configuration for input guardrail filters. +destination and either the region or endpoint need to be provided. e.g. +`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. @@ -5684,30 +5549,43 @@ Configuration for input guardrail filters. - Type - Description -- - `invalid_keywords` - - Sequence - - This field is deprecated +- - `canned_acl` + - String + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. -- - `pii` - - Map - - Configuration for guardrail PII filter. See [\_](#model_serving_endpointsnameai_gatewayguardrailsinputpii). +- - `destination` + - String + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. -- - `safety` +- - `enable_encryption` - Boolean - - Indicates whether the safety filter is enabled. + - (Optional) Flag to enable server side encryption, `false` by default. -- - `valid_topics` - - Sequence - - This field is deprecated +- - `encryption_type` + - String + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. + +- - `endpoint` + - String + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + +- - `kms_key` + - String + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + +- - `region` + - String + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. ::: -### model_serving_endpoints._name_.ai_gateway.guardrails.input.pii +### jobs._name_.tasks.new_cluster.cluster_log_conf.volumes **`Type: Map`** -Configuration for guardrail PII filter. +destination needs to be provided, e.g. +`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` @@ -5717,18 +5595,18 @@ Configuration for guardrail PII filter. - Type - Description -- - `behavior` +- - `destination` - String - - Configuration for input guardrail filters. + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` ::: -### model_serving_endpoints._name_.ai_gateway.guardrails.output +### jobs._name_.tasks.new_cluster.docker_image **`Type: Map`** -Configuration for output guardrail filters. + @@ -5738,30 +5616,22 @@ Configuration for output guardrail filters. - Type - Description -- - `invalid_keywords` - - Sequence - - This field is deprecated - -- - `pii` +- - `basic_auth` - Map - - Configuration for guardrail PII filter. See [\_](#model_serving_endpointsnameai_gatewayguardrailsoutputpii). - -- - `safety` - - Boolean - - Indicates whether the safety filter is enabled. + - See [\_](#jobsnametasksnew_clusterdocker_imagebasic_auth). -- - `valid_topics` - - Sequence - - This field is deprecated +- - `url` + - String + - URL of the docker image. ::: -### model_serving_endpoints._name_.ai_gateway.guardrails.output.pii +### jobs._name_.tasks.new_cluster.docker_image.basic_auth **`Type: Map`** -Configuration for guardrail PII filter. + @@ -5771,19 +5641,22 @@ Configuration for guardrail PII filter. - Type - Description -- - `behavior` +- - `password` - String - - Configuration for input guardrail filters. + - Password of the user + +- - `username` + - String + - Name of the user ::: -### model_serving_endpoints._name_.ai_gateway.inference_table_config +### jobs._name_.tasks.new_cluster.driver_node_type_flexibility **`Type: Map`** -Configuration for payload logging using inference tables. -Use these tables to monitor and audit data being sent to and received from model APIs and to improve model quality. +Flexible node type configuration for the driver node. @@ -5793,30 +5666,66 @@ Use these tables to monitor and audit data being sent to and received from model - Type - Description -- - `catalog_name` +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. + +::: + + +### jobs._name_.tasks.new_cluster.gcp_attributes + +**`Type: Map`** + +Attributes related to clusters running on Google Cloud Platform. +If not specified at cluster creation, a set of default values will be used. + + + +:::list-table + +- - Key + - Type + - Description + +- - `availability` - String - - The name of the catalog in Unity Catalog. Required when enabling inference tables. NOTE: On update, you have to disable inference table first in order to change the catalog name. + - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. -- - `enabled` - - Boolean - - Indicates whether the inference table is enabled. +- - `boot_disk_size` + - Integer + - Boot disk size in GB -- - `schema_name` +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + +- - `google_service_account` - String - - The name of the schema in Unity Catalog. Required when enabling inference tables. NOTE: On update, you have to disable inference table first in order to change the schema name. + - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. -- - `table_name_prefix` +- - `local_ssd_count` + - Integer + - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. + +- - `use_preemptible_executors` + - Boolean + - This field is deprecated + +- - `zone_id` - String - - The prefix of the table in Unity Catalog. NOTE: On update, you have to disable inference table first in order to change the prefix name. + - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. ::: -### model_serving_endpoints._name_.ai_gateway.rate_limits +### jobs._name_.tasks.new_cluster.init_scripts **`Type: Sequence`** -Configuration for rate limits which can be set to limit endpoint traffic. +The configuration for storing init scripts. Any number of destinations can be specified. +The scripts are executed sequentially in the order provided. +If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. @@ -5826,35 +5735,42 @@ Configuration for rate limits which can be set to limit endpoint traffic. - Type - Description -- - `calls` - - Integer - - Used to specify how many calls are allowed for a key within the renewal_period. +- - `abfss` + - Map + - Contains the Azure Data Lake Storage destination path. See [\_](#jobsnametasksnew_clusterinit_scriptsabfss). -- - `key` - - String - - Key field for a rate limit. Currently, 'user', 'user_group, 'service_principal', and 'endpoint' are supported, with 'endpoint' being the default if not specified. +- - `dbfs` + - Map + - This field is deprecated -- - `principal` - - String - - Principal field for a user, user group, or service principal to apply rate limiting to. Accepts a user email, group name, or service principal application ID. +- - `file` + - Map + - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsfile). -- - `renewal_period` - - String - - Renewal period field for a rate limit. Currently, only 'minute' is supported. +- - `gcs` + - Map + - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsgcs). -- - `tokens` - - Integer - - Used to specify how many tokens are allowed for a key within the renewal_period. +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#jobsnametasksnew_clusterinit_scriptss3). + +- - `volumes` + - Map + - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsvolumes). + +- - `workspace` + - Map + - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#jobsnametasksnew_clusterinit_scriptsworkspace). ::: -### model_serving_endpoints._name_.ai_gateway.usage_tracking_config +### jobs._name_.tasks.new_cluster.init_scripts.abfss **`Type: Map`** -Configuration to enable usage tracking using system tables. -These tables allow you to monitor operational usage on endpoints and their associated costs. +Contains the Azure Data Lake Storage destination path @@ -5864,18 +5780,19 @@ These tables allow you to monitor operational usage on endpoints and their assoc - Type - Description -- - `enabled` - - Boolean - - Whether to enable usage tracking. +- - `destination` + - String + - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. ::: -### model_serving_endpoints._name_.config +### jobs._name_.tasks.new_cluster.init_scripts.file **`Type: Map`** -The core config of the serving endpoint. +destination needs to be provided, e.g. +`{ "file": { "destination": "file:/my/local/file.sh" } }` @@ -5885,33 +5802,19 @@ The core config of the serving endpoint. - Type - Description -- - `auto_capture_config` - - Map - - Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog. Note: this field is deprecated for creating new provisioned throughput endpoints, or updating existing provisioned throughput endpoints that never have inference table configured; in these cases please use AI Gateway to manage inference tables. See [\_](#model_serving_endpointsnameconfigauto_capture_config). - -- - `served_entities` - - Sequence - - The list of served entities under the serving endpoint config. See [\_](#model_serving_endpointsnameconfigserved_entities). - -- - `served_models` - - Sequence - - (Deprecated, use served_entities instead) The list of served models under the serving endpoint config. See [\_](#model_serving_endpointsnameconfigserved_models). - -- - `traffic_config` - - Map - - The traffic configuration associated with the serving endpoint config. See [\_](#model_serving_endpointsnameconfigtraffic_config). +- - `destination` + - String + - local file destination, e.g. `file:/my/local/file.sh` ::: -### model_serving_endpoints._name_.config.auto_capture_config +### jobs._name_.tasks.new_cluster.init_scripts.gcs **`Type: Map`** -Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog. -Note: this field is deprecated for creating new provisioned throughput endpoints, -or updating existing provisioned throughput endpoints that never have inference table configured; -in these cases please use AI Gateway to manage inference tables. +destination needs to be provided, e.g. +`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` @@ -5921,30 +5824,21 @@ in these cases please use AI Gateway to manage inference tables. - Type - Description -- - `catalog_name` - - String - - The name of the catalog in Unity Catalog. NOTE: On update, you cannot change the catalog name if the inference table is already enabled. - -- - `enabled` - - Boolean - - Indicates whether the inference table is enabled. - -- - `schema_name` - - String - - The name of the schema in Unity Catalog. NOTE: On update, you cannot change the schema name if the inference table is already enabled. - -- - `table_name_prefix` +- - `destination` - String - - The prefix of the table in Unity Catalog. NOTE: On update, you cannot change the prefix name if the inference table is already enabled. + - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` ::: -### model_serving_endpoints._name_.config.served_entities +### jobs._name_.tasks.new_cluster.init_scripts.s3 -**`Type: Sequence`** +**`Type: Map`** -The list of served entities under the serving endpoint config. +destination and either the region or endpoint need to be provided. e.g. +`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. @@ -5954,70 +5848,43 @@ The list of served entities under the serving endpoint config. - Type - Description -- - `entity_name` +- - `canned_acl` - String - - The name of the entity to be served. The entity may be a model in the Databricks Model Registry, a model in the Unity Catalog (UC), or a function of type FEATURE_SPEC in the UC. If it is a UC object, the full name of the object should be given in the form of **catalog_name.schema_name.model_name**. + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. -- - `entity_version` +- - `destination` - String - - + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. -- - `environment_vars` - - Map - - An object containing a set of optional, user-specified environment variable key-value pairs used for serving this entity. Note: this is an experimental feature and subject to change. Example entity environment variables that refer to Databricks secrets: `{"OPENAI_API_KEY": "{{secrets/my_scope/my_key}}", "DATABRICKS_TOKEN": "{{secrets/my_scope2/my_key2}}"}` +- - `enable_encryption` + - Boolean + - (Optional) Flag to enable server side encryption, `false` by default. -- - `external_model` - - Map - - The external model to be served. NOTE: Only one of external_model and (entity_name, entity_version, workload_size, workload_type, and scale_to_zero_enabled) can be specified with the latter set being used for custom model serving for a Databricks registered model. For an existing endpoint with external_model, it cannot be updated to an endpoint without external_model. If the endpoint is created without external_model, users cannot update it to add external_model later. The task type of all external models within an endpoint must be the same. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_model). - -- - `instance_profile_arn` +- - `encryption_type` - String - - ARN of the instance profile that the served entity uses to access AWS resources. - -- - `max_provisioned_concurrency` - - Integer - - The maximum provisioned concurrency that the endpoint can scale up to. Do not use if workload_size is specified. - -- - `max_provisioned_throughput` - - Integer - - The maximum tokens per second that the endpoint can scale up to. - -- - `min_provisioned_concurrency` - - Integer - - The minimum provisioned concurrency that the endpoint can scale down to. Do not use if workload_size is specified. - -- - `min_provisioned_throughput` - - Integer - - The minimum tokens per second that the endpoint can scale down to. + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. -- - `name` +- - `endpoint` - String - - The name of a served entity. It must be unique across an endpoint. A served entity name can consist of alphanumeric characters, dashes, and underscores. If not specified for an external model, this field defaults to external_model.name, with '.' and ':' replaced with '-', and if not specified for other entities, it defaults to entity_name-entity_version. - -- - `provisioned_model_units` - - Integer - - The number of model units provisioned. - -- - `scale_to_zero_enabled` - - Boolean - - Whether the compute resources for the served entity should scale down to zero. + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. -- - `workload_size` +- - `kms_key` - String - - The workload size of the served entity. The workload size corresponds to a range of provisioned concurrency that the compute autoscales between. A single unit of provisioned concurrency can process one request at a time. Valid workload sizes are "Small" (4 - 4 provisioned concurrency), "Medium" (8 - 16 provisioned concurrency), and "Large" (16 - 64 provisioned concurrency). Additional custom workload sizes can also be used when available in the workspace. If scale-to-zero is enabled, the lower bound of the provisioned concurrency for each workload size is 0. Do not use if min_provisioned_concurrency and max_provisioned_concurrency are specified. + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. -- - `workload_type` +- - `region` - String - - The workload type of the served entity. The workload type selects which type of compute to use in the endpoint. The default value for this parameter is "CPU". For deep learning workloads, GPU acceleration is available by selecting workload types like GPU_SMALL and others. See the available [GPU types](https://docs.databricks.com/en/machine-learning/model-serving/create-manage-serving-endpoints.html#gpu-workload-types). + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. ::: -### model_serving_endpoints._name_.config.served_entities.external_model +### jobs._name_.tasks.new_cluster.init_scripts.volumes **`Type: Map`** -The external model to be served. NOTE: Only one of external_model and (entity_name, entity_version, workload_size, workload_type, and scale_to_zero_enabled) can be specified with the latter set being used for custom model serving for a Databricks registered model. For an existing endpoint with external_model, it cannot be updated to an endpoint without external_model. If the endpoint is created without external_model, users cannot update it to add external_model later. The task type of all external models within an endpoint must be the same. +destination needs to be provided. e.g. +`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` @@ -6027,62 +5894,40 @@ The external model to be served. NOTE: Only one of external_model and (entity_na - Type - Description -- - `ai21labs_config` - - Map - - AI21Labs Config. Only required if the provider is 'ai21labs'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelai21labs_config). - -- - `amazon_bedrock_config` - - Map - - Amazon Bedrock Config. Only required if the provider is 'amazon-bedrock'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelamazon_bedrock_config). +- - `destination` + - String + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` -- - `anthropic_config` - - Map - - Anthropic Config. Only required if the provider is 'anthropic'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelanthropic_config). +::: -- - `cohere_config` - - Map - - Cohere Config. Only required if the provider is 'cohere'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcohere_config). -- - `custom_provider_config` - - Map - - Custom Provider Config. Only required if the provider is 'custom'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_config). +### jobs._name_.tasks.new_cluster.init_scripts.workspace -- - `databricks_model_serving_config` - - Map - - Databricks Model Serving Config. Only required if the provider is 'databricks-model-serving'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modeldatabricks_model_serving_config). +**`Type: Map`** -- - `google_cloud_vertex_ai_config` - - Map - - Google Cloud Vertex AI Config. Only required if the provider is 'google-cloud-vertex-ai'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelgoogle_cloud_vertex_ai_config). +destination needs to be provided, e.g. +`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` -- - `name` - - String - - The name of the external model. -- - `openai_config` - - Map - - OpenAI Config. Only required if the provider is 'openai'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelopenai_config). -- - `palm_config` - - Map - - PaLM Config. Only required if the provider is 'palm'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelpalm_config). +:::list-table -- - `provider` - - String - - The name of the provider for the external model. Currently, the supported providers are 'ai21labs', 'anthropic', 'amazon-bedrock', 'cohere', 'databricks-model-serving', 'google-cloud-vertex-ai', 'openai', 'palm', and 'custom'. +- - Key + - Type + - Description -- - `task` +- - `destination` - String - - The task type of the external model. + - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` ::: -### model_serving_endpoints._name_.config.served_entities.external_model.ai21labs_config +### jobs._name_.tasks.new_cluster.worker_node_type_flexibility **`Type: Map`** -AI21Labs Config. Only required if the provider is 'ai21labs'. +Flexible node type configuration for worker nodes. @@ -6092,22 +5937,18 @@ AI21Labs Config. Only required if the provider is 'ai21labs'. - Type - Description -- - `ai21labs_api_key` - - String - - The Databricks secret key reference for an AI21 Labs API key. If you prefer to paste your API key directly, see `ai21labs_api_key_plaintext`. You must provide an API key using one of the following fields: `ai21labs_api_key` or `ai21labs_api_key_plaintext`. - -- - `ai21labs_api_key_plaintext` - - String - - An AI21 Labs API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `ai21labs_api_key`. You must provide an API key using one of the following fields: `ai21labs_api_key` or `ai21labs_api_key_plaintext`. +- - `alternate_node_type_ids` + - Sequence + - A list of node type IDs to use as fallbacks when the primary node type is unavailable. ::: -### model_serving_endpoints._name_.config.served_entities.external_model.amazon_bedrock_config +### jobs._name_.tasks.new_cluster.workload_type **`Type: Map`** -Amazon Bedrock Config. Only required if the provider is 'amazon-bedrock'. +Cluster Attributes showing for clusters workload types. @@ -6117,42 +5958,18 @@ Amazon Bedrock Config. Only required if the provider is 'amazon-bedrock'. - Type - Description -- - `aws_access_key_id` - - String - - The Databricks secret key reference for an AWS access key ID with permissions to interact with Bedrock services. If you prefer to paste your API key directly, see `aws_access_key_id_plaintext`. You must provide an API key using one of the following fields: `aws_access_key_id` or `aws_access_key_id_plaintext`. - -- - `aws_access_key_id_plaintext` - - String - - An AWS access key ID with permissions to interact with Bedrock services provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `aws_access_key_id`. You must provide an API key using one of the following fields: `aws_access_key_id` or `aws_access_key_id_plaintext`. - -- - `aws_region` - - String - - The AWS region to use. Bedrock has to be enabled there. - -- - `aws_secret_access_key` - - String - - The Databricks secret key reference for an AWS secret access key paired with the access key ID, with permissions to interact with Bedrock services. If you prefer to paste your API key directly, see `aws_secret_access_key_plaintext`. You must provide an API key using one of the following fields: `aws_secret_access_key` or `aws_secret_access_key_plaintext`. - -- - `aws_secret_access_key_plaintext` - - String - - An AWS secret access key paired with the access key ID, with permissions to interact with Bedrock services provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `aws_secret_access_key`. You must provide an API key using one of the following fields: `aws_secret_access_key` or `aws_secret_access_key_plaintext`. - -- - `bedrock_provider` - - String - - The underlying provider in Amazon Bedrock. Supported values (case insensitive) include: Anthropic, Cohere, AI21Labs, Amazon. - -- - `instance_profile_arn` - - String - - ARN of the instance profile that the external model will use to access AWS resources. You must authenticate using an instance profile or access keys. If you prefer to authenticate using access keys, see `aws_access_key_id`, `aws_access_key_id_plaintext`, `aws_secret_access_key` and `aws_secret_access_key_plaintext`. +- - `clients` + - Map + - defined what type of clients can use the cluster. E.g. Notebooks, Jobs. See [\_](#jobsnametasksnew_clusterworkload_typeclients). ::: -### model_serving_endpoints._name_.config.served_entities.external_model.anthropic_config +### jobs._name_.tasks.new_cluster.workload_type.clients **`Type: Map`** -Anthropic Config. Only required if the provider is 'anthropic'. +defined what type of clients can use the cluster. E.g. Notebooks, Jobs @@ -6162,22 +5979,22 @@ Anthropic Config. Only required if the provider is 'anthropic'. - Type - Description -- - `anthropic_api_key` - - String - - The Databricks secret key reference for an Anthropic API key. If you prefer to paste your API key directly, see `anthropic_api_key_plaintext`. You must provide an API key using one of the following fields: `anthropic_api_key` or `anthropic_api_key_plaintext`. +- - `jobs` + - Boolean + - With jobs set, the cluster can be used for jobs -- - `anthropic_api_key_plaintext` - - String - - The Anthropic API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `anthropic_api_key`. You must provide an API key using one of the following fields: `anthropic_api_key` or `anthropic_api_key_plaintext`. +- - `notebooks` + - Boolean + - With notebooks set, this cluster can be used for notebooks ::: -### model_serving_endpoints._name_.config.served_entities.external_model.cohere_config +### jobs._name_.tasks.notebook_task **`Type: Map`** -Cohere Config. Only required if the provider is 'cohere'. +The task runs a notebook when the `notebook_task` field is present. @@ -6187,26 +6004,30 @@ Cohere Config. Only required if the provider is 'cohere'. - Type - Description -- - `cohere_api_base` +- - `base_parameters` + - Map + - Base parameters to be used for each run of this job. If the run is initiated by a call to :method:jobs/run Now with parameters specified, the two parameters maps are merged. If the same key is specified in `base_parameters` and in `run-now`, the value from `run-now` is used. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. If the notebook takes a parameter that is not specified in the job’s `base_parameters` or the `run-now` override parameters, the default value from the notebook is used. Retrieve these parameters in a notebook using [dbutils.widgets.get](https://docs.databricks.com/dev-tools/databricks-utils.html#dbutils-widgets). The JSON representation of this field cannot exceed 1MB. + +- - `notebook_path` - String - - This is an optional field to provide a customized base URL for the Cohere API. If left unspecified, the standard Cohere base URL is used. + - The path of the notebook to be run in the Databricks workspace or remote repository. For notebooks stored in the Databricks workspace, the path must be absolute and begin with a slash. For notebooks stored in a remote repository, the path must be relative. This field is required. -- - `cohere_api_key` +- - `source` - String - - The Databricks secret key reference for a Cohere API key. If you prefer to paste your API key directly, see `cohere_api_key_plaintext`. You must provide an API key using one of the following fields: `cohere_api_key` or `cohere_api_key_plaintext`. + - Optional location type of the notebook. When set to `WORKSPACE`, the notebook will be retrieved from the local Databricks workspace. When set to `GIT`, the notebook will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: Notebook is located in Databricks workspace. * `GIT`: Notebook is located in cloud Git provider. -- - `cohere_api_key_plaintext` +- - `warehouse_id` - String - - The Cohere API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `cohere_api_key`. You must provide an API key using one of the following fields: `cohere_api_key` or `cohere_api_key_plaintext`. + - Optional `warehouse_id` to run the notebook on a SQL warehouse. Classic SQL warehouses are NOT supported, please use serverless or pro SQL warehouses. Note that SQL warehouses only support SQL cells; if the notebook contains non-SQL cells, the run will fail. ::: -### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config +### jobs._name_.tasks.notification_settings **`Type: Map`** -Custom Provider Config. Only required if the provider is 'custom'. +Optional notification settings that are used when sending notifications to each of the `email_notifications` and `webhook_notifications` for this task. @@ -6216,27 +6037,26 @@ Custom Provider Config. Only required if the provider is 'custom'. - Type - Description -- - `api_key_auth` - - Map - - This is a field to provide API key authentication for the custom provider API. You can only specify one authentication method. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_configapi_key_auth). +- - `alert_on_last_attempt` + - Boolean + - If true, do not send notifications to recipients specified in `on_start` for the retried runs and do not send notifications to recipients specified in `on_failure` until the last retry of the run. -- - `bearer_token_auth` - - Map - - This is a field to provide bearer token authentication for the custom provider API. You can only specify one authentication method. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_configbearer_token_auth). +- - `no_alert_for_canceled_runs` + - Boolean + - If true, do not send notifications to recipients specified in `on_failure` if the run is canceled. -- - `custom_provider_url` - - String - - This is a field to provide the URL of the custom provider API. +- - `no_alert_for_skipped_runs` + - Boolean + - If true, do not send notifications to recipients specified in `on_failure` if the run is skipped. ::: -### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config.api_key_auth +### jobs._name_.tasks.pipeline_task **`Type: Map`** -This is a field to provide API key authentication for the custom provider API. -You can only specify one authentication method. +The task triggers a pipeline update when the `pipeline_task` field is present. Only pipelines configured to use triggered more are supported. @@ -6246,27 +6066,22 @@ You can only specify one authentication method. - Type - Description -- - `key` - - String - - The name of the API key parameter used for authentication. - -- - `value` - - String - - The Databricks secret key reference for an API Key. If you prefer to paste your token directly, see `value_plaintext`. +- - `full_refresh` + - Boolean + - If true, triggers a full refresh on the delta live table. -- - `value_plaintext` +- - `pipeline_id` - String - - The API Key provided as a plaintext string. If you prefer to reference your token using Databricks Secrets, see `value`. + - The full name of the pipeline task to execute. ::: -### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config.bearer_token_auth +### jobs._name_.tasks.power_bi_task **`Type: Map`** -This is a field to provide bearer token authentication for the custom provider API. -You can only specify one authentication method. +The task triggers a Power BI semantic model update when the `power_bi_task` field is present. @@ -6276,22 +6091,34 @@ You can only specify one authentication method. - Type - Description -- - `token` +- - `connection_resource_name` - String - - The Databricks secret key reference for a token. If you prefer to paste your token directly, see `token_plaintext`. + - The resource name of the UC connection to authenticate from Databricks to Power BI -- - `token_plaintext` +- - `power_bi_model` + - Map + - The semantic model to update. See [\_](#jobsnametaskspower_bi_taskpower_bi_model). + +- - `refresh_after_update` + - Boolean + - Whether the model should be refreshed after the update + +- - `tables` + - Sequence + - The tables to be exported to Power BI. See [\_](#jobsnametaskspower_bi_tasktables). + +- - `warehouse_id` - String - - The token provided as a plaintext string. If you prefer to reference your token using Databricks Secrets, see `token`. + - The SQL warehouse ID to use as the Power BI data source ::: -### model_serving_endpoints._name_.config.served_entities.external_model.databricks_model_serving_config +### jobs._name_.tasks.power_bi_task.power_bi_model **`Type: Map`** -Databricks Model Serving Config. Only required if the provider is 'databricks-model-serving'. +The semantic model to update @@ -6301,26 +6128,34 @@ Databricks Model Serving Config. Only required if the provider is 'databricks-mo - Type - Description -- - `databricks_api_token` +- - `authentication_method` - String - - The Databricks secret key reference for a Databricks API token that corresponds to a user or service principal with Can Query access to the model serving endpoint pointed to by this external model. If you prefer to paste your API key directly, see `databricks_api_token_plaintext`. You must provide an API key using one of the following fields: `databricks_api_token` or `databricks_api_token_plaintext`. + - How the published Power BI model authenticates to Databricks -- - `databricks_api_token_plaintext` +- - `model_name` - String - - The Databricks API token that corresponds to a user or service principal with Can Query access to the model serving endpoint pointed to by this external model provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `databricks_api_token`. You must provide an API key using one of the following fields: `databricks_api_token` or `databricks_api_token_plaintext`. + - The name of the Power BI model -- - `databricks_workspace_url` +- - `overwrite_existing` + - Boolean + - Whether to overwrite existing Power BI models + +- - `storage_mode` - String - - The URL of the Databricks workspace containing the model serving endpoint pointed to by this external model. + - The default storage mode of the Power BI model + +- - `workspace_name` + - String + - The name of the Power BI workspace of the model ::: -### model_serving_endpoints._name_.config.served_entities.external_model.google_cloud_vertex_ai_config +### jobs._name_.tasks.power_bi_task.tables -**`Type: Map`** +**`Type: Sequence`** -Google Cloud Vertex AI Config. Only required if the provider is 'google-cloud-vertex-ai'. +The tables to be exported to Power BI @@ -6330,30 +6165,30 @@ Google Cloud Vertex AI Config. Only required if the provider is 'google-cloud-ve - Type - Description -- - `private_key` +- - `catalog` - String - - The Databricks secret key reference for a private key for the service account which has access to the Google Cloud Vertex AI Service. See [Best practices for managing service account keys]. If you prefer to paste your API key directly, see `private_key_plaintext`. You must provide an API key using one of the following fields: `private_key` or `private_key_plaintext` [Best practices for managing service account keys]: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys + - The catalog name in Databricks -- - `private_key_plaintext` +- - `name` - String - - The private key for the service account which has access to the Google Cloud Vertex AI Service provided as a plaintext secret. See [Best practices for managing service account keys]. If you prefer to reference your key using Databricks Secrets, see `private_key`. You must provide an API key using one of the following fields: `private_key` or `private_key_plaintext`. [Best practices for managing service account keys]: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys + - The table name in Databricks -- - `project_id` +- - `schema` - String - - This is the Google Cloud project id that the service account is associated with. + - The schema name in Databricks -- - `region` +- - `storage_mode` - String - - This is the region for the Google Cloud Vertex AI Service. See [supported regions] for more details. Some models are only available in specific regions. [supported regions]: https://cloud.google.com/vertex-ai/docs/general/locations + - The Power BI storage mode of the table ::: -### model_serving_endpoints._name_.config.served_entities.external_model.openai_config +### jobs._name_.tasks.python_wheel_task **`Type: Map`** -OpenAI Config. Only required if the provider is 'openai'. +The task runs a Python wheel when the `python_wheel_task` field is present. @@ -6363,58 +6198,30 @@ OpenAI Config. Only required if the provider is 'openai'. - Type - Description -- - `microsoft_entra_client_id` - - String - - This field is only required for Azure AD OpenAI and is the Microsoft Entra Client ID. - -- - `microsoft_entra_client_secret` - - String - - The Databricks secret key reference for a client secret used for Microsoft Entra ID authentication. If you prefer to paste your client secret directly, see `microsoft_entra_client_secret_plaintext`. You must provide an API key using one of the following fields: `microsoft_entra_client_secret` or `microsoft_entra_client_secret_plaintext`. - -- - `microsoft_entra_client_secret_plaintext` - - String - - The client secret used for Microsoft Entra ID authentication provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `microsoft_entra_client_secret`. You must provide an API key using one of the following fields: `microsoft_entra_client_secret` or `microsoft_entra_client_secret_plaintext`. - -- - `microsoft_entra_tenant_id` - - String - - This field is only required for Azure AD OpenAI and is the Microsoft Entra Tenant ID. - -- - `openai_api_base` - - String - - This is a field to provide a customized base URl for the OpenAI API. For Azure OpenAI, this field is required, and is the base URL for the Azure OpenAI API service provided by Azure. For other OpenAI API types, this field is optional, and if left unspecified, the standard OpenAI base URL is used. - -- - `openai_api_key` - - String - - The Databricks secret key reference for an OpenAI API key using the OpenAI or Azure service. If you prefer to paste your API key directly, see `openai_api_key_plaintext`. You must provide an API key using one of the following fields: `openai_api_key` or `openai_api_key_plaintext`. - -- - `openai_api_key_plaintext` - - String - - The OpenAI API key using the OpenAI or Azure service provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `openai_api_key`. You must provide an API key using one of the following fields: `openai_api_key` or `openai_api_key_plaintext`. - -- - `openai_api_type` +- - `entry_point` - String - - This is an optional field to specify the type of OpenAI API to use. For Azure OpenAI, this field is required, and adjust this parameter to represent the preferred security access validation protocol. For access token validation, use azure. For authentication using Azure Active Directory (Azure AD) use, azuread. + - Named entry point to use, if it does not exist in the metadata of the package it executes the function from the package directly using `$packageName.$entryPoint()` -- - `openai_api_version` - - String - - This is an optional field to specify the OpenAI API version. For Azure OpenAI, this field is required, and is the version of the Azure OpenAI service to utilize, specified by a date. +- - `named_parameters` + - Map + - Command-line parameters passed to Python wheel task in the form of `["--name=task", "--data=dbfs:/path/to/data.json"]`. Leave it empty if `parameters` is not null. -- - `openai_deployment_name` +- - `package_name` - String - - This field is only required for Azure OpenAI and is the name of the deployment resource for the Azure OpenAI service. + - Name of the package to execute -- - `openai_organization` - - String - - This is an optional field to specify the organization in OpenAI or Azure OpenAI. +- - `parameters` + - Sequence + - Command-line parameters passed to Python wheel task. Leave it empty if `named_parameters` is not null. ::: -### model_serving_endpoints._name_.config.served_entities.external_model.palm_config +### jobs._name_.tasks.run_job_task **`Type: Map`** -PaLM Config. Only required if the provider is 'palm'. +The task triggers another job when the `run_job_task` field is present. @@ -6424,22 +6231,26 @@ PaLM Config. Only required if the provider is 'palm'. - Type - Description -- - `palm_api_key` - - String - - The Databricks secret key reference for a PaLM API key. If you prefer to paste your API key directly, see `palm_api_key_plaintext`. You must provide an API key using one of the following fields: `palm_api_key` or `palm_api_key_plaintext`. +- - `job_id` + - Integer + - ID of the job to trigger. -- - `palm_api_key_plaintext` - - String - - The PaLM API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `palm_api_key`. You must provide an API key using one of the following fields: `palm_api_key` or `palm_api_key_plaintext`. +- - `job_parameters` + - Map + - Job-level parameters used to trigger the job. + +- - `pipeline_params` + - Map + - Controls whether the pipeline should perform a full refresh. See [\_](#jobsnametasksrun_job_taskpipeline_params). ::: -### model_serving_endpoints._name_.config.served_models +### jobs._name_.tasks.run_job_task.pipeline_params -**`Type: Sequence`** +**`Type: Map`** -(Deprecated, use served_entities instead) The list of served models under the serving endpoint config. +Controls whether the pipeline should perform a full refresh @@ -6449,66 +6260,51 @@ PaLM Config. Only required if the provider is 'palm'. - Type - Description -- - `environment_vars` - - Map - - An object containing a set of optional, user-specified environment variable key-value pairs used for serving this entity. Note: this is an experimental feature and subject to change. Example entity environment variables that refer to Databricks secrets: `{"OPENAI_API_KEY": "{{secrets/my_scope/my_key}}", "DATABRICKS_TOKEN": "{{secrets/my_scope2/my_key2}}"}` - -- - `instance_profile_arn` - - String - - ARN of the instance profile that the served entity uses to access AWS resources. +- - `full_refresh` + - Boolean + - If true, triggers a full refresh on the delta live table. -- - `max_provisioned_concurrency` - - Integer - - The maximum provisioned concurrency that the endpoint can scale up to. Do not use if workload_size is specified. +::: -- - `max_provisioned_throughput` - - Integer - - The maximum tokens per second that the endpoint can scale up to. -- - `min_provisioned_concurrency` - - Integer - - The minimum provisioned concurrency that the endpoint can scale down to. Do not use if workload_size is specified. +### jobs._name_.tasks.spark_jar_task -- - `min_provisioned_throughput` - - Integer - - The minimum tokens per second that the endpoint can scale down to. +**`Type: Map`** -- - `model_name` - - String - - +The task runs a JAR when the `spark_jar_task` field is present. -- - `model_version` - - String - - -- - `name` - - String - - The name of a served entity. It must be unique across an endpoint. A served entity name can consist of alphanumeric characters, dashes, and underscores. If not specified for an external model, this field defaults to external_model.name, with '.' and ':' replaced with '-', and if not specified for other entities, it defaults to entity_name-entity_version. -- - `provisioned_model_units` - - Integer - - The number of model units provisioned. +:::list-table -- - `scale_to_zero_enabled` - - Boolean - - Whether the compute resources for the served entity should scale down to zero. +- - Key + - Type + - Description -- - `workload_size` +- - `jar_uri` - String - - The workload size of the served entity. The workload size corresponds to a range of provisioned concurrency that the compute autoscales between. A single unit of provisioned concurrency can process one request at a time. Valid workload sizes are "Small" (4 - 4 provisioned concurrency), "Medium" (8 - 16 provisioned concurrency), and "Large" (16 - 64 provisioned concurrency). Additional custom workload sizes can also be used when available in the workspace. If scale-to-zero is enabled, the lower bound of the provisioned concurrency for each workload size is 0. Do not use if min_provisioned_concurrency and max_provisioned_concurrency are specified. + - This field is deprecated -- - `workload_type` +- - `main_class_name` - String - - The workload type of the served entity. The workload type selects which type of compute to use in the endpoint. The default value for this parameter is "CPU". For deep learning workloads, GPU acceleration is available by selecting workload types like GPU_SMALL and others. See the available [GPU types](https://docs.databricks.com/en/machine-learning/model-serving/create-manage-serving-endpoints.html#gpu-workload-types). + - The full name of the class containing the main method to be executed. This class must be contained in a JAR provided as a library. The code must use `SparkContext.getOrCreate` to obtain a Spark context; otherwise, runs of the job fail. + +- - `parameters` + - Sequence + - Parameters passed to the main method. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. + +- - `run_as_repl` + - Boolean + - This field is deprecated ::: -### model_serving_endpoints._name_.config.traffic_config +### jobs._name_.tasks.spark_python_task **`Type: Map`** -The traffic configuration associated with the serving endpoint config. +The task runs a Python file when the `spark_python_task` field is present. @@ -6518,18 +6314,26 @@ The traffic configuration associated with the serving endpoint config. - Type - Description -- - `routes` +- - `parameters` - Sequence - - The list of routes that define traffic to each served entity. See [\_](#model_serving_endpointsnameconfigtraffic_configroutes). + - Command line parameters passed to the Python file. Use [Task parameter variables](https://docs.databricks.com/jobs.html#parameter-variables) to set parameters containing information about job runs. + +- - `python_file` + - String + - The Python file to be executed. Cloud file URIs (such as dbfs:/, s3:/, adls:/, gcs:/) and workspace paths are supported. For python files stored in the Databricks workspace, the path must be absolute and begin with `/`. For files stored in a remote repository, the path must be relative. This field is required. + +- - `source` + - String + - Optional location type of the Python file. When set to `WORKSPACE` or not specified, the file will be retrieved from the local Databricks workspace or cloud location (if the `python_file` has a URI format). When set to `GIT`, the Python file will be retrieved from a Git repository defined in `git_source`. * `WORKSPACE`: The Python file is located in a Databricks workspace or at a cloud filesystem URI. * `GIT`: The Python file is located in a remote Git repository. ::: -### model_serving_endpoints._name_.config.traffic_config.routes +### jobs._name_.tasks.sql_task -**`Type: Sequence`** +**`Type: Map`** -The list of routes that define traffic to each served entity. +The task runs a SQL query or file, or it refreshes a SQL alert or a legacy SQL dashboard when the `sql_task` field is present. @@ -6539,26 +6343,38 @@ The list of routes that define traffic to each served entity. - Type - Description -- - `served_entity_name` - - String - - +- - `alert` + - Map + - If alert, indicates that this job must refresh a SQL alert. See [\_](#jobsnametaskssql_taskalert). -- - `served_model_name` - - String - - The name of the served model this route configures traffic for. +- - `dashboard` + - Map + - If dashboard, indicates that this job must refresh a SQL dashboard. See [\_](#jobsnametaskssql_taskdashboard). -- - `traffic_percentage` - - Integer - - The percentage of endpoint traffic to send to this route. It must be an integer between 0 and 100 inclusive. +- - `file` + - Map + - If file, indicates that this job runs a SQL file in a remote Git repository. See [\_](#jobsnametaskssql_taskfile). + +- - `parameters` + - Map + - Parameters to be used for each run of this job. The SQL alert task does not support custom parameters. + +- - `query` + - Map + - If query, indicates that this job must execute a SQL query. See [\_](#jobsnametaskssql_taskquery). + +- - `warehouse_id` + - String + - The canonical identifier of the SQL warehouse. Recommended to use with serverless or pro SQL warehouses. Classic SQL warehouses are only supported for SQL alert, dashboard and query tasks and are limited to scheduled single-task jobs. ::: -### model_serving_endpoints._name_.email_notifications +### jobs._name_.tasks.sql_task.alert **`Type: Map`** -Email notification settings. +If alert, indicates that this job must refresh a SQL alert. @@ -6568,22 +6384,26 @@ Email notification settings. - Type - Description -- - `on_update_failure` - - Sequence - - A list of email addresses to be notified when an endpoint fails to update its configuration or state. +- - `alert_id` + - String + - The canonical identifier of the SQL alert. -- - `on_update_success` +- - `pause_subscriptions` + - Boolean + - If true, the alert notifications are not sent to subscribers. + +- - `subscriptions` - Sequence - - A list of email addresses to be notified when an endpoint successfully updates its configuration or state. + - If specified, alert notifications are sent to subscribers. See [\_](#jobsnametaskssql_taskalertsubscriptions). ::: -### model_serving_endpoints._name_.lifecycle +### jobs._name_.tasks.sql_task.alert.subscriptions -**`Type: Map`** +**`Type: Sequence`** -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. +If specified, alert notifications are sent to subscribers. @@ -6593,18 +6413,22 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `destination_id` + - String + - The canonical identifier of the destination to receive email notification. This parameter is mutually exclusive with user_name. You cannot set both destination_id and user_name for subscription notifications. -::: +- - `user_name` + - String + - The user name to receive the subscription email. This parameter is mutually exclusive with destination_id. You cannot set both destination_id and user_name for subscription notifications. +::: -### model_serving_endpoints._name_.permissions -**`Type: Sequence`** +### jobs._name_.tasks.sql_task.dashboard - +**`Type: Map`** + +If dashboard, indicates that this job must refresh a SQL dashboard. @@ -6614,30 +6438,30 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `group_name` +- - `custom_subject` - String - - + - Subject of the email sent to subscribers of this task. -- - `level` +- - `dashboard_id` - String - - + - The canonical identifier of the SQL dashboard. -- - `service_principal_name` - - String - - +- - `pause_subscriptions` + - Boolean + - If true, the dashboard snapshot is not taken, and emails are not sent to subscribers. -- - `user_name` - - String - - +- - `subscriptions` + - Sequence + - If specified, dashboard snapshots are sent to subscriptions. See [\_](#jobsnametaskssql_taskdashboardsubscriptions). ::: -### model_serving_endpoints._name_.tags +### jobs._name_.tasks.sql_task.dashboard.subscriptions **`Type: Sequence`** -Tags to be attached to the serving endpoint and automatically propagated to billing logs. +If specified, dashboard snapshots are sent to subscriptions. @@ -6647,28 +6471,23 @@ Tags to be attached to the serving endpoint and automatically propagated to bill - Type - Description -- - `key` +- - `destination_id` - String - - Key field for a serving endpoint tag. + - The canonical identifier of the destination to receive email notification. This parameter is mutually exclusive with user_name. You cannot set both destination_id and user_name for subscription notifications. -- - `value` +- - `user_name` - String - - Optional value field for a serving endpoint tag. + - The user name to receive the subscription email. This parameter is mutually exclusive with destination_id. You cannot set both destination_id and user_name for subscription notifications. ::: -## models +### jobs._name_.tasks.sql_task.file **`Type: Map`** -The model resource allows you to define [legacy models](/api/workspace/modelregistry/createmodel) in bundles. Databricks recommends you use Unity Catalog [registered models](#registered-model) instead. +If file, indicates that this job runs a SQL file in a remote Git repository. -```yaml -models: - : - : -``` :::list-table @@ -6677,34 +6496,22 @@ models: - Type - Description -- - `description` +- - `path` - String - - Optional description for registered model. - -- - `lifecycle` - - Map - - 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. See [\_](#modelsnamelifecycle). + - Path of the SQL file. Must be relative if the source is a remote Git repository and absolute for workspace paths. -- - `name` +- - `source` - String - - Register models under this name - -- - `permissions` - - Sequence - - See [\_](#modelsnamepermissions). - -- - `tags` - - Sequence - - Additional metadata for registered model. See [\_](#modelsnametags). + - Optional location type of the SQL file. When set to `WORKSPACE`, the SQL file will be retrieved from the local Databricks workspace. When set to `GIT`, the SQL file will be retrieved from a Git repository defined in `git_source`. If the value is empty, the task will use `GIT` if `git_source` is defined and `WORKSPACE` otherwise. * `WORKSPACE`: SQL file is located in Databricks workspace. * `GIT`: SQL file is located in cloud Git provider. ::: -### models._name_.lifecycle +### jobs._name_.tasks.sql_task.query **`Type: Map`** -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. +If query, indicates that this job must execute a SQL query. @@ -6714,18 +6521,18 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` - - Boolean - - Lifecycle setting to prevent the resource from being destroyed. +- - `query_id` + - String + - The canonical identifier of the SQL query. ::: -### models._name_.permissions +### jobs._name_.tasks.webhook_notifications -**`Type: Sequence`** +**`Type: Map`** - +A collection of system notification IDs to notify when runs of this task begin or complete. The default behavior is to not send any system notifications. @@ -6735,30 +6542,34 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `group_name` - - String - - +- - `on_duration_warning_threshold_exceeded` + - Sequence + - An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. See [\_](#jobsnametaskswebhook_notificationson_duration_warning_threshold_exceeded). -- - `level` - - String - - +- - `on_failure` + - Sequence + - An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. See [\_](#jobsnametaskswebhook_notificationson_failure). -- - `service_principal_name` - - String - - +- - `on_start` + - Sequence + - An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. See [\_](#jobsnametaskswebhook_notificationson_start). -- - `user_name` - - String - - +- - `on_streaming_backlog_exceeded` + - Sequence + - An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. See [\_](#jobsnametaskswebhook_notificationson_streaming_backlog_exceeded). + +- - `on_success` + - Sequence + - An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. See [\_](#jobsnametaskswebhook_notificationson_success). ::: -### models._name_.tags +### jobs._name_.tasks.webhook_notifications.on_duration_warning_threshold_exceeded **`Type: Sequence`** -Additional metadata for registered model. +An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. @@ -6768,28 +6579,19 @@ Additional metadata for registered model. - Type - Description -- - `key` - - String - - The tag key. - -- - `value` +- - `id` - String - - The tag value. + - ::: -## pipelines +### jobs._name_.tasks.webhook_notifications.on_failure -**`Type: Map`** +**`Type: Sequence`** -The pipeline resource allows you to create Delta Live Tables [pipelines](/api/workspace/pipelines/create). For information about pipelines, see [_](/dlt/index.md). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [_](/dev-tools/bundles/pipelines-tutorial.md). +An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. -```yaml -pipelines: - : - : -``` :::list-table @@ -6798,148 +6600,84 @@ pipelines: - Type - Description -- - `allow_duplicate_names` - - Boolean - - If false, deployment will fail if name conflicts with that of another pipeline. - -- - `catalog` - - String - - A catalog in Unity Catalog to publish data from this pipeline to. If `target` is specified, tables in this pipeline are published to a `target` schema inside `catalog` (for example, `catalog`.`target`.`table`). If `target` is not specified, no data is published to Unity Catalog. - -- - `channel` +- - `id` - String - - DLT Release Channel that specifies which version to use. - -- - `clusters` - - Sequence - - Cluster settings for this pipeline deployment. See [\_](#pipelinesnameclusters). + - -- - `configuration` - - Map - - String-String configuration for this pipeline execution. +::: -- - `continuous` - - Boolean - - Whether the pipeline is continuous or triggered. This replaces `trigger`. -- - `deployment` - - Map - - Deployment type of this pipeline. See [\_](#pipelinesnamedeployment). +### jobs._name_.tasks.webhook_notifications.on_start -- - `development` - - Boolean - - Whether the pipeline is in Development mode. Defaults to false. +**`Type: Sequence`** -- - `dry_run` - - Boolean - - +An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. -- - `edition` - - String - - Pipeline product edition. -- - `environment` - - Map - - Environment specification for this pipeline used to install dependencies. See [\_](#pipelinesnameenvironment). -- - `event_log` - - Map - - Event log configuration for this pipeline. See [\_](#pipelinesnameevent_log). +:::list-table -- - `filters` - - Map - - Filters on which Pipeline packages to include in the deployed graph. See [\_](#pipelinesnamefilters). +- - Key + - Type + - Description - - `id` - String - - Unique identifier for this pipeline. + - -- - `ingestion_definition` - - Map - - The configuration for a managed ingestion pipeline. These settings cannot be used with the 'libraries', 'schema', 'target', or 'catalog' settings. See [\_](#pipelinesnameingestion_definition). +::: -- - `libraries` - - Sequence - - Libraries or code needed by this deployment. See [\_](#pipelinesnamelibraries). -- - `lifecycle` - - Map - - 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. See [\_](#pipelinesnamelifecycle). +### jobs._name_.tasks.webhook_notifications.on_streaming_backlog_exceeded -- - `name` - - String - - Friendly identifier for this pipeline. +**`Type: Sequence`** -- - `notifications` - - Sequence - - List of notification settings for this pipeline. See [\_](#pipelinesnamenotifications). +An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. +Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. +Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. +A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. -- - `permissions` - - Sequence - - See [\_](#pipelinesnamepermissions). -- - `photon` - - Boolean - - Whether Photon is enabled for this pipeline. -- - `root_path` - - String - - Root path for this pipeline. This is used as the root directory when editing the pipeline in the Databricks user interface and it is added to sys.path when executing Python sources during pipeline execution. +:::list-table -- - `schema` +- - Key + - Type + - Description + +- - `id` - String - - The default schema (database) where tables are read from or published to. + - -- - `serverless` - - Boolean - - Whether serverless compute is enabled for this pipeline. +::: -- - `storage` - - String - - DBFS root directory for storing checkpoints and tables. -- - `tags` - - Map - - A map of tags associated with the pipeline. These are forwarded to the cluster as cluster tags, and are therefore subject to the same limitations. A maximum of 25 tags can be added to the pipeline. +### jobs._name_.tasks.webhook_notifications.on_success -- - `target` - - String - - This field is deprecated +**`Type: Sequence`** -- - `trigger` - - Map - - Use continuous instead +An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. -::: -**Example** +:::list-table -The following example defines a pipeline with the resource key `hello-pipeline`: - -```yaml -resources: - pipelines: - hello-pipeline: - name: hello-pipeline - clusters: - - label: default - num_workers: 1 - development: true - continuous: false - channel: CURRENT - edition: CORE - photon: false - libraries: - - notebook: - path: ./pipeline.py -``` +- - Key + - Type + - Description -### pipelines._name_.clusters +- - `id` + - String + - -**`Type: Sequence`** +::: -Cluster settings for this pipeline deployment. + +### jobs._name_.trigger + +**`Type: Map`** + +A configuration to trigger a run when certain conditions are met. The default behavior is that the job runs only when triggered by clicking “Run Now” in the Jobs UI or sending an API request to `runNow`. @@ -6949,91 +6687,2430 @@ Cluster settings for this pipeline deployment. - Type - Description -- - `apply_policy_default_values` - - Boolean - - Note: This field won't be persisted. Only API users will check this field. - -- - `autoscale` +- - `file_arrival` - Map - - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#pipelinesnameclustersautoscale). + - File arrival trigger settings. See [\_](#jobsnametriggerfile_arrival). -- - `aws_attributes` - - Map - - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersaws_attributes). +- - `pause_status` + - String + - Whether this trigger is paused or not. -- - `azure_attributes` +- - `periodic` - Map - - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersazure_attributes). + - Periodic trigger settings. See [\_](#jobsnametriggerperiodic). -- - `cluster_log_conf` +- - `table_update` - Map - - The configuration for delivering spark logs to a long-term storage destination. Only dbfs destinations are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#pipelinesnameclusterscluster_log_conf). + - See [\_](#jobsnametriggertable_update). -- - `custom_tags` - - Map - - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags +::: -- - `driver_instance_pool_id` + +### jobs._name_.trigger.file_arrival + +**`Type: Map`** + +File arrival trigger settings. + + + +:::list-table + +- - Key + - Type + - Description + +- - `min_time_between_triggers_seconds` + - Integer + - If set, the trigger starts a run only after the specified amount of time passed since the last time the trigger fired. The minimum allowed value is 60 seconds + +- - `url` - String - - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. + - URL to be monitored for file arrivals. The path must point to the root or a subpath of the external location. + +- - `wait_after_last_change_seconds` + - Integer + - If set, the trigger starts a run only after no file activity has occurred for the specified amount of time. This makes it possible to wait for a batch of incoming files to arrive before triggering a run. The minimum allowed value is 60 seconds. + +::: + + +### jobs._name_.trigger.periodic + +**`Type: Map`** + +Periodic trigger settings. + + + +:::list-table + +- - Key + - Type + - Description + +- - `interval` + - Integer + - The interval at which the trigger should run. + +- - `unit` + - String + - The unit of time for the interval. + +::: + + +### jobs._name_.trigger.table_update + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `condition` + - String + - The table(s) condition based on which to trigger a job run. + +- - `min_time_between_triggers_seconds` + - Integer + - If set, the trigger starts a run only after the specified amount of time has passed since the last time the trigger fired. The minimum allowed value is 60 seconds. + +- - `table_names` + - Sequence + - A list of tables to monitor for changes. The table name must be in the format `catalog_name.schema_name.table_name`. + +- - `wait_after_last_change_seconds` + - Integer + - If set, the trigger starts a run only after no table updates have occurred for the specified time and can be used to wait for a series of table updates before triggering a run. The minimum allowed value is 60 seconds. + +::: + + +### jobs._name_.webhook_notifications + +**`Type: Map`** + +A collection of system notification IDs to notify when runs of this job begin or complete. + + + +:::list-table + +- - Key + - Type + - Description + +- - `on_duration_warning_threshold_exceeded` + - Sequence + - An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. See [\_](#jobsnamewebhook_notificationson_duration_warning_threshold_exceeded). + +- - `on_failure` + - Sequence + - An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. See [\_](#jobsnamewebhook_notificationson_failure). + +- - `on_start` + - Sequence + - An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. See [\_](#jobsnamewebhook_notificationson_start). + +- - `on_streaming_backlog_exceeded` + - Sequence + - An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. See [\_](#jobsnamewebhook_notificationson_streaming_backlog_exceeded). + +- - `on_success` + - Sequence + - An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. See [\_](#jobsnamewebhook_notificationson_success). + +::: + + +### jobs._name_.webhook_notifications.on_duration_warning_threshold_exceeded + +**`Type: Sequence`** + +An optional list of system notification IDs to call when the duration of a run exceeds the threshold specified for the `RUN_DURATION_SECONDS` metric in the `health` field. A maximum of 3 destinations can be specified for the `on_duration_warning_threshold_exceeded` property. + + + +:::list-table + +- - Key + - Type + - Description + +- - `id` + - String + - + +::: + + +### jobs._name_.webhook_notifications.on_failure + +**`Type: Sequence`** + +An optional list of system notification IDs to call when the run fails. A maximum of 3 destinations can be specified for the `on_failure` property. + + + +:::list-table + +- - Key + - Type + - Description + +- - `id` + - String + - + +::: + + +### jobs._name_.webhook_notifications.on_start + +**`Type: Sequence`** + +An optional list of system notification IDs to call when the run starts. A maximum of 3 destinations can be specified for the `on_start` property. + + + +:::list-table + +- - Key + - Type + - Description + +- - `id` + - String + - + +::: + + +### jobs._name_.webhook_notifications.on_streaming_backlog_exceeded + +**`Type: Sequence`** + +An optional list of system notification IDs to call when any streaming backlog thresholds are exceeded for any stream. +Streaming backlog thresholds can be set in the `health` field using the following metrics: `STREAMING_BACKLOG_BYTES`, `STREAMING_BACKLOG_RECORDS`, `STREAMING_BACKLOG_SECONDS`, or `STREAMING_BACKLOG_FILES`. +Alerting is based on the 10-minute average of these metrics. If the issue persists, notifications are resent every 30 minutes. +A maximum of 3 destinations can be specified for the `on_streaming_backlog_exceeded` property. + + + +:::list-table + +- - Key + - Type + - Description + +- - `id` + - String + - + +::: + + +### jobs._name_.webhook_notifications.on_success + +**`Type: Sequence`** + +An optional list of system notification IDs to call when the run completes successfully. A maximum of 3 destinations can be specified for the `on_success` property. + + + +:::list-table + +- - Key + - Type + - Description + +- - `id` + - String + - + +::: + + +## model_serving_endpoints + +**`Type: Map`** + +The model_serving_endpoint resource allows you to define [model serving endpoints](/api/workspace/servingendpoints/create). See [_](/machine-learning/model-serving/manage-serving-endpoints.md). + +```yaml +model_serving_endpoints: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `ai_gateway` + - Map + - The AI Gateway configuration for the serving endpoint. NOTE: External model, provisioned throughput, and pay-per-token endpoints are fully supported; agent endpoints currently only support inference tables. See [\_](#model_serving_endpointsnameai_gateway). + +- - `budget_policy_id` + - String + - The budget policy to be applied to the serving endpoint. + +- - `config` + - Map + - The core config of the serving endpoint. See [\_](#model_serving_endpointsnameconfig). + +- - `description` + - String + - + +- - `email_notifications` + - Map + - Email notification settings. See [\_](#model_serving_endpointsnameemail_notifications). + +- - `lifecycle` + - Map + - 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. See [\_](#model_serving_endpointsnamelifecycle). + +- - `name` + - String + - The name of the serving endpoint. This field is required and must be unique across a Databricks workspace. An endpoint name can consist of alphanumeric characters, dashes, and underscores. + +- - `permissions` + - Sequence + - See [\_](#model_serving_endpointsnamepermissions). + +- - `rate_limits` + - Sequence + - This field is deprecated + +- - `route_optimized` + - Boolean + - Enable route optimization for the serving endpoint. + +- - `tags` + - Sequence + - Tags to be attached to the serving endpoint and automatically propagated to billing logs. See [\_](#model_serving_endpointsnametags). + +::: + + +**Example** + +The following example defines a Unity Catalog model serving endpoint: + +```yaml +resources: + model_serving_endpoints: + uc_model_serving_endpoint: + name: "uc-model-endpoint" + config: + served_entities: + - entity_name: "myCatalog.mySchema.my-ads-model" + entity_version: "10" + workload_size: "Small" + scale_to_zero_enabled: "true" + traffic_config: + routes: + - served_model_name: "my-ads-model-10" + traffic_percentage: "100" + tags: + - key: "team" + value: "data science" +``` + +### model_serving_endpoints._name_.ai_gateway + +**`Type: Map`** + +The AI Gateway configuration for the serving endpoint. NOTE: External model, provisioned throughput, and pay-per-token endpoints are fully supported; agent endpoints currently only support inference tables. + + + +:::list-table + +- - Key + - Type + - Description + +- - `fallback_config` + - Map + - Configuration for traffic fallback which auto fallbacks to other served entities if the request to a served entity fails with certain error codes, to increase availability. See [\_](#model_serving_endpointsnameai_gatewayfallback_config). + +- - `guardrails` + - Map + - Configuration for AI Guardrails to prevent unwanted data and unsafe data in requests and responses. See [\_](#model_serving_endpointsnameai_gatewayguardrails). + +- - `inference_table_config` + - Map + - Configuration for payload logging using inference tables. Use these tables to monitor and audit data being sent to and received from model APIs and to improve model quality. See [\_](#model_serving_endpointsnameai_gatewayinference_table_config). + +- - `rate_limits` + - Sequence + - Configuration for rate limits which can be set to limit endpoint traffic. See [\_](#model_serving_endpointsnameai_gatewayrate_limits). + +- - `usage_tracking_config` + - Map + - Configuration to enable usage tracking using system tables. These tables allow you to monitor operational usage on endpoints and their associated costs. See [\_](#model_serving_endpointsnameai_gatewayusage_tracking_config). + +::: + + +### model_serving_endpoints._name_.ai_gateway.fallback_config + +**`Type: Map`** + +Configuration for traffic fallback which auto fallbacks to other served entities if the request to a served +entity fails with certain error codes, to increase availability. + + + +:::list-table + +- - Key + - Type + - Description + +- - `enabled` + - Boolean + - Whether to enable traffic fallback. When a served entity in the serving endpoint returns specific error codes (e.g. 500), the request will automatically be round-robin attempted with other served entities in the same endpoint, following the order of served entity list, until a successful response is returned. If all attempts fail, return the last response with the error code. + +::: + + +### model_serving_endpoints._name_.ai_gateway.guardrails + +**`Type: Map`** + +Configuration for AI Guardrails to prevent unwanted data and unsafe data in requests and responses. + + + +:::list-table + +- - Key + - Type + - Description + +- - `input` + - Map + - Configuration for input guardrail filters. See [\_](#model_serving_endpointsnameai_gatewayguardrailsinput). + +- - `output` + - Map + - Configuration for output guardrail filters. See [\_](#model_serving_endpointsnameai_gatewayguardrailsoutput). + +::: + + +### model_serving_endpoints._name_.ai_gateway.guardrails.input + +**`Type: Map`** + +Configuration for input guardrail filters. + + + +:::list-table + +- - Key + - Type + - Description + +- - `invalid_keywords` + - Sequence + - This field is deprecated + +- - `pii` + - Map + - Configuration for guardrail PII filter. See [\_](#model_serving_endpointsnameai_gatewayguardrailsinputpii). + +- - `safety` + - Boolean + - Indicates whether the safety filter is enabled. + +- - `valid_topics` + - Sequence + - This field is deprecated + +::: + + +### model_serving_endpoints._name_.ai_gateway.guardrails.input.pii + +**`Type: Map`** + +Configuration for guardrail PII filter. + + + +:::list-table + +- - Key + - Type + - Description + +- - `behavior` + - String + - Configuration for input guardrail filters. + +::: + + +### model_serving_endpoints._name_.ai_gateway.guardrails.output + +**`Type: Map`** + +Configuration for output guardrail filters. + + + +:::list-table + +- - Key + - Type + - Description + +- - `invalid_keywords` + - Sequence + - This field is deprecated + +- - `pii` + - Map + - Configuration for guardrail PII filter. See [\_](#model_serving_endpointsnameai_gatewayguardrailsoutputpii). + +- - `safety` + - Boolean + - Indicates whether the safety filter is enabled. + +- - `valid_topics` + - Sequence + - This field is deprecated + +::: + + +### model_serving_endpoints._name_.ai_gateway.guardrails.output.pii + +**`Type: Map`** + +Configuration for guardrail PII filter. + + + +:::list-table + +- - Key + - Type + - Description + +- - `behavior` + - String + - Configuration for input guardrail filters. + +::: + + +### model_serving_endpoints._name_.ai_gateway.inference_table_config + +**`Type: Map`** + +Configuration for payload logging using inference tables. +Use these tables to monitor and audit data being sent to and received from model APIs and to improve model quality. + + + +:::list-table + +- - Key + - Type + - Description + +- - `catalog_name` + - String + - The name of the catalog in Unity Catalog. Required when enabling inference tables. NOTE: On update, you have to disable inference table first in order to change the catalog name. + +- - `enabled` + - Boolean + - Indicates whether the inference table is enabled. + +- - `schema_name` + - String + - The name of the schema in Unity Catalog. Required when enabling inference tables. NOTE: On update, you have to disable inference table first in order to change the schema name. + +- - `table_name_prefix` + - String + - The prefix of the table in Unity Catalog. NOTE: On update, you have to disable inference table first in order to change the prefix name. + +::: + + +### model_serving_endpoints._name_.ai_gateway.rate_limits + +**`Type: Sequence`** + +Configuration for rate limits which can be set to limit endpoint traffic. + + + +:::list-table + +- - Key + - Type + - Description + +- - `calls` + - Integer + - Used to specify how many calls are allowed for a key within the renewal_period. + +- - `key` + - String + - Key field for a rate limit. Currently, 'user', 'user_group, 'service_principal', and 'endpoint' are supported, with 'endpoint' being the default if not specified. + +- - `principal` + - String + - Principal field for a user, user group, or service principal to apply rate limiting to. Accepts a user email, group name, or service principal application ID. + +- - `renewal_period` + - String + - Renewal period field for a rate limit. Currently, only 'minute' is supported. + +- - `tokens` + - Integer + - Used to specify how many tokens are allowed for a key within the renewal_period. + +::: + + +### model_serving_endpoints._name_.ai_gateway.usage_tracking_config + +**`Type: Map`** + +Configuration to enable usage tracking using system tables. +These tables allow you to monitor operational usage on endpoints and their associated costs. + + + +:::list-table + +- - Key + - Type + - Description + +- - `enabled` + - Boolean + - Whether to enable usage tracking. + +::: + + +### model_serving_endpoints._name_.config + +**`Type: Map`** + +The core config of the serving endpoint. + + + +:::list-table + +- - Key + - Type + - Description + +- - `auto_capture_config` + - Map + - Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog. Note: this field is deprecated for creating new provisioned throughput endpoints, or updating existing provisioned throughput endpoints that never have inference table configured; in these cases please use AI Gateway to manage inference tables. See [\_](#model_serving_endpointsnameconfigauto_capture_config). + +- - `served_entities` + - Sequence + - The list of served entities under the serving endpoint config. See [\_](#model_serving_endpointsnameconfigserved_entities). + +- - `served_models` + - Sequence + - (Deprecated, use served_entities instead) The list of served models under the serving endpoint config. See [\_](#model_serving_endpointsnameconfigserved_models). + +- - `traffic_config` + - Map + - The traffic configuration associated with the serving endpoint config. See [\_](#model_serving_endpointsnameconfigtraffic_config). + +::: + + +### model_serving_endpoints._name_.config.auto_capture_config + +**`Type: Map`** + +Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog. +Note: this field is deprecated for creating new provisioned throughput endpoints, +or updating existing provisioned throughput endpoints that never have inference table configured; +in these cases please use AI Gateway to manage inference tables. + + + +:::list-table + +- - Key + - Type + - Description + +- - `catalog_name` + - String + - The name of the catalog in Unity Catalog. NOTE: On update, you cannot change the catalog name if the inference table is already enabled. + +- - `enabled` + - Boolean + - Indicates whether the inference table is enabled. + +- - `schema_name` + - String + - The name of the schema in Unity Catalog. NOTE: On update, you cannot change the schema name if the inference table is already enabled. + +- - `table_name_prefix` + - String + - The prefix of the table in Unity Catalog. NOTE: On update, you cannot change the prefix name if the inference table is already enabled. + +::: + + +### model_serving_endpoints._name_.config.served_entities + +**`Type: Sequence`** + +The list of served entities under the serving endpoint config. + + + +:::list-table + +- - Key + - Type + - Description + +- - `burst_scaling_enabled` + - Boolean + - Whether burst scaling is enabled. When enabled (default), the endpoint can automatically scale up beyond provisioned capacity to handle traffic spikes. When disabled, the endpoint maintains fixed capacity at provisioned_model_units. + +- - `entity_name` + - String + - The name of the entity to be served. The entity may be a model in the Databricks Model Registry, a model in the Unity Catalog (UC), or a function of type FEATURE_SPEC in the UC. If it is a UC object, the full name of the object should be given in the form of **catalog_name.schema_name.model_name**. + +- - `entity_version` + - String + - + +- - `environment_vars` + - Map + - An object containing a set of optional, user-specified environment variable key-value pairs used for serving this entity. Note: this is an experimental feature and subject to change. Example entity environment variables that refer to Databricks secrets: `{"OPENAI_API_KEY": "{{secrets/my_scope/my_key}}", "DATABRICKS_TOKEN": "{{secrets/my_scope2/my_key2}}"}` + +- - `external_model` + - Map + - The external model to be served. NOTE: Only one of external_model and (entity_name, entity_version, workload_size, workload_type, and scale_to_zero_enabled) can be specified with the latter set being used for custom model serving for a Databricks registered model. For an existing endpoint with external_model, it cannot be updated to an endpoint without external_model. If the endpoint is created without external_model, users cannot update it to add external_model later. The task type of all external models within an endpoint must be the same. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_model). + +- - `instance_profile_arn` + - String + - ARN of the instance profile that the served entity uses to access AWS resources. + +- - `max_provisioned_concurrency` + - Integer + - The maximum provisioned concurrency that the endpoint can scale up to. Do not use if workload_size is specified. + +- - `max_provisioned_throughput` + - Integer + - The maximum tokens per second that the endpoint can scale up to. + +- - `min_provisioned_concurrency` + - Integer + - The minimum provisioned concurrency that the endpoint can scale down to. Do not use if workload_size is specified. + +- - `min_provisioned_throughput` + - Integer + - The minimum tokens per second that the endpoint can scale down to. + +- - `name` + - String + - The name of a served entity. It must be unique across an endpoint. A served entity name can consist of alphanumeric characters, dashes, and underscores. If not specified for an external model, this field defaults to external_model.name, with '.' and ':' replaced with '-', and if not specified for other entities, it defaults to entity_name-entity_version. + +- - `provisioned_model_units` + - Integer + - The number of model units provisioned. + +- - `scale_to_zero_enabled` + - Boolean + - Whether the compute resources for the served entity should scale down to zero. + +- - `workload_size` + - String + - The workload size of the served entity. The workload size corresponds to a range of provisioned concurrency that the compute autoscales between. A single unit of provisioned concurrency can process one request at a time. Valid workload sizes are "Small" (4 - 4 provisioned concurrency), "Medium" (8 - 16 provisioned concurrency), and "Large" (16 - 64 provisioned concurrency). Additional custom workload sizes can also be used when available in the workspace. If scale-to-zero is enabled, the lower bound of the provisioned concurrency for each workload size is 0. Do not use if min_provisioned_concurrency and max_provisioned_concurrency are specified. + +- - `workload_type` + - String + - The workload type of the served entity. The workload type selects which type of compute to use in the endpoint. The default value for this parameter is "CPU". For deep learning workloads, GPU acceleration is available by selecting workload types like GPU_SMALL and others. See the available [GPU types](https://docs.databricks.com/en/machine-learning/model-serving/create-manage-serving-endpoints.html#gpu-workload-types). + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model + +**`Type: Map`** + +The external model to be served. NOTE: Only one of external_model and (entity_name, entity_version, workload_size, workload_type, and scale_to_zero_enabled) can be specified with the latter set being used for custom model serving for a Databricks registered model. For an existing endpoint with external_model, it cannot be updated to an endpoint without external_model. If the endpoint is created without external_model, users cannot update it to add external_model later. The task type of all external models within an endpoint must be the same. + + + +:::list-table + +- - Key + - Type + - Description + +- - `ai21labs_config` + - Map + - AI21Labs Config. Only required if the provider is 'ai21labs'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelai21labs_config). + +- - `amazon_bedrock_config` + - Map + - Amazon Bedrock Config. Only required if the provider is 'amazon-bedrock'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelamazon_bedrock_config). + +- - `anthropic_config` + - Map + - Anthropic Config. Only required if the provider is 'anthropic'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelanthropic_config). + +- - `cohere_config` + - Map + - Cohere Config. Only required if the provider is 'cohere'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcohere_config). + +- - `custom_provider_config` + - Map + - Custom Provider Config. Only required if the provider is 'custom'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_config). + +- - `databricks_model_serving_config` + - Map + - Databricks Model Serving Config. Only required if the provider is 'databricks-model-serving'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modeldatabricks_model_serving_config). + +- - `google_cloud_vertex_ai_config` + - Map + - Google Cloud Vertex AI Config. Only required if the provider is 'google-cloud-vertex-ai'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelgoogle_cloud_vertex_ai_config). + +- - `name` + - String + - The name of the external model. + +- - `openai_config` + - Map + - OpenAI Config. Only required if the provider is 'openai'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelopenai_config). + +- - `palm_config` + - Map + - PaLM Config. Only required if the provider is 'palm'. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelpalm_config). + +- - `provider` + - String + - The name of the provider for the external model. Currently, the supported providers are 'ai21labs', 'anthropic', 'amazon-bedrock', 'cohere', 'databricks-model-serving', 'google-cloud-vertex-ai', 'openai', 'palm', and 'custom'. + +- - `task` + - String + - The task type of the external model. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.ai21labs_config + +**`Type: Map`** + +AI21Labs Config. Only required if the provider is 'ai21labs'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `ai21labs_api_key` + - String + - The Databricks secret key reference for an AI21 Labs API key. If you prefer to paste your API key directly, see `ai21labs_api_key_plaintext`. You must provide an API key using one of the following fields: `ai21labs_api_key` or `ai21labs_api_key_plaintext`. + +- - `ai21labs_api_key_plaintext` + - String + - An AI21 Labs API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `ai21labs_api_key`. You must provide an API key using one of the following fields: `ai21labs_api_key` or `ai21labs_api_key_plaintext`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.amazon_bedrock_config + +**`Type: Map`** + +Amazon Bedrock Config. Only required if the provider is 'amazon-bedrock'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `aws_access_key_id` + - String + - The Databricks secret key reference for an AWS access key ID with permissions to interact with Bedrock services. If you prefer to paste your API key directly, see `aws_access_key_id_plaintext`. You must provide an API key using one of the following fields: `aws_access_key_id` or `aws_access_key_id_plaintext`. + +- - `aws_access_key_id_plaintext` + - String + - An AWS access key ID with permissions to interact with Bedrock services provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `aws_access_key_id`. You must provide an API key using one of the following fields: `aws_access_key_id` or `aws_access_key_id_plaintext`. + +- - `aws_region` + - String + - The AWS region to use. Bedrock has to be enabled there. + +- - `aws_secret_access_key` + - String + - The Databricks secret key reference for an AWS secret access key paired with the access key ID, with permissions to interact with Bedrock services. If you prefer to paste your API key directly, see `aws_secret_access_key_plaintext`. You must provide an API key using one of the following fields: `aws_secret_access_key` or `aws_secret_access_key_plaintext`. + +- - `aws_secret_access_key_plaintext` + - String + - An AWS secret access key paired with the access key ID, with permissions to interact with Bedrock services provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `aws_secret_access_key`. You must provide an API key using one of the following fields: `aws_secret_access_key` or `aws_secret_access_key_plaintext`. + +- - `bedrock_provider` + - String + - The underlying provider in Amazon Bedrock. Supported values (case insensitive) include: Anthropic, Cohere, AI21Labs, Amazon. + +- - `instance_profile_arn` + - String + - ARN of the instance profile that the external model will use to access AWS resources. You must authenticate using an instance profile or access keys. If you prefer to authenticate using access keys, see `aws_access_key_id`, `aws_access_key_id_plaintext`, `aws_secret_access_key` and `aws_secret_access_key_plaintext`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.anthropic_config + +**`Type: Map`** + +Anthropic Config. Only required if the provider is 'anthropic'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `anthropic_api_key` + - String + - The Databricks secret key reference for an Anthropic API key. If you prefer to paste your API key directly, see `anthropic_api_key_plaintext`. You must provide an API key using one of the following fields: `anthropic_api_key` or `anthropic_api_key_plaintext`. + +- - `anthropic_api_key_plaintext` + - String + - The Anthropic API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `anthropic_api_key`. You must provide an API key using one of the following fields: `anthropic_api_key` or `anthropic_api_key_plaintext`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.cohere_config + +**`Type: Map`** + +Cohere Config. Only required if the provider is 'cohere'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `cohere_api_base` + - String + - This is an optional field to provide a customized base URL for the Cohere API. If left unspecified, the standard Cohere base URL is used. + +- - `cohere_api_key` + - String + - The Databricks secret key reference for a Cohere API key. If you prefer to paste your API key directly, see `cohere_api_key_plaintext`. You must provide an API key using one of the following fields: `cohere_api_key` or `cohere_api_key_plaintext`. + +- - `cohere_api_key_plaintext` + - String + - The Cohere API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `cohere_api_key`. You must provide an API key using one of the following fields: `cohere_api_key` or `cohere_api_key_plaintext`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config + +**`Type: Map`** + +Custom Provider Config. Only required if the provider is 'custom'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `api_key_auth` + - Map + - This is a field to provide API key authentication for the custom provider API. You can only specify one authentication method. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_configapi_key_auth). + +- - `bearer_token_auth` + - Map + - This is a field to provide bearer token authentication for the custom provider API. You can only specify one authentication method. See [\_](#model_serving_endpointsnameconfigserved_entitiesexternal_modelcustom_provider_configbearer_token_auth). + +- - `custom_provider_url` + - String + - This is a field to provide the URL of the custom provider API. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config.api_key_auth + +**`Type: Map`** + +This is a field to provide API key authentication for the custom provider API. +You can only specify one authentication method. + + + +:::list-table + +- - Key + - Type + - Description + +- - `key` + - String + - The name of the API key parameter used for authentication. + +- - `value` + - String + - The Databricks secret key reference for an API Key. If you prefer to paste your token directly, see `value_plaintext`. + +- - `value_plaintext` + - String + - The API Key provided as a plaintext string. If you prefer to reference your token using Databricks Secrets, see `value`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.custom_provider_config.bearer_token_auth + +**`Type: Map`** + +This is a field to provide bearer token authentication for the custom provider API. +You can only specify one authentication method. + + + +:::list-table + +- - Key + - Type + - Description + +- - `token` + - String + - The Databricks secret key reference for a token. If you prefer to paste your token directly, see `token_plaintext`. + +- - `token_plaintext` + - String + - The token provided as a plaintext string. If you prefer to reference your token using Databricks Secrets, see `token`. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.databricks_model_serving_config + +**`Type: Map`** + +Databricks Model Serving Config. Only required if the provider is 'databricks-model-serving'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `databricks_api_token` + - String + - The Databricks secret key reference for a Databricks API token that corresponds to a user or service principal with Can Query access to the model serving endpoint pointed to by this external model. If you prefer to paste your API key directly, see `databricks_api_token_plaintext`. You must provide an API key using one of the following fields: `databricks_api_token` or `databricks_api_token_plaintext`. + +- - `databricks_api_token_plaintext` + - String + - The Databricks API token that corresponds to a user or service principal with Can Query access to the model serving endpoint pointed to by this external model provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `databricks_api_token`. You must provide an API key using one of the following fields: `databricks_api_token` or `databricks_api_token_plaintext`. + +- - `databricks_workspace_url` + - String + - The URL of the Databricks workspace containing the model serving endpoint pointed to by this external model. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.google_cloud_vertex_ai_config + +**`Type: Map`** + +Google Cloud Vertex AI Config. Only required if the provider is 'google-cloud-vertex-ai'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `private_key` + - String + - The Databricks secret key reference for a private key for the service account which has access to the Google Cloud Vertex AI Service. See [Best practices for managing service account keys]. If you prefer to paste your API key directly, see `private_key_plaintext`. You must provide an API key using one of the following fields: `private_key` or `private_key_plaintext` [Best practices for managing service account keys]: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys + +- - `private_key_plaintext` + - String + - The private key for the service account which has access to the Google Cloud Vertex AI Service provided as a plaintext secret. See [Best practices for managing service account keys]. If you prefer to reference your key using Databricks Secrets, see `private_key`. You must provide an API key using one of the following fields: `private_key` or `private_key_plaintext`. [Best practices for managing service account keys]: https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys + +- - `project_id` + - String + - This is the Google Cloud project id that the service account is associated with. + +- - `region` + - String + - This is the region for the Google Cloud Vertex AI Service. See [supported regions] for more details. Some models are only available in specific regions. [supported regions]: https://cloud.google.com/vertex-ai/docs/general/locations + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.openai_config + +**`Type: Map`** + +OpenAI Config. Only required if the provider is 'openai'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `microsoft_entra_client_id` + - String + - This field is only required for Azure AD OpenAI and is the Microsoft Entra Client ID. + +- - `microsoft_entra_client_secret` + - String + - The Databricks secret key reference for a client secret used for Microsoft Entra ID authentication. If you prefer to paste your client secret directly, see `microsoft_entra_client_secret_plaintext`. You must provide an API key using one of the following fields: `microsoft_entra_client_secret` or `microsoft_entra_client_secret_plaintext`. + +- - `microsoft_entra_client_secret_plaintext` + - String + - The client secret used for Microsoft Entra ID authentication provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `microsoft_entra_client_secret`. You must provide an API key using one of the following fields: `microsoft_entra_client_secret` or `microsoft_entra_client_secret_plaintext`. + +- - `microsoft_entra_tenant_id` + - String + - This field is only required for Azure AD OpenAI and is the Microsoft Entra Tenant ID. + +- - `openai_api_base` + - String + - This is a field to provide a customized base URl for the OpenAI API. For Azure OpenAI, this field is required, and is the base URL for the Azure OpenAI API service provided by Azure. For other OpenAI API types, this field is optional, and if left unspecified, the standard OpenAI base URL is used. + +- - `openai_api_key` + - String + - The Databricks secret key reference for an OpenAI API key using the OpenAI or Azure service. If you prefer to paste your API key directly, see `openai_api_key_plaintext`. You must provide an API key using one of the following fields: `openai_api_key` or `openai_api_key_plaintext`. + +- - `openai_api_key_plaintext` + - String + - The OpenAI API key using the OpenAI or Azure service provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `openai_api_key`. You must provide an API key using one of the following fields: `openai_api_key` or `openai_api_key_plaintext`. + +- - `openai_api_type` + - String + - This is an optional field to specify the type of OpenAI API to use. For Azure OpenAI, this field is required, and adjust this parameter to represent the preferred security access validation protocol. For access token validation, use azure. For authentication using Azure Active Directory (Azure AD) use, azuread. + +- - `openai_api_version` + - String + - This is an optional field to specify the OpenAI API version. For Azure OpenAI, this field is required, and is the version of the Azure OpenAI service to utilize, specified by a date. + +- - `openai_deployment_name` + - String + - This field is only required for Azure OpenAI and is the name of the deployment resource for the Azure OpenAI service. + +- - `openai_organization` + - String + - This is an optional field to specify the organization in OpenAI or Azure OpenAI. + +::: + + +### model_serving_endpoints._name_.config.served_entities.external_model.palm_config + +**`Type: Map`** + +PaLM Config. Only required if the provider is 'palm'. + + + +:::list-table + +- - Key + - Type + - Description + +- - `palm_api_key` + - String + - The Databricks secret key reference for a PaLM API key. If you prefer to paste your API key directly, see `palm_api_key_plaintext`. You must provide an API key using one of the following fields: `palm_api_key` or `palm_api_key_plaintext`. + +- - `palm_api_key_plaintext` + - String + - The PaLM API key provided as a plaintext string. If you prefer to reference your key using Databricks Secrets, see `palm_api_key`. You must provide an API key using one of the following fields: `palm_api_key` or `palm_api_key_plaintext`. + +::: + + +### model_serving_endpoints._name_.config.served_models + +**`Type: Sequence`** + +(Deprecated, use served_entities instead) The list of served models under the serving endpoint config. + + + +:::list-table + +- - Key + - Type + - Description + +- - `burst_scaling_enabled` + - Boolean + - Whether burst scaling is enabled. When enabled (default), the endpoint can automatically scale up beyond provisioned capacity to handle traffic spikes. When disabled, the endpoint maintains fixed capacity at provisioned_model_units. + +- - `environment_vars` + - Map + - An object containing a set of optional, user-specified environment variable key-value pairs used for serving this entity. Note: this is an experimental feature and subject to change. Example entity environment variables that refer to Databricks secrets: `{"OPENAI_API_KEY": "{{secrets/my_scope/my_key}}", "DATABRICKS_TOKEN": "{{secrets/my_scope2/my_key2}}"}` + +- - `instance_profile_arn` + - String + - ARN of the instance profile that the served entity uses to access AWS resources. + +- - `max_provisioned_concurrency` + - Integer + - The maximum provisioned concurrency that the endpoint can scale up to. Do not use if workload_size is specified. + +- - `max_provisioned_throughput` + - Integer + - The maximum tokens per second that the endpoint can scale up to. + +- - `min_provisioned_concurrency` + - Integer + - The minimum provisioned concurrency that the endpoint can scale down to. Do not use if workload_size is specified. + +- - `min_provisioned_throughput` + - Integer + - The minimum tokens per second that the endpoint can scale down to. + +- - `model_name` + - String + - + +- - `model_version` + - String + - + +- - `name` + - String + - The name of a served entity. It must be unique across an endpoint. A served entity name can consist of alphanumeric characters, dashes, and underscores. If not specified for an external model, this field defaults to external_model.name, with '.' and ':' replaced with '-', and if not specified for other entities, it defaults to entity_name-entity_version. + +- - `provisioned_model_units` + - Integer + - The number of model units provisioned. + +- - `scale_to_zero_enabled` + - Boolean + - Whether the compute resources for the served entity should scale down to zero. + +- - `workload_size` + - String + - The workload size of the served entity. The workload size corresponds to a range of provisioned concurrency that the compute autoscales between. A single unit of provisioned concurrency can process one request at a time. Valid workload sizes are "Small" (4 - 4 provisioned concurrency), "Medium" (8 - 16 provisioned concurrency), and "Large" (16 - 64 provisioned concurrency). Additional custom workload sizes can also be used when available in the workspace. If scale-to-zero is enabled, the lower bound of the provisioned concurrency for each workload size is 0. Do not use if min_provisioned_concurrency and max_provisioned_concurrency are specified. + +- - `workload_type` + - String + - The workload type of the served entity. The workload type selects which type of compute to use in the endpoint. The default value for this parameter is "CPU". For deep learning workloads, GPU acceleration is available by selecting workload types like GPU_SMALL and others. See the available [GPU types](https://docs.databricks.com/en/machine-learning/model-serving/create-manage-serving-endpoints.html#gpu-workload-types). + +::: + + +### model_serving_endpoints._name_.config.traffic_config + +**`Type: Map`** + +The traffic configuration associated with the serving endpoint config. + + + +:::list-table + +- - Key + - Type + - Description + +- - `routes` + - Sequence + - The list of routes that define traffic to each served entity. See [\_](#model_serving_endpointsnameconfigtraffic_configroutes). + +::: + + +### model_serving_endpoints._name_.config.traffic_config.routes + +**`Type: Sequence`** + +The list of routes that define traffic to each served entity. + + + +:::list-table + +- - Key + - Type + - Description + +- - `served_entity_name` + - String + - + +- - `served_model_name` + - String + - The name of the served model this route configures traffic for. + +- - `traffic_percentage` + - Integer + - The percentage of endpoint traffic to send to this route. It must be an integer between 0 and 100 inclusive. + +::: + + +### model_serving_endpoints._name_.email_notifications + +**`Type: Map`** + +Email notification settings. + + + +:::list-table + +- - Key + - Type + - Description + +- - `on_update_failure` + - Sequence + - A list of email addresses to be notified when an endpoint fails to update its configuration or state. + +- - `on_update_success` + - Sequence + - A list of email addresses to be notified when an endpoint successfully updates its configuration or state. + +::: + + +### model_serving_endpoints._name_.lifecycle + +**`Type: Map`** + +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. + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### model_serving_endpoints._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - + +- - `level` + - String + - Permission level + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - + +::: + + +### model_serving_endpoints._name_.tags + +**`Type: Sequence`** + +Tags to be attached to the serving endpoint and automatically propagated to billing logs. + + + +:::list-table + +- - Key + - Type + - Description + +- - `key` + - String + - Key field for a serving endpoint tag. + +- - `value` + - String + - Optional value field for a serving endpoint tag. + +::: + + +## models + +**`Type: Map`** + +The model resource allows you to define [legacy models](/api/workspace/modelregistry/createmodel) in bundles. Databricks recommends you use Unity Catalog [registered models](#registered-model) instead. + +```yaml +models: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `description` + - String + - Optional description for registered model. + +- - `lifecycle` + - Map + - 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. See [\_](#modelsnamelifecycle). + +- - `name` + - String + - Register models under this name + +- - `permissions` + - Sequence + - See [\_](#modelsnamepermissions). + +- - `tags` + - Sequence + - Additional metadata for registered model. See [\_](#modelsnametags). + +::: + + +### models._name_.lifecycle + +**`Type: Map`** + +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. + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### models._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - + +- - `level` + - String + - Permission level + +- - `service_principal_name` + - String + - + +- - `user_name` + - String + - + +::: + + +### models._name_.tags + +**`Type: Sequence`** + +Additional metadata for registered model. + + + +:::list-table + +- - Key + - Type + - Description + +- - `key` + - String + - The tag key. + +- - `value` + - String + - The tag value. + +::: + + +## pipelines + +**`Type: Map`** + +This resource allows you to create [pipelines](/api/workspace/pipelines/create). For information about pipelines, see [_](/dlt/index.md). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [_](/dev-tools/bundles/pipelines-tutorial.md). + +```yaml +pipelines: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `allow_duplicate_names` + - Boolean + - If false, deployment will fail if name conflicts with that of another pipeline. + +- - `budget_policy_id` + - String + - Budget policy of this pipeline. + +- - `catalog` + - String + - A catalog in Unity Catalog to publish data from this pipeline to. If `target` is specified, tables in this pipeline are published to a `target` schema inside `catalog` (for example, `catalog`.`target`.`table`). If `target` is not specified, no data is published to Unity Catalog. + +- - `channel` + - String + - DLT Release Channel that specifies which version to use. + +- - `clusters` + - Sequence + - Cluster settings for this pipeline deployment. See [\_](#pipelinesnameclusters). + +- - `configuration` + - Map + - String-String configuration for this pipeline execution. + +- - `continuous` + - Boolean + - Whether the pipeline is continuous or triggered. This replaces `trigger`. + +- - `deployment` + - Map + - Deployment type of this pipeline. See [\_](#pipelinesnamedeployment). + +- - `development` + - Boolean + - Whether the pipeline is in Development mode. Defaults to false. + +- - `dry_run` + - Boolean + - + +- - `edition` + - String + - Pipeline product edition. + +- - `environment` + - Map + - Environment specification for this pipeline used to install dependencies. See [\_](#pipelinesnameenvironment). + +- - `event_log` + - Map + - Event log configuration for this pipeline. See [\_](#pipelinesnameevent_log). + +- - `filters` + - Map + - Filters on which Pipeline packages to include in the deployed graph. See [\_](#pipelinesnamefilters). + +- - `id` + - String + - Unique identifier for this pipeline. + +- - `ingestion_definition` + - Map + - The configuration for a managed ingestion pipeline. These settings cannot be used with the 'libraries', 'schema', 'target', or 'catalog' settings. See [\_](#pipelinesnameingestion_definition). + +- - `libraries` + - Sequence + - Libraries or code needed by this deployment. See [\_](#pipelinesnamelibraries). + +- - `lifecycle` + - Map + - 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. See [\_](#pipelinesnamelifecycle). + +- - `name` + - String + - Friendly identifier for this pipeline. + +- - `notifications` + - Sequence + - List of notification settings for this pipeline. See [\_](#pipelinesnamenotifications). + +- - `permissions` + - Sequence + - See [\_](#pipelinesnamepermissions). + +- - `photon` + - Boolean + - Whether Photon is enabled for this pipeline. + +- - `root_path` + - String + - Root path for this pipeline. This is used as the root directory when editing the pipeline in the Databricks user interface and it is added to sys.path when executing Python sources during pipeline execution. + +- - `run_as` + - Map + - Write-only setting, available only in Create/Update calls. Specifies the user or service principal that the pipeline runs as. If not specified, the pipeline runs as the user who created the pipeline. Only `user_name` or `service_principal_name` can be specified. If both are specified, an error is thrown. See [\_](#pipelinesnamerun_as). + +- - `schema` + - String + - The default schema (database) where tables are read from or published to. + +- - `serverless` + - Boolean + - Whether serverless compute is enabled for this pipeline. + +- - `storage` + - String + - DBFS root directory for storing checkpoints and tables. + +- - `tags` + - Map + - A map of tags associated with the pipeline. These are forwarded to the cluster as cluster tags, and are therefore subject to the same limitations. A maximum of 25 tags can be added to the pipeline. + +- - `target` + - String + - This field is deprecated + +- - `trigger` + - Map + - Use continuous instead + +::: + + +**Example** + +The following example defines a pipeline with the resource key `hello-pipeline`: + +```yaml +resources: + pipelines: + hello-pipeline: + name: hello-pipeline + clusters: + - label: default + num_workers: 1 + development: true + continuous: false + channel: CURRENT + edition: CORE + photon: false + libraries: + - notebook: + path: ./pipeline.py +``` + +### pipelines._name_.clusters + +**`Type: Sequence`** + +Cluster settings for this pipeline deployment. + + + +:::list-table + +- - Key + - Type + - Description + +- - `apply_policy_default_values` + - Boolean + - Note: This field won't be persisted. Only API users will check this field. + +- - `autoscale` + - Map + - Parameters needed in order to automatically scale clusters up and down based on load. Note: autoscaling works best with DB runtime versions 3.0 or later. See [\_](#pipelinesnameclustersautoscale). + +- - `aws_attributes` + - Map + - Attributes related to clusters running on Amazon Web Services. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersaws_attributes). + +- - `azure_attributes` + - Map + - Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersazure_attributes). + +- - `cluster_log_conf` + - Map + - The configuration for delivering spark logs to a long-term storage destination. Only dbfs destinations are supported. Only one destination can be specified for one cluster. If the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is `$destination/$clusterId/executor`. See [\_](#pipelinesnameclusterscluster_log_conf). + +- - `custom_tags` + - Map + - Additional tags for cluster resources. Databricks will tag all cluster resources (e.g., AWS instances and EBS volumes) with these tags in addition to `default_tags`. Notes: - Currently, Databricks allows at most 45 custom tags - Clusters can only reuse cloud resources if the resources' tags are a subset of the cluster tags + +- - `driver_instance_pool_id` + - String + - The optional ID of the instance pool for the driver of the cluster belongs. The pool cluster uses the instance pool with id (instance_pool_id) if the driver pool is not assigned. + +- - `driver_node_type_id` + - String + - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. + +- - `enable_local_disk_encryption` + - Boolean + - Whether to enable local disk encryption for the cluster. + +- - `gcp_attributes` + - Map + - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersgcp_attributes). + +- - `init_scripts` + - Sequence + - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#pipelinesnameclustersinit_scripts). + +- - `instance_pool_id` + - String + - The optional ID of the instance pool to which the cluster belongs. + +- - `label` + - String + - A label for the cluster specification, either `default` to configure the default cluster, or `maintenance` to configure the maintenance cluster. This field is optional. The default value is `default`. + +- - `node_type_id` + - String + - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. + +- - `num_workers` + - Integer + - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. + +- - `policy_id` + - String + - The ID of the cluster policy used to create the cluster if applicable. + +- - `spark_conf` + - Map + - An object containing a set of optional, user-specified Spark configuration key-value pairs. See :method:clusters/create for more details. + +- - `spark_env_vars` + - Map + - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` + +- - `ssh_public_keys` + - Sequence + - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. + +::: + + +### pipelines._name_.clusters.autoscale + +**`Type: Map`** + +Parameters needed in order to automatically scale clusters up and down based on load. +Note: autoscaling works best with DB runtime versions 3.0 or later. + + + +:::list-table + +- - Key + - Type + - Description + +- - `max_workers` + - Integer + - The maximum number of workers to which the cluster can scale up when overloaded. `max_workers` must be strictly greater than `min_workers`. + +- - `min_workers` + - Integer + - The minimum number of workers the cluster can scale down to when underutilized. It is also the initial number of workers the cluster will have after creation. + +- - `mode` + - String + - Databricks Enhanced Autoscaling optimizes cluster utilization by automatically allocating cluster resources based on workload volume, with minimal impact to the data processing latency of your pipelines. Enhanced Autoscaling is available for `updates` clusters only. The legacy autoscaling feature is used for `maintenance` clusters. + +::: + + +### pipelines._name_.clusters.aws_attributes + +**`Type: Map`** + +Attributes related to clusters running on Amazon Web Services. +If not specified at cluster creation, a set of default values will be used. + + + +:::list-table + +- - Key + - Type + - Description + +- - `availability` + - String + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. + +- - `ebs_volume_count` + - Integer + - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. + +- - `ebs_volume_iops` + - Integer + - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + +- - `ebs_volume_size` + - Integer + - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. + +- - `ebs_volume_throughput` + - Integer + - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + +- - `ebs_volume_type` + - String + - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. + +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + +- - `instance_profile_arn` + - String + - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. + +- - `spot_bid_price_percent` + - Integer + - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. + +- - `zone_id` + - String + - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, the zone "auto" will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. + +::: + + +### pipelines._name_.clusters.azure_attributes + +**`Type: Map`** + +Attributes related to clusters running on Microsoft Azure. +If not specified at cluster creation, a set of default values will be used. + + + +:::list-table + +- - Key + - Type + - Description + +- - `availability` + - String + - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. + +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + +- - `log_analytics_info` + - Map + - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#pipelinesnameclustersazure_attributeslog_analytics_info). + +- - `spot_bid_max_price` + - Any + - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. + +::: + + +### pipelines._name_.clusters.azure_attributes.log_analytics_info + +**`Type: Map`** + +Defines values necessary to configure and run Azure Log Analytics agent + + + +:::list-table + +- - Key + - Type + - Description + +- - `log_analytics_primary_key` + - String + - The primary key for the Azure Log Analytics agent configuration + +- - `log_analytics_workspace_id` + - String + - The workspace ID for the Azure Log Analytics agent configuration + +::: + + +### pipelines._name_.clusters.cluster_log_conf + +**`Type: Map`** + +The configuration for delivering spark logs to a long-term storage destination. +Only dbfs destinations are supported. Only one destination can be specified +for one cluster. If the conf is given, the logs will be delivered to the destination every +`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while +the destination of executor logs is `$destination/$clusterId/executor`. + + + +:::list-table + +- - Key + - Type + - Description + +- - `dbfs` + - Map + - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#pipelinesnameclusterscluster_log_confdbfs). + +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#pipelinesnameclusterscluster_log_confs3). + +- - `volumes` + - Map + - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#pipelinesnameclusterscluster_log_confvolumes). + +::: + + +### pipelines._name_.clusters.cluster_log_conf.dbfs + +**`Type: Map`** + +destination needs to be provided. e.g. +`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - dbfs destination, e.g. `dbfs:/my/path` + +::: + + +### pipelines._name_.clusters.cluster_log_conf.s3 + +**`Type: Map`** + +destination and either the region or endpoint need to be provided. e.g. +`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. + + + +:::list-table + +- - Key + - Type + - Description + +- - `canned_acl` + - String + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. + +- - `destination` + - String + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. + +- - `enable_encryption` + - Boolean + - (Optional) Flag to enable server side encryption, `false` by default. + +- - `encryption_type` + - String + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. + +- - `endpoint` + - String + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + +- - `kms_key` + - String + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + +- - `region` + - String + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + +::: + + +### pipelines._name_.clusters.cluster_log_conf.volumes + +**`Type: Map`** + +destination needs to be provided, e.g. +`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` + +::: + + +### pipelines._name_.clusters.gcp_attributes + +**`Type: Map`** + +Attributes related to clusters running on Google Cloud Platform. +If not specified at cluster creation, a set of default values will be used. + + + +:::list-table + +- - Key + - Type + - Description + +- - `availability` + - String + - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. + +- - `boot_disk_size` + - Integer + - Boot disk size in GB + +- - `first_on_demand` + - Integer + - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. + +- - `google_service_account` + - String + - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. + +- - `local_ssd_count` + - Integer + - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. + +- - `use_preemptible_executors` + - Boolean + - This field is deprecated + +- - `zone_id` + - String + - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. + +::: + + +### pipelines._name_.clusters.init_scripts + +**`Type: Sequence`** + +The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. + + + +:::list-table + +- - Key + - Type + - Description + +- - `abfss` + - Map + - Contains the Azure Data Lake Storage destination path. See [\_](#pipelinesnameclustersinit_scriptsabfss). + +- - `dbfs` + - Map + - This field is deprecated + +- - `file` + - Map + - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsfile). + +- - `gcs` + - Map + - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsgcs). + +- - `s3` + - Map + - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#pipelinesnameclustersinit_scriptss3). + +- - `volumes` + - Map + - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#pipelinesnameclustersinit_scriptsvolumes). + +- - `workspace` + - Map + - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsworkspace). + +::: + + +### pipelines._name_.clusters.init_scripts.abfss + +**`Type: Map`** + +Contains the Azure Data Lake Storage destination path + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. + +::: + + +### pipelines._name_.clusters.init_scripts.file + +**`Type: Map`** + +destination needs to be provided, e.g. +`{ "file": { "destination": "file:/my/local/file.sh" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - local file destination, e.g. `file:/my/local/file.sh` + +::: + + +### pipelines._name_.clusters.init_scripts.gcs + +**`Type: Map`** + +destination needs to be provided, e.g. +`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` + +::: + + +### pipelines._name_.clusters.init_scripts.s3 + +**`Type: Map`** + +destination and either the region or endpoint need to be provided. e.g. +`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` +Cluster iam role is used to access s3, please make sure the cluster iam role in +`instance_profile_arn` has permission to write data to the s3 destination. + + + +:::list-table + +- - Key + - Type + - Description + +- - `canned_acl` + - String + - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. + +- - `destination` + - String + - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. + +- - `enable_encryption` + - Boolean + - (Optional) Flag to enable server side encryption, `false` by default. + +- - `encryption_type` + - String + - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. + +- - `endpoint` + - String + - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + +- - `kms_key` + - String + - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + +- - `region` + - String + - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + +::: + + +### pipelines._name_.clusters.init_scripts.volumes + +**`Type: Map`** + +destination needs to be provided. e.g. +`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` + +::: + + +### pipelines._name_.clusters.init_scripts.workspace + +**`Type: Map`** + +destination needs to be provided, e.g. +`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` + + + +:::list-table + +- - Key + - Type + - Description + +- - `destination` + - String + - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` + +::: + + +### pipelines._name_.deployment + +**`Type: Map`** + +Deployment type of this pipeline. + + + +:::list-table + +- - Key + - Type + - Description + +- - `kind` + - String + - The deployment method that manages the pipeline. + +- - `metadata_file_path` + - String + - The path to the file containing metadata about the deployment. + +::: + + +### pipelines._name_.environment + +**`Type: Map`** + +Environment specification for this pipeline used to install dependencies. + + + +:::list-table + +- - Key + - Type + - Description + +- - `dependencies` + - Sequence + - List of pip dependencies, as supported by the version of pip in this environment. Each dependency is a pip requirement file line https://pip.pypa.io/en/stable/reference/requirements-file-format/ Allowed dependency could be , , (WSFS or Volumes in Databricks), + +::: + + +### pipelines._name_.event_log + +**`Type: Map`** + +Event log configuration for this pipeline + + + +:::list-table + +- - Key + - Type + - Description + +- - `catalog` + - String + - The UC catalog the event log is published under. + +- - `name` + - String + - The name the event log is published to in UC. -- - `driver_node_type_id` +- - `schema` - String - - The node type of the Spark driver. Note that this field is optional; if unset, the driver node type will be set as the same value as `node_type_id` defined above. + - The UC schema the event log is published under. -- - `enable_local_disk_encryption` - - Boolean - - Whether to enable local disk encryption for the cluster. +::: -- - `gcp_attributes` - - Map - - Attributes related to clusters running on Google Cloud Platform. If not specified at cluster creation, a set of default values will be used. See [\_](#pipelinesnameclustersgcp_attributes). -- - `init_scripts` - - Sequence - - The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. See [\_](#pipelinesnameclustersinit_scripts). +### pipelines._name_.filters -- - `instance_pool_id` - - String - - The optional ID of the instance pool to which the cluster belongs. +**`Type: Map`** -- - `label` - - String - - A label for the cluster specification, either `default` to configure the default cluster, or `maintenance` to configure the maintenance cluster. This field is optional. The default value is `default`. +Filters on which Pipeline packages to include in the deployed graph. -- - `node_type_id` - - String - - This field encodes, through a single value, the resources available to each of the Spark nodes in this cluster. For example, the Spark nodes can be provisioned and optimized for memory or compute intensive workloads. A list of available node types can be retrieved by using the :method:clusters/listNodeTypes API call. -- - `num_workers` - - Integer - - Number of worker nodes that this cluster should have. A cluster has one Spark Driver and `num_workers` Executors for a total of `num_workers` + 1 Spark nodes. Note: When reading the properties of a cluster, this field reflects the desired number of workers rather than the actual current number of workers. For instance, if a cluster is resized from 5 to 10 workers, this field will immediately be updated to reflect the target size of 10 workers, whereas the workers listed in `spark_info` will gradually increase from 5 to 10 as the new nodes are provisioned. -- - `policy_id` - - String - - The ID of the cluster policy used to create the cluster if applicable. +:::list-table -- - `spark_conf` - - Map - - An object containing a set of optional, user-specified Spark configuration key-value pairs. See :method:clusters/create for more details. +- - Key + - Type + - Description -- - `spark_env_vars` - - Map - - An object containing a set of optional, user-specified environment variable key-value pairs. Please note that key-value pair of the form (X,Y) will be exported as is (i.e., `export X='Y'`) while launching the driver and workers. In order to specify an additional set of `SPARK_DAEMON_JAVA_OPTS`, we recommend appending them to `$SPARK_DAEMON_JAVA_OPTS` as shown in the example below. This ensures that all default databricks managed environmental variables are included as well. Example Spark environment variables: `{"SPARK_WORKER_MEMORY": "28000m", "SPARK_LOCAL_DIRS": "/local_disk0"}` or `{"SPARK_DAEMON_JAVA_OPTS": "$SPARK_DAEMON_JAVA_OPTS -Dspark.shuffle.service.enabled=true"}` +- - `exclude` + - Sequence + - Paths to exclude. -- - `ssh_public_keys` +- - `include` - Sequence - - SSH public key contents that will be added to each Spark node in this cluster. The corresponding private keys can be used to login with the user name `ubuntu` on port `2200`. Up to 10 keys can be specified. + - Paths to include. ::: -### pipelines._name_.clusters.autoscale +### pipelines._name_.ingestion_definition **`Type: Map`** -Parameters needed in order to automatically scale clusters up and down based on load. -Note: autoscaling works best with DB runtime versions 3.0 or later. +The configuration for a managed ingestion pipeline. These settings cannot be used with the 'libraries', 'schema', 'target', or 'catalog' settings. @@ -7043,27 +9120,38 @@ Note: autoscaling works best with DB runtime versions 3.0 or later. - Type - Description -- - `max_workers` - - Integer - - The maximum number of workers to which the cluster can scale up when overloaded. `max_workers` must be strictly greater than `min_workers`. +- - `connection_name` + - String + - The Unity Catalog connection that this ingestion pipeline uses to communicate with the source. This is used with both connectors for applications like Salesforce, Workday, and so on, and also database connectors like Oracle, (connector_type = QUERY_BASED OR connector_type = CDC). If connection name corresponds to database connectors like Oracle, and connector_type is not provided then connector_type defaults to QUERY_BASED. If connector_type is passed as CDC we use Combined Cdc Managed Ingestion pipeline. Under certain conditions, this can be replaced with ingestion_gateway_id to change the connector to Cdc Managed Ingestion Pipeline with Gateway pipeline. -- - `min_workers` - - Integer - - The minimum number of workers the cluster can scale down to when underutilized. It is also the initial number of workers the cluster will have after creation. +- - `full_refresh_window` + - Map + - (Optional) A window that specifies a set of time ranges for snapshot queries in CDC. See [\_](#pipelinesnameingestion_definitionfull_refresh_window). -- - `mode` +- - `ingestion_gateway_id` - String - - Databricks Enhanced Autoscaling optimizes cluster utilization by automatically allocating cluster resources based on workload volume, with minimal impact to the data processing latency of your pipelines. Enhanced Autoscaling is available for `updates` clusters only. The legacy autoscaling feature is used for `maintenance` clusters. + - Identifier for the gateway that is used by this ingestion pipeline to communicate with the source database. This is used with CDC connectors to databases like SQL Server using a gateway pipeline (connector_type = CDC). Under certain conditions, this can be replaced with connection_name to change the connector to Combined Cdc Managed Ingestion Pipeline. + +- - `objects` + - Sequence + - Required. Settings specifying tables to replicate and the destination for the replicated tables. See [\_](#pipelinesnameingestion_definitionobjects). + +- - `source_configurations` + - Sequence + - Top-level source configurations. See [\_](#pipelinesnameingestion_definitionsource_configurations). + +- - `table_configuration` + - Map + - Configuration settings to control the ingestion of tables. These settings are applied to all tables in the pipeline. See [\_](#pipelinesnameingestion_definitiontable_configuration). ::: -### pipelines._name_.clusters.aws_attributes +### pipelines._name_.ingestion_definition.full_refresh_window **`Type: Map`** -Attributes related to clusters running on Amazon Web Services. -If not specified at cluster creation, a set of default values will be used. +(Optional) A window that specifies a set of time ranges for snapshot queries in CDC. @@ -7073,55 +9161,34 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` - - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. - -- - `ebs_volume_count` - - Integer - - The number of volumes launched for each instance. Users can choose up to 10 volumes. This feature is only enabled for supported node types. Legacy node types cannot specify custom EBS volumes. For node types with no instance store, at least one EBS volume needs to be specified; otherwise, cluster creation will fail. These EBS volumes will be mounted at `/ebs0`, `/ebs1`, and etc. Instance store volumes will be mounted at `/local_disk0`, `/local_disk1`, and etc. If EBS volumes are attached, Databricks will configure Spark to use only the EBS volumes for scratch storage because heterogenously sized scratch devices can lead to inefficient disk utilization. If no EBS volumes are attached, Databricks will configure Spark to use instance store volumes. Please note that if EBS volumes are specified, then the Spark configuration `spark.local.dir` will be overridden. - -- - `ebs_volume_iops` - - Integer - - If using gp3 volumes, what IOPS to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. - -- - `ebs_volume_size` - - Integer - - The size of each EBS volume (in GiB) launched for each instance. For general purpose SSD, this value must be within the range 100 - 4096. For throughput optimized HDD, this value must be within the range 500 - 4096. +- - `days_of_week` + - Sequence + - Days of week in which the window is allowed to happen If not specified all days of the week will be used. -- - `ebs_volume_throughput` +- - `start_hour` - Integer - - If using gp3 volumes, what throughput to use for the disk. If this is not set, the maximum performance of a gp2 volume with the same volume size will be used. + - An integer between 0 and 23 denoting the start hour for the window in the 24-hour day. -- - `ebs_volume_type` +- - `time_zone_id` - String - - All EBS volume types that Databricks supports. See https://aws.amazon.com/ebs/details/ for details. + - Time zone id of window. See https://docs.databricks.com/sql/language-manual/sql-ref-syntax-aux-conf-mgmt-set-timezone.html for details. If not specified, UTC will be used. -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. If this value is greater than 0, the cluster driver node in particular will be placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. +::: -- - `instance_profile_arn` - - String - - Nodes for this cluster will only be placed on AWS instances with this instance profile. If ommitted, nodes will be placed on instances without an IAM instance profile. The instance profile must have previously been added to the Databricks environment by an account administrator. This feature may only be available to certain customer plans. -- - `spot_bid_price_percent` - - Integer - - The bid price for AWS spot instances, as a percentage of the corresponding instance type's on-demand price. For example, if this field is set to 50, and the cluster needs a new `r3.xlarge` spot instance, then the bid price is half of the price of on-demand `r3.xlarge` instances. Similarly, if this field is set to 200, the bid price is twice the price of on-demand `r3.xlarge` instances. If not specified, the default value is 100. When spot instances are requested for this cluster, only spot instances whose bid price percentage matches this field will be considered. Note that, for safety, we enforce this field to be no more than 10000. +### pipelines._name_.ingestion_definition.full_refresh_window.days_of_week -- - `zone_id` - - String - - Identifier for the availability zone/datacenter in which the cluster resides. This string will be of a form like "us-west-2a". The provided availability zone must be in the same region as the Databricks deployment. For example, "us-west-2a" is not a valid zone id if the Databricks deployment resides in the "us-east-1" region. This is an optional field at cluster creation, and if not specified, a default zone will be used. If the zone specified is "auto", will try to place cluster in a zone with high availability, and will retry placement in a different AZ if there is not enough capacity. The list of available zones as well as the default value can be found by using the `List Zones` method. +**`Type: Sequence`** -::: +Days of week in which the window is allowed to happen +If not specified all days of the week will be used. -### pipelines._name_.clusters.azure_attributes +### pipelines._name_.ingestion_definition.objects -**`Type: Map`** +**`Type: Sequence`** -Attributes related to clusters running on Microsoft Azure. -If not specified at cluster creation, a set of default values will be used. +Required. Settings specifying tables to replicate and the destination for the replicated tables. @@ -7131,30 +9198,26 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` - - String - - Availability type used for all subsequent nodes past the `first_on_demand` ones. Note: If `first_on_demand` is zero, this availability type will be used for the entire cluster. - -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. +- - `report` + - Map + - Select a specific source report. See [\_](#pipelinesnameingestion_definitionobjectsreport). -- - `log_analytics_info` +- - `schema` - Map - - Defines values necessary to configure and run Azure Log Analytics agent. See [\_](#pipelinesnameclustersazure_attributeslog_analytics_info). + - Select all tables from a specific source schema. See [\_](#pipelinesnameingestion_definitionobjectsschema). -- - `spot_bid_max_price` - - Any - - The max bid price to be used for Azure spot instances. The Max price for the bid cannot be higher than the on-demand price of the instance. If not specified, the default value is -1, which specifies that the instance cannot be evicted on the basis of price, and only on the basis of availability. Further, the value should > 0 or -1. +- - `table` + - Map + - Select a specific source table. See [\_](#pipelinesnameingestion_definitionobjectstable). ::: -### pipelines._name_.clusters.azure_attributes.log_analytics_info +### pipelines._name_.ingestion_definition.objects.report **`Type: Map`** -Defines values necessary to configure and run Azure Log Analytics agent +Select a specific source report. @@ -7164,26 +9227,34 @@ Defines values necessary to configure and run Azure Log Analytics agent - Type - Description -- - `log_analytics_primary_key` +- - `destination_catalog` - String - - The primary key for the Azure Log Analytics agent configuration + - Required. Destination catalog to store table. -- - `log_analytics_workspace_id` +- - `destination_schema` - String - - The workspace ID for the Azure Log Analytics agent configuration + - Required. Destination schema to store table. + +- - `destination_table` + - String + - Required. Destination table name. The pipeline fails if a table with that name already exists. + +- - `source_url` + - String + - Required. Report URL in the source system. + +- - `table_configuration` + - Map + - Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object. See [\_](#pipelinesnameingestion_definitionobjectsreporttable_configuration). ::: -### pipelines._name_.clusters.cluster_log_conf +### pipelines._name_.ingestion_definition.objects.report.table_configuration **`Type: Map`** -The configuration for delivering spark logs to a long-term storage destination. -Only dbfs destinations are supported. Only one destination can be specified -for one cluster. If the conf is given, the logs will be delivered to the destination every -`5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while -the destination of executor logs is `$destination/$clusterId/executor`. +Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object. @@ -7193,27 +9264,44 @@ the destination of executor logs is `$destination/$clusterId/executor`. - Type - Description -- - `dbfs` +- - `auto_full_refresh_policy` - Map - - destination needs to be provided. e.g. `{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }`. See [\_](#pipelinesnameclusterscluster_log_confdbfs). + - (Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy in table configuration will override the above level auto_full_refresh_policy. For example, { "auto_full_refresh_policy": { "enabled": true, "min_interval_hours": 23, } } If unspecified, auto full refresh is disabled. See [\_](#pipelinesnameingestion_definitionobjectsreporttable_configurationauto_full_refresh_policy). -- - `s3` - - Map - - destination and either the region or endpoint need to be provided. e.g. `{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#pipelinesnameclusterscluster_log_confs3). +- - `exclude_columns` + - Sequence + - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. -- - `volumes` - - Map - - destination needs to be provided, e.g. `{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }`. See [\_](#pipelinesnameclusterscluster_log_confvolumes). +- - `include_columns` + - Sequence + - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. + +- - `primary_keys` + - Sequence + - The primary key of the table used to apply changes. + +- - `sequence_by` + - Sequence + - The column names specifying the logical order of events in the source data. Spark Declarative Pipelines uses this sequencing to handle change events that arrive out of order. ::: -### pipelines._name_.clusters.cluster_log_conf.dbfs +### pipelines._name_.ingestion_definition.objects.report.table_configuration.auto_full_refresh_policy **`Type: Map`** -destination needs to be provided. e.g. -`{ "dbfs" : { "destination" : "dbfs:/home/cluster_log" } }` +(Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try +to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy +in table configuration will override the above level auto_full_refresh_policy. +For example, +{ +"auto_full_refresh_policy": { +"enabled": true, +"min_interval_hours": 23, +} +} +If unspecified, auto full refresh is disabled. @@ -7223,21 +9311,22 @@ destination needs to be provided. e.g. - Type - Description -- - `destination` - - String - - dbfs destination, e.g. `dbfs:/my/path` +- - `enabled` + - Boolean + - (Required, Mutable) Whether to enable auto full refresh or not. + +- - `min_interval_hours` + - Integer + - (Optional, Mutable) Specify the minimum interval in hours between the timestamp at which a table was last full refreshed and the current timestamp for triggering auto full If unspecified and autoFullRefresh is enabled then by default min_interval_hours is 24 hours. ::: -### pipelines._name_.clusters.cluster_log_conf.s3 +### pipelines._name_.ingestion_definition.objects.schema **`Type: Map`** -destination and either the region or endpoint need to be provided. e.g. -`{ "s3": { "destination" : "s3://cluster_log_bucket/prefix", "region" : "us-west-2" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. +Select all tables from a specific source schema. @@ -7247,43 +9336,81 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` +- - `destination_catalog` - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. + - Required. Destination catalog to store tables. -- - `destination` +- - `destination_schema` - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. + - Required. Destination schema to store tables in. Tables with the same name as the source tables are created in this destination schema. The pipeline fails If a table with the same name already exists. -- - `enable_encryption` - - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. +- - `source_catalog` + - String + - The source catalog name. Might be optional depending on the type of source. -- - `encryption_type` +- - `source_schema` - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. + - Required. Schema name in the source database. + +- - `table_configuration` + - Map + - Configuration settings to control the ingestion of tables. These settings are applied to all tables in this schema and override the table_configuration defined in the IngestionPipelineDefinition object. See [\_](#pipelinesnameingestion_definitionobjectsschematable_configuration). + +::: + + +### pipelines._name_.ingestion_definition.objects.schema.table_configuration + +**`Type: Map`** + +Configuration settings to control the ingestion of tables. These settings are applied to all tables in this schema and override the table_configuration defined in the IngestionPipelineDefinition object. + + + +:::list-table + +- - Key + - Type + - Description + +- - `auto_full_refresh_policy` + - Map + - (Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy in table configuration will override the above level auto_full_refresh_policy. For example, { "auto_full_refresh_policy": { "enabled": true, "min_interval_hours": 23, } } If unspecified, auto full refresh is disabled. See [\_](#pipelinesnameingestion_definitionobjectsschematable_configurationauto_full_refresh_policy). + +- - `exclude_columns` + - Sequence + - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. -- - `endpoint` - - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. +- - `include_columns` + - Sequence + - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. -- - `kms_key` - - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. +- - `primary_keys` + - Sequence + - The primary key of the table used to apply changes. -- - `region` - - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. +- - `sequence_by` + - Sequence + - The column names specifying the logical order of events in the source data. Spark Declarative Pipelines uses this sequencing to handle change events that arrive out of order. ::: -### pipelines._name_.clusters.cluster_log_conf.volumes +### pipelines._name_.ingestion_definition.objects.schema.table_configuration.auto_full_refresh_policy **`Type: Map`** -destination needs to be provided, e.g. -`{ "volumes": { "destination": "/Volumes/catalog/schema/volume/cluster_log" } }` +(Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try +to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy +in table configuration will override the above level auto_full_refresh_policy. +For example, +{ +"auto_full_refresh_policy": { +"enabled": true, +"min_interval_hours": 23, +} +} +If unspecified, auto full refresh is disabled. @@ -7293,19 +9420,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` - - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` +- - `enabled` + - Boolean + - (Required, Mutable) Whether to enable auto full refresh or not. + +- - `min_interval_hours` + - Integer + - (Optional, Mutable) Specify the minimum interval in hours between the timestamp at which a table was last full refreshed and the current timestamp for triggering auto full If unspecified and autoFullRefresh is enabled then by default min_interval_hours is 24 hours. ::: -### pipelines._name_.clusters.gcp_attributes +### pipelines._name_.ingestion_definition.objects.table **`Type: Map`** -Attributes related to clusters running on Google Cloud Platform. -If not specified at cluster creation, a set of default values will be used. +Select a specific source table. @@ -7315,42 +9445,42 @@ If not specified at cluster creation, a set of default values will be used. - Type - Description -- - `availability` +- - `destination_catalog` - String - - This field determines whether the instance pool will contain preemptible VMs, on-demand VMs, or preemptible VMs with a fallback to on-demand VMs if the former is unavailable. - -- - `boot_disk_size` - - Integer - - Boot disk size in GB + - Required. Destination catalog to store table. -- - `first_on_demand` - - Integer - - The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. This value should be greater than 0, to make sure the cluster driver node is placed on an on-demand instance. If this value is greater than or equal to the current cluster size, all nodes will be placed on on-demand instances. If this value is less than the current cluster size, `first_on_demand` nodes will be placed on on-demand instances and the remainder will be placed on `availability` instances. Note that this value does not affect cluster size and cannot currently be mutated over the lifetime of a cluster. +- - `destination_schema` + - String + - Required. Destination schema to store table. -- - `google_service_account` +- - `destination_table` - String - - If provided, the cluster will impersonate the google service account when accessing gcloud services (like GCS). The google service account must have previously been added to the Databricks environment by an account administrator. + - Optional. Destination table name. The pipeline fails if a table with that name already exists. If not set, the source table name is used. -- - `local_ssd_count` - - Integer - - If provided, each node (workers and driver) in the cluster will have this number of local SSDs attached. Each local SSD is 375GB in size. Refer to [GCP documentation](https://cloud.google.com/compute/docs/disks/local-ssd#choose_number_local_ssds) for the supported number of local SSDs for each instance type. +- - `source_catalog` + - String + - Source catalog name. Might be optional depending on the type of source. -- - `use_preemptible_executors` - - Boolean - - This field is deprecated +- - `source_schema` + - String + - Schema name in the source database. Might be optional depending on the type of source. -- - `zone_id` +- - `source_table` - String - - Identifier for the availability zone in which the cluster resides. This can be one of the following: - "HA" => High availability, spread nodes across availability zones for a Databricks deployment region [default]. - "AUTO" => Databricks picks an availability zone to schedule the cluster on. - A GCP availability zone => Pick One of the available zones for (machine type + region) from https://cloud.google.com/compute/docs/regions-zones. + - Required. Table name in the source database. + +- - `table_configuration` + - Map + - Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. See [\_](#pipelinesnameingestion_definitionobjectstabletable_configuration). ::: -### pipelines._name_.clusters.init_scripts +### pipelines._name_.ingestion_definition.objects.table.table_configuration -**`Type: Sequence`** +**`Type: Map`** -The configuration for storing init scripts. Any number of destinations can be specified. The scripts are executed sequentially in the order provided. If `cluster_log_conf` is specified, init script logs are sent to `//init_scripts`. +Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. @@ -7360,42 +9490,44 @@ The configuration for storing init scripts. Any number of destinations can be sp - Type - Description -- - `abfss` - - Map - - Contains the Azure Data Lake Storage destination path. See [\_](#pipelinesnameclustersinit_scriptsabfss). - -- - `dbfs` - - Map - - This field is deprecated - -- - `file` +- - `auto_full_refresh_policy` - Map - - destination needs to be provided, e.g. `{ "file": { "destination": "file:/my/local/file.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsfile). + - (Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy in table configuration will override the above level auto_full_refresh_policy. For example, { "auto_full_refresh_policy": { "enabled": true, "min_interval_hours": 23, } } If unspecified, auto full refresh is disabled. See [\_](#pipelinesnameingestion_definitionobjectstabletable_configurationauto_full_refresh_policy). -- - `gcs` - - Map - - destination needs to be provided, e.g. `{ "gcs": { "destination": "gs://my-bucket/file.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsgcs). +- - `exclude_columns` + - Sequence + - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. -- - `s3` - - Map - - destination and either the region or endpoint need to be provided. e.g. `{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` Cluster iam role is used to access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to write data to the s3 destination. See [\_](#pipelinesnameclustersinit_scriptss3). +- - `include_columns` + - Sequence + - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. -- - `volumes` - - Map - - destination needs to be provided. e.g. `{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }`. See [\_](#pipelinesnameclustersinit_scriptsvolumes). +- - `primary_keys` + - Sequence + - The primary key of the table used to apply changes. -- - `workspace` - - Map - - destination needs to be provided, e.g. `{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }`. See [\_](#pipelinesnameclustersinit_scriptsworkspace). +- - `sequence_by` + - Sequence + - The column names specifying the logical order of events in the source data. Spark Declarative Pipelines uses this sequencing to handle change events that arrive out of order. ::: -### pipelines._name_.clusters.init_scripts.abfss +### pipelines._name_.ingestion_definition.objects.table.table_configuration.auto_full_refresh_policy **`Type: Map`** -Contains the Azure Data Lake Storage destination path +(Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try +to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy +in table configuration will override the above level auto_full_refresh_policy. +For example, +{ +"auto_full_refresh_policy": { +"enabled": true, +"min_interval_hours": 23, +} +} +If unspecified, auto full refresh is disabled. @@ -7405,19 +9537,22 @@ Contains the Azure Data Lake Storage destination path - Type - Description -- - `destination` - - String - - abfss destination, e.g. `abfss://@.dfs.core.windows.net/`. +- - `enabled` + - Boolean + - (Required, Mutable) Whether to enable auto full refresh or not. + +- - `min_interval_hours` + - Integer + - (Optional, Mutable) Specify the minimum interval in hours between the timestamp at which a table was last full refreshed and the current timestamp for triggering auto full If unspecified and autoFullRefresh is enabled then by default min_interval_hours is 24 hours. ::: -### pipelines._name_.clusters.init_scripts.file +### pipelines._name_.ingestion_definition.source_configurations -**`Type: Map`** +**`Type: Sequence`** -destination needs to be provided, e.g. -`{ "file": { "destination": "file:/my/local/file.sh" } }` +Top-level source configurations @@ -7427,19 +9562,18 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` - - String - - local file destination, e.g. `file:/my/local/file.sh` +- - `catalog` + - Map + - Catalog-level source configuration parameters. See [\_](#pipelinesnameingestion_definitionsource_configurationscatalog). ::: -### pipelines._name_.clusters.init_scripts.gcs +### pipelines._name_.ingestion_definition.source_configurations.catalog **`Type: Map`** -destination needs to be provided, e.g. -`{ "gcs": { "destination": "gs://my-bucket/file.sh" } }` +Catalog-level source configuration parameters @@ -7449,21 +9583,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` +- - `postgres` + - Map + - Postgres-specific catalog-level configuration parameters. See [\_](#pipelinesnameingestion_definitionsource_configurationscatalogpostgres). + +- - `source_catalog` - String - - GCS destination/URI, e.g. `gs://my-bucket/some-prefix` + - Source catalog name ::: -### pipelines._name_.clusters.init_scripts.s3 +### pipelines._name_.ingestion_definition.source_configurations.catalog.postgres **`Type: Map`** -destination and either the region or endpoint need to be provided. e.g. -`{ \"s3\": { \"destination\": \"s3://cluster_log_bucket/prefix\", \"region\": \"us-west-2\" } }` -Cluster iam role is used to access s3, please make sure the cluster iam role in -`instance_profile_arn` has permission to write data to the s3 destination. +Postgres-specific catalog-level configuration parameters @@ -7473,43 +9608,43 @@ Cluster iam role is used to access s3, please make sure the cluster iam role in - Type - Description -- - `canned_acl` - - String - - (Optional) Set canned access control list for the logs, e.g. `bucket-owner-full-control`. If `canned_cal` is set, please make sure the cluster iam role has `s3:PutObjectAcl` permission on the destination bucket and prefix. The full list of possible canned acl can be found at http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl. Please also note that by default only the object owner gets full controls. If you are using cross account role for writing data, you may want to set `bucket-owner-full-control` to make bucket owner able to read the logs. +- - `slot_config` + - Map + - Optional. The Postgres slot configuration to use for logical replication. See [\_](#pipelinesnameingestion_definitionsource_configurationscatalogpostgresslot_config). -- - `destination` - - String - - S3 destination, e.g. `s3://my-bucket/some-prefix` Note that logs will be delivered using cluster iam role, please make sure you set cluster iam role and the role has write access to the destination. Please also note that you cannot use AWS keys to deliver logs. +::: -- - `enable_encryption` - - Boolean - - (Optional) Flag to enable server side encryption, `false` by default. -- - `encryption_type` - - String - - (Optional) The encryption type, it could be `sse-s3` or `sse-kms`. It will be used only when encryption is enabled and the default type is `sse-s3`. +### pipelines._name_.ingestion_definition.source_configurations.catalog.postgres.slot_config -- - `endpoint` - - String - - S3 endpoint, e.g. `https://s3-us-west-2.amazonaws.com`. Either region or endpoint needs to be set. If both are set, endpoint will be used. +**`Type: Map`** -- - `kms_key` +Optional. The Postgres slot configuration to use for logical replication + + + +:::list-table + +- - Key + - Type + - Description + +- - `publication_name` - String - - (Optional) Kms key which will be used if encryption is enabled and encryption type is set to `sse-kms`. + - The name of the publication to use for the Postgres source -- - `region` +- - `slot_name` - String - - S3 region, e.g. `us-west-2`. Either region or endpoint needs to be set. If both are set, endpoint will be used. + - The name of the logical replication slot to use for the Postgres source ::: -### pipelines._name_.clusters.init_scripts.volumes +### pipelines._name_.ingestion_definition.table_configuration **`Type: Map`** -destination needs to be provided. e.g. -`{ \"volumes\" : { \"destination\" : \"/Volumes/my-init.sh\" } }` +Configuration settings to control the ingestion of tables. These settings are applied to all tables in the pipeline. @@ -7519,19 +9654,44 @@ destination needs to be provided. e.g. - Type - Description -- - `destination` - - String - - UC Volumes destination, e.g. `/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` or `dbfs:/Volumes/catalog/schema/vol1/init-scripts/setup-datadog.sh` +- - `auto_full_refresh_policy` + - Map + - (Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy in table configuration will override the above level auto_full_refresh_policy. For example, { "auto_full_refresh_policy": { "enabled": true, "min_interval_hours": 23, } } If unspecified, auto full refresh is disabled. See [\_](#pipelinesnameingestion_definitiontable_configurationauto_full_refresh_policy). + +- - `exclude_columns` + - Sequence + - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. + +- - `include_columns` + - Sequence + - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. + +- - `primary_keys` + - Sequence + - The primary key of the table used to apply changes. + +- - `sequence_by` + - Sequence + - The column names specifying the logical order of events in the source data. Spark Declarative Pipelines uses this sequencing to handle change events that arrive out of order. ::: -### pipelines._name_.clusters.init_scripts.workspace +### pipelines._name_.ingestion_definition.table_configuration.auto_full_refresh_policy **`Type: Map`** -destination needs to be provided, e.g. -`{ "workspace": { "destination": "/cluster-init-scripts/setup-datadog.sh" } }` +(Optional, Mutable) Policy for auto full refresh, if enabled pipeline will automatically try +to fix issues by doing a full refresh on the table in the retry run. auto_full_refresh_policy +in table configuration will override the above level auto_full_refresh_policy. +For example, +{ +"auto_full_refresh_policy": { +"enabled": true, +"min_interval_hours": 23, +} +} +If unspecified, auto full refresh is disabled. @@ -7541,18 +9701,22 @@ destination needs to be provided, e.g. - Type - Description -- - `destination` - - String - - wsfs destination, e.g. `workspace:/cluster-init-scripts/setup-datadog.sh` +- - `enabled` + - Boolean + - (Required, Mutable) Whether to enable auto full refresh or not. + +- - `min_interval_hours` + - Integer + - (Optional, Mutable) Specify the minimum interval in hours between the timestamp at which a table was last full refreshed and the current timestamp for triggering auto full If unspecified and autoFullRefresh is enabled then by default min_interval_hours is 24 hours. ::: -### pipelines._name_.deployment +### pipelines._name_.libraries -**`Type: Map`** +**`Type: Sequence`** -Deployment type of this pipeline. +Libraries or code needed by this deployment. @@ -7562,22 +9726,30 @@ Deployment type of this pipeline. - Type - Description -- - `kind` - - String - - The deployment method that manages the pipeline. +- - `file` + - Map + - The path to a file that defines a pipeline and is stored in the Databricks Repos. See [\_](#pipelinesnamelibrariesfile). -- - `metadata_file_path` +- - `glob` + - Map + - The unified field to include source codes. Each entry can be a notebook path, a file path, or a folder path that ends `/**`. This field cannot be used together with `notebook` or `file`. See [\_](#pipelinesnamelibrariesglob). + +- - `notebook` + - Map + - The path to a notebook that defines a pipeline and is stored in the Databricks workspace. See [\_](#pipelinesnamelibrariesnotebook). + +- - `whl` - String - - The path to the file containing metadata about the deployment. + - This field is deprecated ::: -### pipelines._name_.environment +### pipelines._name_.libraries.file **`Type: Map`** -Environment specification for this pipeline used to install dependencies. +The path to a file that defines a pipeline and is stored in the Databricks Repos. @@ -7587,18 +9759,20 @@ Environment specification for this pipeline used to install dependencies. - Type - Description -- - `dependencies` - - Sequence - - List of pip dependencies, as supported by the version of pip in this environment. Each dependency is a pip requirement file line https://pip.pypa.io/en/stable/reference/requirements-file-format/ Allowed dependency could be , , (WSFS or Volumes in Databricks), +- - `path` + - String + - The absolute path of the source code. ::: -### pipelines._name_.event_log +### pipelines._name_.libraries.glob **`Type: Map`** -Event log configuration for this pipeline +The unified field to include source codes. +Each entry can be a notebook path, a file path, or a folder path that ends `/**`. +This field cannot be used together with `notebook` or `file`. @@ -7608,26 +9782,18 @@ Event log configuration for this pipeline - Type - Description -- - `catalog` - - String - - The UC catalog the event log is published under. - -- - `name` - - String - - The name the event log is published to in UC. - -- - `schema` +- - `include` - String - - The UC schema the event log is published under. + - The source code to include for pipelines ::: -### pipelines._name_.filters +### pipelines._name_.libraries.notebook **`Type: Map`** -Filters on which Pipeline packages to include in the deployed graph. +The path to a notebook that defines a pipeline and is stored in the Databricks workspace. @@ -7637,22 +9803,18 @@ Filters on which Pipeline packages to include in the deployed graph. - Type - Description -- - `exclude` - - Sequence - - Paths to exclude. - -- - `include` - - Sequence - - Paths to include. +- - `path` + - String + - The absolute path of the source code. ::: -### pipelines._name_.ingestion_definition +### pipelines._name_.lifecycle **`Type: Map`** -The configuration for a managed ingestion pipeline. These settings cannot be used with the 'libraries', 'schema', 'target', or 'catalog' settings. +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. @@ -7662,30 +9824,18 @@ The configuration for a managed ingestion pipeline. These settings cannot be use - Type - Description -- - `connection_name` - - String - - Immutable. The Unity Catalog connection that this ingestion pipeline uses to communicate with the source. This is used with connectors for applications like Salesforce, Workday, and so on. - -- - `ingestion_gateway_id` - - String - - Immutable. Identifier for the gateway that is used by this ingestion pipeline to communicate with the source database. This is used with connectors to databases like SQL Server. - -- - `objects` - - Sequence - - Required. Settings specifying tables to replicate and the destination for the replicated tables. See [\_](#pipelinesnameingestion_definitionobjects). - -- - `table_configuration` - - Map - - Configuration settings to control the ingestion of tables. These settings are applied to all tables in the pipeline. See [\_](#pipelinesnameingestion_definitiontable_configuration). +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### pipelines._name_.ingestion_definition.objects +### pipelines._name_.notifications **`Type: Sequence`** -Required. Settings specifying tables to replicate and the destination for the replicated tables. +List of notification settings for this pipeline. @@ -7695,26 +9845,22 @@ Required. Settings specifying tables to replicate and the destination for the re - Type - Description -- - `report` - - Map - - Select a specific source report. See [\_](#pipelinesnameingestion_definitionobjectsreport). - -- - `schema` - - Map - - Select all tables from a specific source schema. See [\_](#pipelinesnameingestion_definitionobjectsschema). +- - `alerts` + - Sequence + - A list of alerts that trigger the sending of notifications to the configured destinations. The supported alerts are: * `on-update-success`: A pipeline update completes successfully. * `on-update-failure`: Each time a pipeline update fails. * `on-update-fatal-failure`: A pipeline update fails with a non-retryable (fatal) error. * `on-flow-failure`: A single data flow fails. -- - `table` - - Map - - Select a specific source table. See [\_](#pipelinesnameingestion_definitionobjectstable). +- - `email_recipients` + - Sequence + - A list of email addresses notified when a configured alert is triggered. ::: -### pipelines._name_.ingestion_definition.objects.report +### pipelines._name_.permissions -**`Type: Map`** +**`Type: Sequence`** -Select a specific source report. + @@ -7724,34 +9870,32 @@ Select a specific source report. - Type - Description -- - `destination_catalog` +- - `group_name` - String - - Required. Destination catalog to store table. + - -- - `destination_schema` +- - `level` - String - - Required. Destination schema to store table. + - Permission level -- - `destination_table` +- - `service_principal_name` - String - - Required. Destination table name. The pipeline fails if a table with that name already exists. + - -- - `source_url` +- - `user_name` - String - - Required. Report URL in the source system. - -- - `table_configuration` - - Map - - Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object. See [\_](#pipelinesnameingestion_definitionobjectsreporttable_configuration). + - ::: -### pipelines._name_.ingestion_definition.objects.report.table_configuration +### pipelines._name_.run_as **`Type: Map`** -Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object. +Write-only setting, available only in Create/Update calls. Specifies the user or service principal that the pipeline runs as. If not specified, the pipeline runs as the user who created the pipeline. + +Only `user_name` or `service_principal_name` can be specified. If both are specified, an error is thrown. @@ -7761,31 +9905,28 @@ Configuration settings to control the ingestion of tables. These settings overri - Type - Description -- - `exclude_columns` - - Sequence - - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. - -- - `include_columns` - - Sequence - - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. - -- - `primary_keys` - - Sequence - - The primary key of the table used to apply changes. +- - `service_principal_name` + - String + - Application ID of an active service principal. Setting this field requires the `servicePrincipal/user` role. -- - `sequence_by` - - Sequence - - The column names specifying the logical order of events in the source data. Delta Live Tables uses this sequencing to handle change events that arrive out of order. +- - `user_name` + - String + - The email of an active workspace user. Users can only set this field to their own email. ::: -### pipelines._name_.ingestion_definition.objects.schema +## postgres_branches **`Type: Map`** -Select all tables from a specific source schema. + +```yaml +postgres_branches: + : + : +``` :::list-table @@ -7794,34 +9935,54 @@ Select all tables from a specific source schema. - Type - Description -- - `destination_catalog` +- - `branch_id` - String - - Required. Destination catalog to store tables. + - -- - `destination_schema` +- - `expire_time` + - Map + - + +- - `is_protected` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#postgres_branchesnamelifecycle). + +- - `no_expiry` + - Boolean + - + +- - `parent` - String - - Required. Destination schema to store tables in. Tables with the same name as the source tables are created in this destination schema. The pipeline fails If a table with the same name already exists. + - -- - `source_catalog` +- - `source_branch` - String - - The source catalog name. Might be optional depending on the type of source. + - -- - `source_schema` +- - `source_branch_lsn` - String - - Required. Schema name in the source database. + - -- - `table_configuration` +- - `source_branch_time` - Map - - Configuration settings to control the ingestion of tables. These settings are applied to all tables in this schema and override the table_configuration defined in the IngestionPipelineDefinition object. See [\_](#pipelinesnameingestion_definitionobjectsschematable_configuration). + - + +- - `ttl` + - String + - ::: -### pipelines._name_.ingestion_definition.objects.schema.table_configuration +### postgres_branches._name_.lifecycle **`Type: Map`** -Configuration settings to control the ingestion of tables. These settings are applied to all tables in this schema and override the table_configuration defined in the IngestionPipelineDefinition object. + @@ -7831,31 +9992,24 @@ Configuration settings to control the ingestion of tables. These settings are ap - Type - Description -- - `exclude_columns` - - Sequence - - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. - -- - `include_columns` - - Sequence - - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. - -- - `primary_keys` - - Sequence - - The primary key of the table used to apply changes. - -- - `sequence_by` - - Sequence - - The column names specifying the logical order of events in the source data. Delta Live Tables uses this sequencing to handle change events that arrive out of order. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### pipelines._name_.ingestion_definition.objects.table +## postgres_endpoints **`Type: Map`** -Select a specific source table. + +```yaml +postgres_endpoints: + : + : +``` :::list-table @@ -7864,42 +10018,58 @@ Select a specific source table. - Type - Description -- - `destination_catalog` - - String - - Required. Destination catalog to store table. +- - `autoscaling_limit_max_cu` + - Any + - -- - `destination_schema` - - String - - Required. Destination schema to store table. +- - `autoscaling_limit_min_cu` + - Any + - -- - `destination_table` - - String - - Optional. Destination table name. The pipeline fails if a table with that name already exists. If not set, the source table name is used. +- - `disabled` + - Boolean + - -- - `source_catalog` +- - `endpoint_id` - String - - Source catalog name. Might be optional depending on the type of source. + - -- - `source_schema` +- - `endpoint_type` - String - - Schema name in the source database. Might be optional depending on the type of source. + - The compute endpoint type. Either `read_write` or `read_only`. -- - `source_table` +- - `group` + - Map + - See [\_](#postgres_endpointsnamegroup). + +- - `lifecycle` + - Map + - See [\_](#postgres_endpointsnamelifecycle). + +- - `no_suspension` + - Boolean + - + +- - `parent` - String - - Required. Table name in the source database. + - -- - `table_configuration` +- - `settings` - Map - - Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. See [\_](#pipelinesnameingestion_definitionobjectstabletable_configuration). + - A collection of settings for a compute endpoint. See [\_](#postgres_endpointsnamesettings). + +- - `suspend_timeout_duration` + - String + - ::: -### pipelines._name_.ingestion_definition.objects.table.table_configuration +### postgres_endpoints._name_.group **`Type: Map`** -Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. + @@ -7909,30 +10079,26 @@ Configuration settings to control the ingestion of tables. These settings overri - Type - Description -- - `exclude_columns` - - Sequence - - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. - -- - `include_columns` - - Sequence - - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. +- - `enable_readable_secondaries` + - Boolean + - Whether to allow read-only connections to read-write endpoints. Only relevant for read-write endpoints where size.max > 1. -- - `primary_keys` - - Sequence - - The primary key of the table used to apply changes. +- - `max` + - Integer + - The maximum number of computes in the endpoint group. Currently, this must be equal to min. Set to 1 for single compute endpoints, to disable HA. To manually suspend all computes in an endpoint group, set disabled to true on the EndpointSpec. -- - `sequence_by` - - Sequence - - The column names specifying the logical order of events in the source data. Delta Live Tables uses this sequencing to handle change events that arrive out of order. +- - `min` + - Integer + - The minimum number of computes in the endpoint group. Currently, this must be equal to max. This must be greater than or equal to 1. ::: -### pipelines._name_.ingestion_definition.table_configuration +### postgres_endpoints._name_.lifecycle **`Type: Map`** -Configuration settings to control the ingestion of tables. These settings are applied to all tables in the pipeline. + @@ -7942,30 +10108,18 @@ Configuration settings to control the ingestion of tables. These settings are ap - Type - Description -- - `exclude_columns` - - Sequence - - A list of column names to be excluded for the ingestion. When not specified, include_columns fully controls what columns to be ingested. When specified, all other columns including future ones will be automatically included for ingestion. This field in mutually exclusive with `include_columns`. - -- - `include_columns` - - Sequence - - A list of column names to be included for the ingestion. When not specified, all columns except ones in exclude_columns will be included. Future columns will be automatically included. When specified, all other future columns will be automatically excluded from ingestion. This field in mutually exclusive with `exclude_columns`. - -- - `primary_keys` - - Sequence - - The primary key of the table used to apply changes. - -- - `sequence_by` - - Sequence - - The column names specifying the logical order of events in the source data. Delta Live Tables uses this sequencing to handle change events that arrive out of order. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### pipelines._name_.libraries +### postgres_endpoints._name_.settings -**`Type: Sequence`** +**`Type: Map`** -Libraries or code needed by this deployment. +A collection of settings for a compute endpoint. @@ -7975,31 +10129,24 @@ Libraries or code needed by this deployment. - Type - Description -- - `file` - - Map - - The path to a file that defines a pipeline and is stored in the Databricks Repos. See [\_](#pipelinesnamelibrariesfile). - -- - `glob` - - Map - - The unified field to include source codes. Each entry can be a notebook path, a file path, or a folder path that ends `/**`. This field cannot be used together with `notebook` or `file`. See [\_](#pipelinesnamelibrariesglob). - -- - `notebook` - - Map - - The path to a notebook that defines a pipeline and is stored in the Databricks workspace. See [\_](#pipelinesnamelibrariesnotebook). - -- - `whl` - - String - - This field is deprecated - +- - `pg_settings` + - Map + - A raw representation of Postgres settings. + ::: -### pipelines._name_.libraries.file +## postgres_projects **`Type: Map`** -The path to a file that defines a pipeline and is stored in the Databricks Repos. + +```yaml +postgres_projects: + : + : +``` :::list-table @@ -8008,41 +10155,58 @@ The path to a file that defines a pipeline and is stored in the Databricks Repos - Type - Description -- - `path` +- - `budget_policy_id` - String - - The absolute path of the source code. + - -::: +- - `custom_tags` + - Sequence + - See [\_](#postgres_projectsnamecustom_tags). +- - `default_branch` + - String + - -### pipelines._name_.libraries.glob +- - `default_endpoint_settings` + - Map + - A collection of settings for a compute endpoint. See [\_](#postgres_projectsnamedefault_endpoint_settings). -**`Type: Map`** +- - `display_name` + - String + - -The unified field to include source codes. -Each entry can be a notebook path, a file path, or a folder path that ends `/**`. -This field cannot be used together with `notebook` or `file`. +- - `enable_pg_native_login` + - Boolean + - +- - `history_retention_duration` + - String + - +- - `lifecycle` + - Map + - See [\_](#postgres_projectsnamelifecycle). -:::list-table +- - `permissions` + - Sequence + - See [\_](#postgres_projectsnamepermissions). -- - Key - - Type - - Description +- - `pg_version` + - Integer + - -- - `include` +- - `project_id` - String - - The source code to include for pipelines + - ::: -### pipelines._name_.libraries.notebook +### postgres_projects._name_.custom_tags -**`Type: Map`** +**`Type: Sequence`** -The path to a notebook that defines a pipeline and is stored in the Databricks workspace. + @@ -8052,18 +10216,22 @@ The path to a notebook that defines a pipeline and is stored in the Databricks w - Type - Description -- - `path` +- - `key` - String - - The absolute path of the source code. + - The key of the custom tag. + +- - `value` + - String + - The value of the custom tag. ::: -### pipelines._name_.lifecycle +### postgres_projects._name_.default_endpoint_settings **`Type: Map`** -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. +A collection of settings for a compute endpoint. @@ -8073,18 +10241,34 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - Type - Description -- - `prevent_destroy` +- - `autoscaling_limit_max_cu` + - Any + - The maximum number of Compute Units. Minimum value is 0.5. + +- - `autoscaling_limit_min_cu` + - Any + - The minimum number of Compute Units. Minimum value is 0.5. + +- - `no_suspension` - Boolean - - Lifecycle setting to prevent the resource from being destroyed. + - When set to true, explicitly disables automatic suspension (never suspend). Should be set to true when provided. + +- - `pg_settings` + - Map + - A raw representation of Postgres settings. + +- - `suspend_timeout_duration` + - String + - Duration of inactivity after which the compute endpoint is automatically suspended. If specified should be between 60s and 604800s (1 minute to 1 week). ::: -### pipelines._name_.notifications +### postgres_projects._name_.lifecycle -**`Type: Sequence`** +**`Type: Map`** -List of notification settings for this pipeline. + @@ -8094,18 +10278,14 @@ List of notification settings for this pipeline. - Type - Description -- - `alerts` - - Sequence - - A list of alerts that trigger the sending of notifications to the configured destinations. The supported alerts are: * `on-update-success`: A pipeline update completes successfully. * `on-update-failure`: Each time a pipeline update fails. * `on-update-fatal-failure`: A pipeline update fails with a non-retryable (fatal) error. * `on-flow-failure`: A single data flow fails. - -- - `email_recipients` - - Sequence - - A list of email addresses notified when a configured alert is triggered. +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. ::: -### pipelines._name_.permissions +### postgres_projects._name_.permissions **`Type: Sequence`** @@ -8121,19 +10301,19 @@ List of notification settings for this pipeline. - - `group_name` - String - - + - The name of the group that has the permission set in level. - - `level` - String - - + - The allowed permission for user, group, service principal defined for this permission. - - `service_principal_name` - String - - + - The name of the service principal that has the permission set in level. - - `user_name` - String - - + - The name of the user that has the permission set in level. ::: @@ -8468,6 +10648,14 @@ registered_models: - Type - Description +- - `aliases` + - Sequence + - See [\_](#registered_modelsnamealiases). + +- - `browse_only` + - Boolean + - + - - `catalog_name` - String - The name of the catalog where the schema and the registered model reside @@ -8476,6 +10664,18 @@ registered_models: - String - The comment attached to the registered model +- - `created_at` + - Integer + - + +- - `created_by` + - String + - + +- - `full_name` + - String + - + - - `grants` - Sequence - See [\_](#registered_modelsnamegrants). @@ -8484,10 +10684,18 @@ registered_models: - Map - 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. See [\_](#registered_modelsnamelifecycle). +- - `metastore_id` + - String + - + - - `name` - String - The name of the registered model +- - `owner` + - String + - + - - `schema_name` - String - The name of the schema where the registered model resides @@ -8496,6 +10704,14 @@ registered_models: - String - The storage location on the cloud under which model version data files are stored +- - `updated_at` + - Integer + - + +- - `updated_by` + - String + - + ::: @@ -8517,6 +10733,47 @@ resources: principal: account users ``` +### registered_models._name_.aliases + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `alias_name` + - String + - Name of the alias, e.g. 'champion' or 'latest_stable' + +- - `catalog_name` + - String + - + +- - `id` + - String + - + +- - `model_name` + - String + - + +- - `schema_name` + - String + - + +- - `version_num` + - Integer + - Integer version number of the model version to which this alias points. + +::: + + ### registered_models._name_.grants **`Type: Sequence`** @@ -8533,15 +10790,22 @@ resources: - - `principal` - String - - The name of the principal that will be granted privileges + - The principal (user email address or group name). For deleted principals, `principal` is empty while `principal_id` is populated. - - `privileges` - Sequence - - The privileges to grant to the specified entity + - The privileges assigned to the principal. ::: +### registered_models._name_.grants.privileges + +**`Type: Sequence`** + +The privileges assigned to the principal. + + ### registered_models._name_.lifecycle **`Type: Map`** @@ -8567,7 +10831,7 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co **`Type: Map`** -The schema resource type allows you to define Unity Catalog [schemas](/api/workspace/schemas/create) for tables and other assets in your workflows and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations: +The schema resource type allows you to define Unity Catalog [schemas](/api/workspace/schemas/create) for tables and other assets in your jobs and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations: - The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema. - Only fields supported by the corresponding [Schemas object create API](/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](/api/workspace/schemas/update). @@ -8674,11 +10938,11 @@ resources: - - `principal` - String - - + - The principal (user email address or group name). For deleted principals, `principal` is empty while `principal_id` is populated. - - `privileges` - Sequence - - + - The privileges assigned to the principal. ::: @@ -8687,7 +10951,7 @@ resources: **`Type: Sequence`** - +The privileges assigned to the principal. ### schemas._name_.lifecycle @@ -8836,7 +11100,7 @@ The permissions to apply to the secret scope. Permissions are managed via secret **`Type: Map`** -The SQL warehouse definitions for the bundle, where each key is the name of the warehouse. See [\_](/dev-tools/bundles/resources.md#sql_warehouses). +Creates a new SQL warehouse. ```yaml sql_warehouses: @@ -8853,7 +11117,7 @@ sql_warehouses: - - `auto_stop_mins` - Integer - - The amount of time in minutes that a SQL warehouse must be idle (i.e., no RUNNING queries) before it is automatically stopped. Supported values: - Must be >= 0 mins for serverless warehouses - Must be == 0 or >= 10 mins for non-serverless warehouses - 0 indicates no autostop. Defaults to 120 mins + - The amount of time in minutes that a SQL warehouse must be idle (i.e., no RUNNING queries) before it is automatically stopped. Supported values: - Must be == 0 or >= 10 mins - 0 indicates no autostop. Defaults to 120 mins - - `channel` - Map @@ -8861,7 +11125,7 @@ sql_warehouses: - - `cluster_size` - String - - Size of the clusters allocated for this warehouse. Increasing the size of a spark cluster allows you to run larger queries on it. If you want to increase the number of concurrent queries, please tune max_num_clusters. Supported values: - 2X-Small - X-Small - Small - Medium - Large - X-Large - 2X-Large - 3X-Large - 4X-Large + - Size of the clusters allocated for this warehouse. Increasing the size of a spark cluster allows you to run larger queries on it. If you want to increase the number of concurrent queries, please tune max_num_clusters. Supported values: - 2X-Small - X-Small - Small - Medium - Large - X-Large - 2X-Large - 3X-Large - 4X-Large - 5X-Large - - `creator_name` - String @@ -8885,15 +11149,15 @@ sql_warehouses: - - `max_num_clusters` - Integer - - Maximum number of clusters that the autoscaler will create to handle concurrent queries. Supported values: - Must be >= min_num_clusters - Must be <= 30. Defaults to min_clusters if unset. + - Maximum number of clusters that the autoscaler will create to handle concurrent queries. Supported values: - Must be >= min_num_clusters - Must be <= 40. Defaults to min_clusters if unset. - - `min_num_clusters` - Integer - - Minimum number of available clusters that will be maintained for this SQL warehouse. Increasing this will ensure that a larger number of clusters are always running and therefore may reduce the cold start time for new queries. This is similar to reserved vs. revocable cores in a resource manager. Supported values: - Must be > 0 - Must be <= min(max_num_clusters, 30) Defaults to 1 + - Minimum number of available clusters that will be maintained for this SQL warehouse. Increasing this will ensure that a larger number of clusters are always running and therefore may reduce the cold start time for new queries. This is similar to reserved vs. revocable cores in a resource manager. Supported values: - Must be > 0 - Must be <= min(max_num_clusters, 30) Defaults to 1 - - `name` - String - - Logical name for the cluster. Supported values: - Must be unique within an org. - Must be less than 100 characters. + - Logical name for the cluster. Supported values: - Must be unique within an org. - Must be less than 100 characters. - - `permissions` - Sequence @@ -8901,15 +11165,15 @@ sql_warehouses: - - `spot_instance_policy` - String - - Configurations whether the warehouse should use spot instances. + - EndpointSpotInstancePolicy configures whether the endpoint should use spot instances. The breakdown of how the EndpointSpotInstancePolicy converts to per cloud configurations is: +-------+--------------------------------------+--------------------------------+ | Cloud | COST_OPTIMIZED | RELIABILITY_OPTIMIZED | +-------+--------------------------------------+--------------------------------+ | AWS | On Demand Driver with Spot Executors | On Demand Driver and Executors | | AZURE | On Demand Driver and Executors | On Demand Driver and Executors | +-------+--------------------------------------+--------------------------------+ While including "spot" in the enum name may limit the the future extensibility of this field because it limits this enum to denoting "spot or not", this is the field that PM recommends after discussion with customers per SC-48783. - - `tags` - Map - - A set of key-value pairs that will be tagged on all resources (e.g., AWS instances and EBS volumes) associated with this SQL warehouse. Supported values: - Number of tags < 45. See [\_](#sql_warehousesnametags). + - A set of key-value pairs that will be tagged on all resources (e.g., AWS instances and EBS volumes) associated with this SQL warehouse. Supported values: - Number of tags < 45. See [\_](#sql_warehousesnametags). - - `warehouse_type` - String - - Warehouse type: `PRO` or `CLASSIC`. If you want to use serverless compute, you must set to `PRO` and also set the field `enable_serverless_compute` to `true`. + - ::: @@ -8980,7 +11244,7 @@ Lifecycle is a struct that contains the lifecycle settings for a resource. It co - - `level` - String - - + - Permission level - - `service_principal_name` - String @@ -9001,7 +11265,7 @@ A set of key-value pairs that will be tagged on all resources (e.g., AWS instanc with this SQL warehouse. Supported values: - - Number of tags < 45. +- Number of tags < 45. @@ -9047,7 +11311,7 @@ Supported values: **`Type: Map`** -Next field marker: 14 + ```yaml synced_database_tables: @@ -9068,7 +11332,7 @@ synced_database_tables: - - `lifecycle` - Map - - 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. See [\_](#synced_database_tablesnamelifecycle). + - See [\_](#synced_database_tablesnamelifecycle). - - `logical_database_name` - String @@ -9089,7 +11353,7 @@ synced_database_tables: **`Type: Map`** -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. + @@ -9170,6 +11434,10 @@ only requires read permissions. - Type - Description +- - `budget_policy_id` + - String + - Budget policy to set on the newly created pipeline. + - - `storage_catalog` - String - This field needs to be specified if the destination catalog is a managed postgres catalog. UC catalog for the pipeline to store intermediate files (checkpoints, event logs etc). This needs to be a standard catalog where the user has permissions to create Delta tables. @@ -9181,6 +11449,106 @@ only requires read permissions. ::: +## vector_search_endpoints + +**`Type: Map`** + + + +```yaml +vector_search_endpoints: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `budget_policy_id` + - String + - + +- - `endpoint_type` + - String + - Type of endpoint. + +- - `lifecycle` + - Map + - See [\_](#vector_search_endpointsnamelifecycle). + +- - `min_qps` + - Integer + - + +- - `name` + - String + - + +- - `permissions` + - Sequence + - See [\_](#vector_search_endpointsnamepermissions). + +::: + + +### vector_search_endpoints._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### vector_search_endpoints._name_.permissions + +**`Type: Sequence`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `group_name` + - String + - The name of the group that has the permission set in level. + +- - `level` + - String + - The allowed permission for user, group, service principal defined for this permission. + +- - `service_principal_name` + - String + - The name of the service principal that has the permission set in level. + +- - `user_name` + - String + - The name of the user that has the permission set in level. + +::: + + ## volumes **`Type: Map`** @@ -9234,7 +11602,7 @@ volumes: - - `volume_type` - String - - The type of the volume. An external volume is located in the specified external location. A managed volume is located in the default location which is specified by the parent schema, or the parent catalog, or the Metastore. [Learn more](https://docs.databricks.com/aws/en/volumes/managed-vs-external) + - ::: @@ -9270,11 +11638,11 @@ For an example bundle that runs a job that writes to a file in Unity Catalog vol - - `principal` - String - - + - The principal (user email address or group name). For deleted principals, `principal` is empty while `principal_id` is populated. - - `privileges` - Sequence - - + - The privileges assigned to the principal. ::: @@ -9283,7 +11651,7 @@ For an example bundle that runs a job that writes to a file in Unity Catalog vol **`Type: Sequence`** - +The privileges assigned to the principal. ### volumes._name_.lifecycle diff --git a/bundle/env/http_timeout.go b/bundle/env/http_timeout.go new file mode 100644 index 00000000000..676de3bd722 --- /dev/null +++ b/bundle/env/http_timeout.go @@ -0,0 +1,13 @@ +package env + +import "context" + +// HTTPTimeoutSecondsVariable names the environment variable that overrides the HTTP timeout for bundle operations. +const HTTPTimeoutSecondsVariable = "DATABRICKS_BUNDLE_HTTP_TIMEOUT_SECONDS" + +// HTTPTimeoutSeconds returns the HTTP timeout override for bundle operations. +func HTTPTimeoutSeconds(ctx context.Context) (string, bool) { + return get(ctx, []string{ + HTTPTimeoutSecondsVariable, + }) +} diff --git a/bundle/fingerprint.go b/bundle/fingerprint.go index 526547b0ab5..355eccf2960 100644 --- a/bundle/fingerprint.go +++ b/bundle/fingerprint.go @@ -16,17 +16,17 @@ func (f *UserFingerprint) IsEmpty() bool { func (b *Bundle) GetUserFingerprint(ctx context.Context) UserFingerprint { return UserFingerprint{ - Host: b.WorkspaceClient().Config.Host, - AuthHeader: b.getAuthorizationHeader(), + Host: b.WorkspaceClient(ctx).Config.Host, + AuthHeader: b.getAuthorizationHeader(ctx), } } // getAuthorizationHeader extracts the Authorization header from the workspace client configuration. // If it fails to authenticate, it returns an empty string. -func (b *Bundle) getAuthorizationHeader() string { +func (b *Bundle) getAuthorizationHeader(ctx context.Context) string { // Create a dummy request to extract the Authorization header req := &http.Request{Header: http.Header{}} - if err := b.WorkspaceClient().Config.Authenticate(req); err != nil { + if err := b.WorkspaceClient(ctx).Config.Authenticate(req); err != nil { return "" } diff --git a/bundle/generate/downloader.go b/bundle/generate/downloader.go index 30b91c05399..4376dd4ac5e 100644 --- a/bundle/generate/downloader.go +++ b/bundle/generate/downloader.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/notebook" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/jobs" @@ -55,7 +56,7 @@ func (n *Downloader) MarkPipelineLibraryForDownload(ctx context.Context, lib *pi } func (n *Downloader) markFileForDownload(ctx context.Context, filePath *string) error { - _, err := n.w.Workspace.GetStatusByPath(ctx, *filePath) + _, err := n.w.Workspace.GetStatusByPath(ctx, *filePath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { return err } @@ -73,12 +74,12 @@ func (n *Downloader) markFileForDownload(ctx context.Context, filePath *string) return err } - *filePath = rel + *filePath = filepath.ToSlash(rel) return nil } func (n *Downloader) MarkDirectoryForDownload(ctx context.Context, dirPath *string) error { - _, err := n.w.Workspace.GetStatusByPath(ctx, *dirPath) + _, err := n.w.Workspace.GetStatusByPath(ctx, *dirPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { return err } @@ -109,7 +110,7 @@ func (n *Downloader) MarkDirectoryForDownload(ctx context.Context, dirPath *stri return err } - *dirPath = rel + *dirPath = filepath.ToSlash(rel) return nil } @@ -118,7 +119,7 @@ func (n *Downloader) MarkDirectoryForDownload(ctx context.Context, dirPath *stri func (n *Downloader) recursiveListWithExclusions(ctx context.Context, dirPath string) ([]workspace.ObjectInfo, error) { var result []workspace.ObjectInfo - objects, err := n.w.Workspace.ListAll(ctx, workspace.ListWorkspaceRequest{ + objects, err := n.w.Workspace.ListAll(ctx, workspace.ListWorkspaceRequest{ //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. Path: dirPath, }) if err != nil { @@ -203,10 +204,75 @@ func (n *Downloader) markNotebookForDownload(ctx context.Context, notebookPath * return err } - *notebookPath = rel + *notebookPath = filepath.ToSlash(rel) return nil } +func (n *Downloader) MarkTasksForDownload(ctx context.Context, tasks []jobs.Task) error { + var paths []string + for _, task := range tasks { + if task.NotebookTask != nil { + paths = append(paths, task.NotebookTask.NotebookPath) + } + } + if len(paths) > 0 { + n.basePath = commonDirPrefix(paths) + } + for i := range tasks { + if err := n.MarkTaskForDownload(ctx, &tasks[i]); err != nil { + return err + } + } + return nil +} + +func (n *Downloader) CleanupOldFiles(ctx context.Context) { + for targetPath := range n.files { + rel, err := filepath.Rel(n.sourceDir, targetPath) + if err != nil { + continue + } + if filepath.Base(rel) == rel { + continue + } + oldPath := filepath.Join(n.sourceDir, filepath.Base(rel)) + if _, isNewFile := n.files[oldPath]; isNewFile { + continue + } + if err := os.Remove(oldPath); err == nil { + log.Infof(ctx, "Removed previously generated file %s", filepath.ToSlash(oldPath)) + } + } +} + +// commonDirPrefix returns the longest common directory-aligned prefix of the given paths. +func commonDirPrefix(paths []string) string { + if len(paths) == 0 { + return "" + } + if len(paths) == 1 { + return path.Dir(paths[0]) + } + + prefix := paths[0] + for _, p := range paths[1:] { + for !strings.HasPrefix(p, prefix) { + prefix = prefix[:len(prefix)-1] + if prefix == "" { + return "" + } + } + } + + // Truncate to last '/' to ensure directory alignment. + if i := strings.LastIndex(prefix, "/"); i >= 0 { + prefix = prefix[:i] + } else { + prefix = "" + } + return prefix +} + func (n *Downloader) relativePath(fullPath string) string { basePath := path.Dir(fullPath) if n.basePath != "" { diff --git a/bundle/generate/downloader_test.go b/bundle/generate/downloader_test.go index d0877ac3d29..9363ce98d3c 100644 --- a/bundle/generate/downloader_test.go +++ b/bundle/generate/downloader_test.go @@ -1,10 +1,16 @@ package generate import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" "path/filepath" "testing" + "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/experimental/mocks" + "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -28,7 +34,7 @@ func TestDownloader_MarkFileReturnsRelativePath(t *testing.T) { }, nil) err = downloader.markFileForDownload(ctx, &f1) require.NoError(t, err) - assert.Equal(t, filepath.FromSlash("../source/c"), f1) + assert.Equal(t, "../source/c", f1) // Test that the previous path doesn't influence the next path. f2 := "/a/b/c/d" @@ -37,7 +43,7 @@ func TestDownloader_MarkFileReturnsRelativePath(t *testing.T) { }, nil) err = downloader.markFileForDownload(ctx, &f2) require.NoError(t, err) - assert.Equal(t, filepath.FromSlash("../source/d"), f2) + assert.Equal(t, "../source/d", f2) } func TestDownloader_DoesNotRecurseIntoNodeModules(t *testing.T) { @@ -93,3 +99,186 @@ func TestDownloader_DoesNotRecurseIntoNodeModules(t *testing.T) { assert.Contains(t, downloader.files, filepath.Join(sourceDir, "app.py")) assert.Contains(t, downloader.files, filepath.Join(sourceDir, "src/index.js")) } + +func TestCommonDirPrefix(t *testing.T) { + tests := []struct { + name string + paths []string + want string + }{ + { + name: "empty", + paths: nil, + want: "", + }, + { + name: "single path", + paths: []string{"/a/b/c"}, + want: "/a/b", + }, + { + name: "shared parent", + paths: []string{"/a/b/c", "/a/b/d"}, + want: "/a/b", + }, + { + name: "root divergence", + paths: []string{"/x/y", "/z/w"}, + want: "", + }, + { + name: "partial dir name safety", + paths: []string{"/a/bc/d", "/a/bd/e"}, + want: "/a", + }, + { + name: "nested shared prefix", + paths: []string{"/Users/user/project/etl/extract", "/Users/user/project/reporting/dashboard"}, + want: "/Users/user/project", + }, + { + name: "identical paths", + paths: []string{"/a/b/c", "/a/b/c"}, + want: "/a/b", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, commonDirPrefix(tt.paths)) + }) + } +} + +func newTestWorkspaceClient(t *testing.T, handler http.HandlerFunc) *databricks.WorkspaceClient { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/.well-known/databricks-config" { + http.NotFound(w, r) + return + } + + handler(w, r) + })) + t.Cleanup(server.Close) + + w, err := databricks.NewWorkspaceClient(&databricks.Config{ + Host: server.URL, + Token: "test-token", + }) + require.NoError(t, err) + return w +} + +func notebookStatusHandler(t *testing.T) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/2.0/workspace/get-status" { + t.Fatalf("unexpected request path: %s", r.URL.Path) + } + resp := workspaceStatus{ + Language: workspace.LanguagePython, + ObjectType: workspace.ObjectTypeNotebook, + ExportFormat: workspace.ExportFormatSource, + } + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(resp) + if err != nil { + t.Fatal(err) + } + } +} + +func TestDownloader_MarkTasksForDownload_PreservesStructure(t *testing.T) { + w := newTestWorkspaceClient(t, notebookStatusHandler(t)) + + dir := "base/dir" + sourceDir := filepath.Join(dir, "source") + configDir := filepath.Join(dir, "config") + downloader := NewDownloader(w, sourceDir, configDir) + + tasks := []jobs.Task{ + { + TaskKey: "extract_task", + NotebookTask: &jobs.NotebookTask{ + NotebookPath: "/Users/user/project/etl/extract", + }, + }, + { + TaskKey: "dashboard_task", + NotebookTask: &jobs.NotebookTask{ + NotebookPath: "/Users/user/project/reporting/dashboard", + }, + }, + } + + err := downloader.MarkTasksForDownload(t.Context(), tasks) + require.NoError(t, err) + + assert.Equal(t, "../source/etl/extract.py", tasks[0].NotebookTask.NotebookPath) + assert.Equal(t, "../source/reporting/dashboard.py", tasks[1].NotebookTask.NotebookPath) + assert.Len(t, downloader.files, 2) +} + +func TestDownloader_MarkTasksForDownload_SingleNotebook(t *testing.T) { + ctx := t.Context() + w := newTestWorkspaceClient(t, notebookStatusHandler(t)) + + dir := "base/dir" + sourceDir := filepath.Join(dir, "source") + configDir := filepath.Join(dir, "config") + downloader := NewDownloader(w, sourceDir, configDir) + + tasks := []jobs.Task{ + { + TaskKey: "task1", + NotebookTask: &jobs.NotebookTask{ + NotebookPath: "/Users/user/project/notebook", + }, + }, + } + + err := downloader.MarkTasksForDownload(ctx, tasks) + require.NoError(t, err) + + // Single notebook: basePath = path.Dir => same as old behavior. + assert.Equal(t, "../source/notebook.py", tasks[0].NotebookTask.NotebookPath) + assert.Len(t, downloader.files, 1) +} + +func TestDownloader_MarkTasksForDownload_NoNotebooks(t *testing.T) { + ctx := t.Context() + w := newTestWorkspaceClient(t, func(w http.ResponseWriter, r *http.Request) { + t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path) + }) + + downloader := NewDownloader(w, "source", "config") + + tasks := []jobs.Task{ + {TaskKey: "spark_task"}, + {TaskKey: "python_wheel_task"}, + } + + err := downloader.MarkTasksForDownload(ctx, tasks) + require.NoError(t, err) + assert.Empty(t, downloader.files) +} + +func TestDownloader_CleanupOldFiles(t *testing.T) { + ctx := t.Context() + sourceDir := t.TempDir() + + oldExtract := filepath.Join(sourceDir, "extract.py") + oldDashboard := filepath.Join(sourceDir, "dashboard.py") + unrelated := filepath.Join(sourceDir, "utils.py") + require.NoError(t, os.WriteFile(oldExtract, []byte("old"), 0o644)) + require.NoError(t, os.WriteFile(oldDashboard, []byte("old"), 0o644)) + require.NoError(t, os.WriteFile(unrelated, []byte("keep"), 0o644)) + + downloader := NewDownloader(nil, sourceDir, "config") + downloader.files[filepath.Join(sourceDir, "etl", "extract.py")] = exportFile{} + downloader.files[filepath.Join(sourceDir, "reporting", "dashboard.py")] = exportFile{} + + downloader.CleanupOldFiles(ctx) + + assert.NoFileExists(t, oldExtract) + assert.NoFileExists(t, oldDashboard) + assert.FileExists(t, unrelated) +} diff --git a/bundle/internal/schema/annotations.go b/bundle/internal/schema/annotations.go index 689d75ae2e1..26b9dcfc0de 100644 --- a/bundle/internal/schema/annotations.go +++ b/bundle/internal/schema/annotations.go @@ -3,6 +3,7 @@ package main import ( "bytes" "fmt" + "maps" "os" "reflect" "regexp" @@ -183,12 +184,7 @@ func saveYamlWithStyle(outputPath string, annotations annotation.File) error { } func getAlphabeticalOrder[T any](mapping map[string]T) *yamlsaver.Order { - var order []string - for k := range mapping { - order = append(order, k) - } - slices.Sort(order) - return yamlsaver.NewOrder(order) + return yamlsaver.NewOrder(slices.Sorted(maps.Keys(mapping))) } func convertLinksToAbsoluteUrl(s string) string { @@ -210,8 +206,8 @@ func convertLinksToAbsoluteUrl(s string) string { link := matches[2] var text, absoluteURL string - if strings.HasPrefix(link, "#") { - text = strings.TrimPrefix(link, "#") + if after, ok := strings.CutPrefix(link, "#"); ok { + text = after absoluteURL = fmt.Sprintf("%s%s%s", base, referencePage, link) // Handle relative paths like /dev-tools/bundles/resources.html#dashboard diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 459d2c19f6f..2f28ca27596 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -249,6 +249,9 @@ github.com/databricks/cli/bundle/config.Resources: "synced_database_tables": "description": |- PLACEHOLDER + "vector_search_endpoints": + "description": |- + PLACEHOLDER "volumes": "description": |- The volume definitions for the bundle, where each key is the name of the volume. @@ -327,9 +330,9 @@ github.com/databricks/cli/bundle/config.Root: ``` "run_as": "description": |- - The identity to use when running Declarative Automation Bundles workflows. + The identity to use when running Declarative Automation Bundles resources. "markdown_description": |- - The identity to use when running Declarative Automation Bundles workflows. See [\_](/dev-tools/bundles/run-as.md). + The identity to use when running Declarative Automation Bundles resources. See [\_](/dev-tools/bundles/run-as.md). "scripts": "description": |- PLACEHOLDER @@ -420,7 +423,7 @@ github.com/databricks/cli/bundle/config.Workspace: The Databricks account ID. "artifact_path": "description": |- - The artifact path to use within the workspace for both deployments and workflow runs + The artifact path to use within the workspace for both deployments and job runs "auth_type": "description": |- The authentication type. @@ -447,10 +450,10 @@ github.com/databricks/cli/bundle/config.Workspace: The client ID for the workspace "experimental_is_unified_host": "description": |- - Experimental feature flag to indicate if the host is a unified host + Deprecated: no-op. Unified hosts are now detected automatically from /.well-known/databricks-config. Retained for schema compatibility with existing databricks.yml files. "file_path": "description": |- - The file path to use within the workspace for both deployments and workflow runs + The file path to use within the workspace for both deployments and job runs "google_service_account": "description": |- The Google service account name @@ -849,6 +852,9 @@ github.com/databricks/cli/bundle/config/resources.PostgresProject: "custom_tags": "description": |- PLACEHOLDER + "default_branch": + "description": |- + PLACEHOLDER "default_endpoint_settings": "description": |- PLACEHOLDER @@ -955,6 +961,28 @@ github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable: "unity_catalog_provisioning_state": "description": |- PLACEHOLDER +github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint: + "budget_policy_id": + "description": |- + PLACEHOLDER + "endpoint_type": + "description": |- + PLACEHOLDER + "lifecycle": + "description": |- + PLACEHOLDER + "min_qps": + "description": |- + PLACEHOLDER + "name": + "description": |- + PLACEHOLDER + "permissions": + "description": |- + PLACEHOLDER + "usage_policy_id": + "description": |- + PLACEHOLDER github.com/databricks/cli/bundle/config/variable.Lookup: "alert": "description": |- diff --git a/bundle/internal/schema/annotations_openapi.yml b/bundle/internal/schema/annotations_openapi.yml index c196c7799a8..84a4b01b545 100644 --- a/bundle/internal/schema/annotations_openapi.yml +++ b/bundle/internal/schema/annotations_openapi.yml @@ -180,6 +180,11 @@ github.com/databricks/cli/bundle/config/resources.Catalog: "connection_name": "description": |- The name of the connection to an external data source. + "managed_encryption_settings": + "description": |- + Control CMK encryption for managed catalog data + "x-databricks-preview": |- + PRIVATE "name": "description": |- Name of catalog. @@ -566,6 +571,14 @@ github.com/databricks/cli/bundle/config/resources.ExternalLocation: The effective value of `enable_file_events` after applying server-side defaults. "x-databricks-field-behaviors_output_only": |- true + "effective_file_event_queue": + "description": |- + The effective file event queue configuration after applying server-side defaults. + Always populated when a queue is provisioned, regardless of whether the user explicitly + set `enable_file_events`. Use this field instead of `file_event_queue` for reading + the actual queue state. + "x-databricks-field-behaviors_output_only": |- + true "enable_file_events": "description": |- Whether to enable file events on this external location. Default to `true`. Set to `false` to disable file events. @@ -1116,6 +1129,25 @@ github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable: may be in "PROVISIONING" as it runs asynchronously). "x-databricks-field-behaviors_output_only": |- true +github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint: + "budget_policy_id": + "description": |- + The budget policy id to be applied + "endpoint_type": + "description": |- + Type of endpoint + "min_qps": + "description": |- + Min QPS for the endpoint. Mutually exclusive with num_replicas. + The actual replica count is calculated at index creation/sync time based on this value. + "name": + "description": |- + Name of the vector search endpoint + "usage_policy_id": + "description": |- + The usage policy id to be applied once we've migrated to usage policies + "x-databricks-preview": |- + PRIVATE github.com/databricks/cli/bundle/config/resources.Volume: "catalog_name": "description": |- @@ -1240,14 +1272,19 @@ github.com/databricks/databricks-sdk-go/service/apps.AppResource: "name": "description": |- Name of the App Resource. - "postgres": - "x-databricks-preview": |- - PRIVATE + "postgres": {} "secret": {} "serving_endpoint": {} "sql_warehouse": {} "uc_securable": {} -github.com/databricks/databricks-sdk-go/service/apps.AppResourceApp: {} +github.com/databricks/databricks-sdk-go/service/apps.AppResourceApp: + "name": {} + "permission": {} +github.com/databricks/databricks-sdk-go/service/apps.AppResourceAppAppPermission: + "_": + "enum": + - |- + CAN_USE github.com/databricks/databricks-sdk-go/service/apps.AppResourceDatabase: "database_name": {} "instance_name": {} @@ -1303,15 +1340,9 @@ github.com/databricks/databricks-sdk-go/service/apps.AppResourceJobJobPermission - |- CAN_VIEW github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgres: - "branch": - "x-databricks-preview": |- - PRIVATE - "database": - "x-databricks-preview": |- - PRIVATE - "permission": - "x-databricks-preview": |- - PRIVATE + "branch": {} + "database": {} + "permission": {} github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgresPostgresPermission: "_": "enum": @@ -1555,6 +1586,10 @@ github.com/databricks/databricks-sdk-go/service/catalog.AwsSqsQueue: "description": |- The AQS queue url in the format https://sqs.{region}.amazonaws.com/{account id}/{queue name}. Only required for provided_sqs. +github.com/databricks/databricks-sdk-go/service/catalog.AzureEncryptionSettings: + "azure_cmk_access_connector_id": {} + "azure_cmk_managed_identity_id": {} + "azure_tenant_id": {} github.com/databricks/databricks-sdk-go/service/catalog.AzureQueueStorage: "managed_resource_id": "description": |- @@ -1582,6 +1617,20 @@ github.com/databricks/databricks-sdk-go/service/catalog.EncryptionDetails: "sse_encryption_details": "description": |- Server-Side Encryption properties for clients communicating with AWS s3. +github.com/databricks/databricks-sdk-go/service/catalog.EncryptionSettings: + "_": + "description": |- + Encryption Settings are used to carry metadata for securable encryption at rest. + Currently used for catalogs, we can use the information supplied here to interact with a CMK. + "azure_encryption_settings": + "description": |- + optional Azure settings - only required if an Azure CMK is used. + "azure_key_vault_key_id": + "description": |- + the AKV URL in Azure, null otherwise. + "customer_managed_key_id": + "description": |- + the CMK uuid in AWS and GCP, null otherwise. github.com/databricks/databricks-sdk-go/service/catalog.FileEventQueue: "managed_aqs": {} "managed_pubsub": {} @@ -2349,8 +2398,12 @@ github.com/databricks/databricks-sdk-go/service/compute.Environment: In this minimal environment spec, only pip and java dependencies are supported. "base_environment": "description": |- - The `base_environment` key refers to an `env.yaml` file that specifies an environment version and a collection of dependencies required for the environment setup. - This `env.yaml` file may itself include a `base_environment` reference pointing to another `env_1.yaml` file. However, when used as a base environment, `env_1.yaml` (or further nested references) will not be processed or included in the final environment, meaning that the resolution of `base_environment` references is not recursive. + The base environment this environment is built on top of. A base environment defines the environment version and a + list of dependencies for serverless compute. The value can be a file path to a custom `env.yaml` file + (e.g., `/Workspace/path/to/env.yaml`). Support for a Databricks-provided base environment ID + (e.g., `workspace-base-environments/databricks_ai_v4`) and workspace base environment ID + (e.g., `workspace-base-environments/dbe_b849b66e-b31a-4cb5-b161-1f2b10877fb7`) is in Beta. + Either `environment_version` or `base_environment` can be provided. For more information, see "client": "description": |- Use `environment_version` instead. @@ -4018,7 +4071,8 @@ github.com/databricks/databricks-sdk-go/service/jobs.TableUpdateTriggerConfigura github.com/databricks/databricks-sdk-go/service/jobs.Task: "alert_task": "description": |- - New alert v2 task + The task evaluates a Databricks alert and sends notifications to subscribers + when the `alert_task` field is present. "clean_rooms_notebook_task": "description": |- The task runs a [clean rooms](https://docs.databricks.com/clean-rooms/index.html) notebook @@ -4310,6 +4364,28 @@ github.com/databricks/databricks-sdk-go/service/pipelines.ConnectionParameters: For Oracle databases, this maps to a service name. "x-databricks-preview": |- PRIVATE +github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions: + "_": + "description": |- + Wrapper message for source-specific options to support multiple connector types + "gdrive_options": + "x-databricks-preview": |- + PRIVATE + "google_ads_options": + "description": |- + Google Ads specific options for ingestion (object-level). + When set, these values override the corresponding fields in GoogleAdsConfig + (source_configurations). + "x-databricks-preview": |- + PRIVATE + "sharepoint_options": + "x-databricks-preview": |- + PRIVATE + "tiktok_ads_options": + "description": |- + TikTok Ads specific options for ingestion + "x-databricks-preview": |- + PRIVATE github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorType: "_": "description": |- @@ -4382,6 +4458,82 @@ github.com/databricks/databricks-sdk-go/service/pipelines.EventLogSpec: "schema": "description": |- The UC schema the event log is published under. +github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter: + "modified_after": + "description": |- + Include files with modification times occurring after the specified time. + Timestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00) + Based on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters + "modified_before": + "description": |- + Include files with modification times occurring before the specified time. + Timestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00) + Based on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters + "path_filter": + "description": |- + Include files with file names matching the pattern + Based on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#path-glob-filter +github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions: + "corrupt_record_column": {} + "file_filters": + "description": |- + Generic options + "format": + "description": |- + required for TableSpec + "format_options": + "description": |- + Format-specific options + Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/options#file-format-options + "ignore_corrupt_files": {} + "infer_column_types": {} + "reader_case_sensitive": + "description": |- + Column name case sensitivity + https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#change-case-sensitive-behavior + "rescued_data_column": {} + "schema_evolution_mode": + "description": |- + Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work + "schema_hints": + "description": |- + Override inferred schema of specific columns + Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#override-schema-inference-with-schema-hints + "single_variant_column": {} +github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFileFormat: + "_": + "enum": + - |- + BINARYFILE + - |- + JSON + - |- + CSV + - |- + XML + - |- + EXCEL + - |- + PARQUET + - |- + AVRO + - |- + ORC +github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode: + "_": + "description": |- + Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work + "enum": + - |- + ADD_NEW_COLUMNS_WITH_TYPE_WIDENING + - |- + ADD_NEW_COLUMNS + - |- + RESCUE + - |- + FAIL_ON_NEW_COLUMNS + - |- + NONE github.com/databricks/databricks-sdk-go/service/pipelines.FileLibrary: "path": "description": |- @@ -4393,6 +4545,41 @@ github.com/databricks/databricks-sdk-go/service/pipelines.Filters: "include": "description": |- Paths to include. +github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsOptions: + "_": + "description": |- + Google Ads specific options for ingestion (object-level). + When set, these values override the corresponding fields in GoogleAdsConfig + (source_configurations). + "lookback_window_days": + "description": |- + (Optional) Number of days to look back for report tables to capture late-arriving data. + If not specified, defaults to 30 days. + "manager_account_id": + "description": |- + (Optional at this level) Manager Account ID (also called MCC Account ID) used to list + and access customer accounts under this manager account. + Overrides GoogleAdsConfig.manager_account_id from source_configurations when set. + "sync_start_date": + "description": |- + (Optional) Start date for the initial sync of report tables in YYYY-MM-DD format. + This determines the earliest date from which to sync historical data. + If not specified, defaults to 2 years of historical data. +github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions: + "entity_type": {} + "file_ingestion_options": {} + "url": + "description": |- + Google Drive URL. +github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoogleDriveEntityType: + "_": + "enum": + - |- + FILE + - |- + FILE_METADATA + - |- + PERMISSION github.com/databricks/databricks-sdk-go/service/pipelines.IngestionConfig: "report": "description": |- @@ -4594,6 +4781,8 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionSourceType: SHAREPOINT - |- DYNAMICS365 + - |- + GOOGLE_DRIVE - |- FOREIGN_CATALOG github.com/databricks/databricks-sdk-go/service/pipelines.ManualTrigger: {} @@ -4892,6 +5081,11 @@ github.com/databricks/databricks-sdk-go/service/pipelines.RunAs: "description": |- The email of an active workspace user. Users can only set this field to their own email. github.com/databricks/databricks-sdk-go/service/pipelines.SchemaSpec: + "connector_options": + "description": |- + (Optional) Source Specific Connector Options + "x-databricks-preview": |- + PRIVATE "destination_catalog": "description": |- Required. Destination catalog to store tables. @@ -4907,6 +5101,28 @@ github.com/databricks/databricks-sdk-go/service/pipelines.SchemaSpec: "table_configuration": "description": |- Configuration settings to control the ingestion of tables. These settings are applied to all tables in this schema and override the table_configuration defined in the IngestionPipelineDefinition object. +github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptions: + "entity_type": + "description": |- + (Optional) The type of SharePoint entity to ingest. + If not specified, defaults to FILE. + "file_ingestion_options": + "description": |- + (Optional) File ingestion options for processing files. + "url": + "description": |- + Required. The SharePoint URL. +github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptionsSharepointEntityType: + "_": + "enum": + - |- + FILE + - |- + FILE_METADATA + - |- + PERMISSION + - |- + LIST github.com/databricks/databricks-sdk-go/service/pipelines.SourceCatalogConfig: "_": "description": |- @@ -4922,6 +5138,11 @@ github.com/databricks/databricks-sdk-go/service/pipelines.SourceConfig: "description": |- Catalog-level source configuration parameters github.com/databricks/databricks-sdk-go/service/pipelines.TableSpec: + "connector_options": + "description": |- + (Optional) Source Specific Connector Options + "x-databricks-preview": |- + PRIVATE "destination_catalog": "description": |- Required. Destination catalog to store table. @@ -5014,6 +5235,74 @@ github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfigScd SCD_TYPE_2 - |- APPEND_ONLY +github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptions: + "_": + "description": |- + TikTok Ads specific options for ingestion + "data_level": + "description": |- + (Optional) Data level for the report. + If not specified, defaults to AUCTION_CAMPAIGN. + "dimensions": + "description": |- + (Optional) Dimensions to include in the report. + Examples: "campaign_id", "adgroup_id", "ad_id", "stat_time_day", "stat_time_hour" + If not specified, defaults to campaign_id. + "lookback_window_days": + "description": |- + (Optional) Number of days to look back for report tables during incremental sync + to capture late-arriving conversions and attribution data. + If not specified, defaults to 7 days. + "metrics": + "description": |- + (Optional) Metrics to include in the report. + Examples: "spend", "impressions", "clicks", "conversion", "cpc" + If not specified, defaults to basic metrics (spend, impressions, clicks, etc.) + "query_lifetime": + "description": |- + (Optional) Whether to request lifetime metrics (all-time aggregated data). + When true, the report returns all-time data. + If not specified, defaults to false. + "report_type": + "description": |- + (Optional) Report type for the TikTok Ads API. + If not specified, defaults to BASIC. + "sync_start_date": + "description": |- + (Optional) Start date for the initial sync of report tables in YYYY-MM-DD format. + This determines the earliest date from which to sync historical data. + If not specified, defaults to 1 year of historical data for daily reports + and 30 days for hourly reports. +github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokDataLevel: + "_": + "description": |- + Data level for TikTok Ads report aggregation. + "enum": + - |- + AUCTION_ADVERTISER + - |- + AUCTION_CAMPAIGN + - |- + AUCTION_ADGROUP + - |- + AUCTION_AD +github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokReportType: + "_": + "description": |- + Report type for TikTok Ads API. + "enum": + - |- + BASIC + - |- + AUDIENCE + - |- + PLAYABLE_AD + - |- + DSA + - |- + BUSINESS_CENTER + - |- + GMV_MAX github.com/databricks/databricks-sdk-go/service/postgres.EndpointGroupSpec: "enable_readable_secondaries": "description": |- @@ -5934,6 +6223,15 @@ github.com/databricks/databricks-sdk-go/service/sql.WarehousePermissionLevel: CAN_MONITOR - |- CAN_VIEW +github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType: + "_": + "description": |- + Type of endpoint. + "enum": + - |- + STORAGE_OPTIMIZED + - |- + STANDARD github.com/databricks/databricks-sdk-go/service/workspace.AzureKeyVaultSecretScopeMetadata: "_": "description": |- diff --git a/bundle/internal/schema/annotations_openapi_overrides.yml b/bundle/internal/schema/annotations_openapi_overrides.yml index 611289083e7..690e0c852ee 100644 --- a/bundle/internal/schema/annotations_openapi_overrides.yml +++ b/bundle/internal/schema/annotations_openapi_overrides.yml @@ -328,7 +328,7 @@ github.com/databricks/cli/bundle/config/resources.ModelServingEndpoint: github.com/databricks/cli/bundle/config/resources.Pipeline: "_": "markdown_description": |- - The pipeline resource allows you to create Delta Live Tables [pipelines](/api/workspace/pipelines/create). For information about pipelines, see [_](/dlt/index.md). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [_](/dev-tools/bundles/pipelines-tutorial.md). + This resource allows you to create [pipelines](/api/workspace/pipelines/create). For information about pipelines, see [_](/dlt/index.md). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [_](/dev-tools/bundles/pipelines-tutorial.md). "markdown_examples": |- The following example defines a pipeline with the resource key `hello-pipeline`: @@ -454,7 +454,7 @@ github.com/databricks/cli/bundle/config/resources.RegisteredModel: github.com/databricks/cli/bundle/config/resources.Schema: "_": "markdown_description": |- - The schema resource type allows you to define Unity Catalog [schemas](/api/workspace/schemas/create) for tables and other assets in your workflows and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations: + The schema resource type allows you to define Unity Catalog [schemas](/api/workspace/schemas/create) for tables and other assets in your jobs and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations: - The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema. - Only fields supported by the corresponding [Schemas object create API](/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](/api/workspace/schemas/update). @@ -538,6 +538,13 @@ github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable: "lifecycle": "description": |- PLACEHOLDER +github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint: + "lifecycle": + "description": |- + PLACEHOLDER + "permissions": + "description": |- + PLACEHOLDER github.com/databricks/cli/bundle/config/resources.Volume: "_": "markdown_description": |- @@ -635,6 +642,13 @@ github.com/databricks/databricks-sdk-go/service/apps.AppResource: "uc_securable": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/apps.AppResourceApp: + "name": + "description": |- + PLACEHOLDER + "permission": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/apps.AppResourceDatabase: "database_name": "description": |- @@ -732,6 +746,16 @@ github.com/databricks/databricks-sdk-go/service/catalog.AwsSqsQueue: "queue_url": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/catalog.AzureEncryptionSettings: + "azure_cmk_access_connector_id": + "description": |- + PLACEHOLDER + "azure_cmk_managed_identity_id": + "description": |- + PLACEHOLDER + "azure_tenant_id": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/catalog.AzureQueueStorage: "managed_resource_id": "description": |- @@ -947,6 +971,13 @@ github.com/databricks/databricks-sdk-go/service/jobs.Webhook: "id": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions: + "gdrive_options": + "description": |- + PLACEHOLDER + "sharepoint_options": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/pipelines.CronTrigger: "quartz_cron_schedule": "description": |- @@ -954,6 +985,29 @@ github.com/databricks/databricks-sdk-go/service/pipelines.CronTrigger: "timezone_id": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions: + "corrupt_record_column": + "description": |- + PLACEHOLDER + "ignore_corrupt_files": + "description": |- + PLACEHOLDER + "infer_column_types": + "description": |- + PLACEHOLDER + "rescued_data_column": + "description": |- + PLACEHOLDER + "single_variant_column": + "description": |- + PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions: + "entity_type": + "description": |- + PLACEHOLDER + "file_ingestion_options": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefinition: "netsuite_jar_path": "description": |- diff --git a/bundle/internal/schema/main.go b/bundle/internal/schema/main.go index 3efaf1ea4e1..f8bc399134e 100644 --- a/bundle/internal/schema/main.go +++ b/bundle/internal/schema/main.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" @@ -20,13 +21,13 @@ func interpolationPattern(s string) string { } func addInterpolationPatterns(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { - if typ == reflect.TypeOf(config.Root{}) || typ == reflect.TypeOf(variable.Variable{}) { + if typ == reflect.TypeFor[config.Root]() || typ == reflect.TypeFor[variable.Variable]() { return s } // The variables block in a target override allows for directly specifying // the value of the variable. - if typ == reflect.TypeOf(variable.TargetVariable{}) { + if typ == reflect.TypeFor[variable.TargetVariable]() { return jsonschema.Schema{ AnyOf: []jsonschema.Schema{ // We keep the original schema so that autocomplete suggestions @@ -86,7 +87,7 @@ func addInterpolationPatterns(typ reflect.Type, s jsonschema.Schema) jsonschema. func removeJobsFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { switch typ { - case reflect.TypeOf(resources.Job{}): + case reflect.TypeFor[resources.Job](): // This field has been deprecated in jobs API v2.1 and is always set to // "MULTI_TASK" in the backend. We should not expose it to the user. delete(s.Properties, "format") @@ -97,7 +98,7 @@ func removeJobsFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { delete(s.Properties, "deployment") delete(s.Properties, "edit_mode") - case reflect.TypeOf(jobs.GitSource{}): + case reflect.TypeFor[jobs.GitSource](): // These fields are readonly and are not meant to be set by the user. delete(s.Properties, "job_source") delete(s.Properties, "git_snapshot") @@ -111,7 +112,7 @@ func removeJobsFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { func removePipelineFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { switch typ { - case reflect.TypeOf(resources.Pipeline{}): + case reflect.TypeFor[resources.Pipeline](): // Even though DABs supports this field, TF provider does not. Thus, we // should not expose it to the user. delete(s.Properties, "dry_run") @@ -131,7 +132,7 @@ func removePipelineFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Sche // it's value to "MANAGED" if it's not provided. Thus, we make it optional // in the bundle schema. func makeVolumeTypeOptional(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { - if typ != reflect.TypeOf(resources.Volume{}) { + if typ != reflect.TypeFor[resources.Volume]() { return s } @@ -156,11 +157,8 @@ func removeOutputOnlyFields(typ reflect.Type, s jsonschema.Schema) jsonschema.Sc for name, prop := range s.Properties { // Check if this property is marked as output-only via FieldBehaviors if prop.FieldBehaviors != nil { - for _, behavior := range prop.FieldBehaviors { - if behavior == "OUTPUT_ONLY" { - toRemove = append(toRemove, name) - break - } + if slices.Contains(prop.FieldBehaviors, "OUTPUT_ONLY") { + toRemove = append(toRemove, name) } } } @@ -213,7 +211,7 @@ func generateSchema(workdir, outputFile string, docsMode bool) { log.Fatal(err) } fmt.Printf("Writing OpenAPI annotations to %s\n", annotationsOpenApiPath) - err = p.extractAnnotations(reflect.TypeOf(config.Root{}), annotationsOpenApiPath, annotationsOpenApiOverridesPath) + err = p.extractAnnotations(reflect.TypeFor[config.Root](), annotationsOpenApiPath, annotationsOpenApiOverridesPath) if err != nil { log.Fatal(err) } @@ -236,7 +234,7 @@ func generateSchema(workdir, outputFile string, docsMode bool) { } // Generate the JSON schema from the bundle Go struct. - s, err := jsonschema.FromType(reflect.TypeOf(config.Root{}), transforms) + s, err := jsonschema.FromType(reflect.TypeFor[config.Root](), transforms) // AdditionalProperties is set to an empty schema to allow non-typed keys used as yaml-anchors // Example: diff --git a/bundle/internal/schema/main_test.go b/bundle/internal/schema/main_test.go index 1b655d2e161..0d0e6868ba8 100644 --- a/bundle/internal/schema/main_test.go +++ b/bundle/internal/schema/main_test.go @@ -43,9 +43,9 @@ func copyFile(src, dst string) error { // Checks whether descriptions are added for new config fields in the annotations.yml file // If this test fails either manually add descriptions to the `annotations.yml` or do the following: // 1. for fields described outside of CLI package fetch latest schema from the OpenAPI spec and add path to file to DATABRICKS_OPENAPI_SPEC env variable -// 2. run `make schema` from the repository root to add placeholder descriptions +// 2. run `./task generate-schema` from the repository root to add placeholder descriptions // 2. replace all "PLACEHOLDER" values with the actual descriptions if possible -// 3. run `make schema` again to regenerate the schema with acutal descriptions +// 3. run `./task generate-schema` again to regenerate the schema with acutal descriptions func TestRequiredAnnotationsForNewFields(t *testing.T) { workdir := t.TempDir() annotationsPath := path.Join(workdir, "annotations.yml") @@ -100,7 +100,7 @@ func TestNoDetachedAnnotations(t *testing.T) { } } - _, err := jsonschema.FromType(reflect.TypeOf(config.Root{}), []func(reflect.Type, jsonschema.Schema) jsonschema.Schema{ + _, err := jsonschema.FromType(reflect.TypeFor[config.Root](), []func(reflect.Type, jsonschema.Schema) jsonschema.Schema{ func(typ reflect.Type, s jsonschema.Schema) jsonschema.Schema { delete(types, getPath(typ)) return s @@ -125,6 +125,7 @@ func getAnnotations(path string) (annotation.File, error) { return data, err } +//deadcode:allow disabled pending annotation system overhaul; preserved intentionally func DisabledTestNoDuplicatedAnnotations(t *testing.T) { // Check for duplicated annotations in annotation files files := []string{ diff --git a/bundle/internal/schema/since_version.go b/bundle/internal/schema/since_version.go index f06f0305433..4f9b7d30b7a 100644 --- a/bundle/internal/schema/since_version.go +++ b/bundle/internal/schema/since_version.go @@ -83,7 +83,7 @@ func getVersionTags() ([]string, error) { } var tags []string - for _, line := range strings.Split(string(output), "\n") { + for line := range strings.SplitSeq(string(output), "\n") { tag := strings.TrimSpace(line) if tag == "" { continue diff --git a/bundle/internal/schema/testdata/pass/job.yml b/bundle/internal/schema/testdata/pass/job.yml index ec447ba39ae..cab0824b0a3 100644 --- a/bundle/internal/schema/testdata/pass/job.yml +++ b/bundle/internal/schema/testdata/pass/job.yml @@ -2,7 +2,7 @@ bundle: name: a job workspace: - host: "https://myworkspace.com" + host: "https://myworkspace.test" root_path: /abc presets: diff --git a/bundle/internal/schema/testdata/pass/ml.yml b/bundle/internal/schema/testdata/pass/ml.yml index d2885b6412f..58473e813db 100644 --- a/bundle/internal/schema/testdata/pass/ml.yml +++ b/bundle/internal/schema/testdata/pass/ml.yml @@ -2,7 +2,7 @@ bundle: name: ML workspace: - host: "https://myworkspace.com" + host: "https://myworkspace.test" root_path: /abc presets: diff --git a/bundle/internal/schema/testdata/pass/pipeline.yml b/bundle/internal/schema/testdata/pass/pipeline.yml index 1b2b1a10f0f..7114563c22c 100644 --- a/bundle/internal/schema/testdata/pass/pipeline.yml +++ b/bundle/internal/schema/testdata/pass/pipeline.yml @@ -2,7 +2,7 @@ bundle: name: a pipeline workspace: - host: "https://myworkspace.com" + host: "https://myworkspace.test" root_path: /abc presets: diff --git a/bundle/internal/tf/codegen/generator/generator.go b/bundle/internal/tf/codegen/generator/generator.go index 47af677c00c..0acf9caecf2 100644 --- a/bundle/internal/tf/codegen/generator/generator.go +++ b/bundle/internal/tf/codegen/generator/generator.go @@ -1,11 +1,14 @@ +// Package generator produces Go types from the Terraform provider schema. package generator import ( "context" "fmt" "log" + "maps" "os" "path/filepath" + "slices" "strings" "text/template" @@ -29,7 +32,7 @@ func (c *collection) Generate(path string) error { return err } - defer f.Close() + defer func() { _ = f.Close() }() return tmpl.Execute(f, c) } @@ -48,15 +51,16 @@ func (r *root) Generate(path string) error { return err } - defer f.Close() + defer func() { _ = f.Close() }() return tmpl.Execute(f, r) } -func Run(ctx context.Context, schema *tfjson.ProviderSchema, checksums *schemapkg.ProviderChecksums, path string) error { +// Run generates Go type files under path for every resource and data source in schema. +func Run(_ context.Context, schema *tfjson.ProviderSchema, checksums *schemapkg.ProviderChecksums, path string) error { // Generate types for resources var resources []*namedBlock - for _, k := range sortKeys(schema.ResourceSchemas) { + for _, k := range slices.Sorted(maps.Keys(schema.ResourceSchemas)) { // Skipping all plugin framework struct generation. // TODO: This is a temporary fix, generation should be fixed in the future. if strings.HasSuffix(k, "_pluginframework") { @@ -87,7 +91,7 @@ func Run(ctx context.Context, schema *tfjson.ProviderSchema, checksums *schemapk // Generate types for data sources. var dataSources []*namedBlock - for _, k := range sortKeys(schema.DataSourceSchemas) { + for _, k := range slices.Sorted(maps.Keys(schema.DataSourceSchemas)) { // Skipping all plugin framework struct generation. // TODO: This is a temporary fix, generation should be fixed in the future. if strings.HasSuffix(k, "_pluginframework") { diff --git a/bundle/internal/tf/codegen/generator/named_block.go b/bundle/internal/tf/codegen/generator/named_block.go index bc556cf5bed..5cb5bcc9b71 100644 --- a/bundle/internal/tf/codegen/generator/named_block.go +++ b/bundle/internal/tf/codegen/generator/named_block.go @@ -53,7 +53,7 @@ func (b *namedBlock) Generate(path string) error { return err } - defer f.Close() + defer func() { _ = f.Close() }() tmpl := template.Must(template.ParseFiles("./templates/block.go.tmpl")) return tmpl.Execute(f, w) diff --git a/bundle/internal/tf/codegen/generator/util.go b/bundle/internal/tf/codegen/generator/util.go deleted file mode 100644 index 6e703a70331..00000000000 --- a/bundle/internal/tf/codegen/generator/util.go +++ /dev/null @@ -1,14 +0,0 @@ -package generator - -import ( - "slices" - - "golang.org/x/exp/maps" -) - -// sortKeys returns a sorted copy of the keys in the specified map. -func sortKeys[M ~map[K]V, K string, V any](m M) []K { - keys := maps.Keys(m) - slices.Sort(keys) - return keys -} diff --git a/bundle/internal/tf/codegen/generator/walker.go b/bundle/internal/tf/codegen/generator/walker.go index e08490fe528..aff905c492e 100644 --- a/bundle/internal/tf/codegen/generator/walker.go +++ b/bundle/internal/tf/codegen/generator/walker.go @@ -2,6 +2,7 @@ package generator import ( "fmt" + "maps" "slices" "strings" @@ -117,7 +118,7 @@ func processAttributeType(typ cty.Type, resourceName, attributePath string) stri } func nestedBlockKeys(block *tfjson.SchemaBlock) []string { - keys := sortKeys(block.NestedBlocks) + keys := slices.Sorted(maps.Keys(block.NestedBlocks)) // Remove TF specific "timeouts" block. if i := slices.Index(keys, "timeouts"); i != -1 { @@ -137,7 +138,7 @@ func nestedField(name []string, k string, isRef bool) field { fieldTypePrefix = "[]" } fieldType := fmt.Sprintf("%s%s", fieldTypePrefix, strings.Join(append(name, strcase.ToCamel(k)), "")) - fieldTag := fmt.Sprintf("%s,omitempty", k) + fieldTag := k + ",omitempty" return field{ Name: fieldName, @@ -163,7 +164,7 @@ func (w *walker) walk(block *tfjson.SchemaBlock, name []string) error { } // Declare attributes. - for _, k := range sortKeys(block.Attributes) { + for _, k := range slices.Sorted(maps.Keys(block.Attributes)) { v := block.Attributes[k] // Assert the attribute type is always set. @@ -189,12 +190,14 @@ func (w *walker) walk(block *tfjson.SchemaBlock, name []string) error { fieldName := strcase.ToCamel(k) attributePath := buildAttributePath(name, k) fieldType := processAttributeType(v.AttributeType, w.resourceName, attributePath) - fieldTag := k if v.Required && v.Optional { return fmt.Errorf("both required and optional are set for attribute %s", k) } - if !v.Required { - fieldTag = fmt.Sprintf("%s,omitempty", fieldTag) + var fieldTag string + if v.Required { + fieldTag = k + } else { + fieldTag = k + ",omitempty" } // Append to list of fields for type. diff --git a/bundle/internal/tf/codegen/go.mod b/bundle/internal/tf/codegen/go.mod index bb8cbe8e02d..77e6de4ef13 100644 --- a/bundle/internal/tf/codegen/go.mod +++ b/bundle/internal/tf/codegen/go.mod @@ -2,7 +2,7 @@ module github.com/databricks/cli/bundle/internal/tf/codegen go 1.25.0 -toolchain go1.25.7 +toolchain go1.25.10 require ( github.com/hashicorp/go-version v1.7.0 @@ -11,7 +11,6 @@ require ( github.com/hashicorp/terraform-json v0.27.2 github.com/iancoleman/strcase v0.3.0 github.com/zclconf/go-cty v1.16.4 - golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 ) require ( diff --git a/bundle/internal/tf/codegen/go.sum b/bundle/internal/tf/codegen/go.sum index dc0716a57f0..4bf08058d86 100644 --- a/bundle/internal/tf/codegen/go.sum +++ b/bundle/internal/tf/codegen/go.sum @@ -62,8 +62,6 @@ github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= -golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= diff --git a/bundle/internal/tf/codegen/main.go b/bundle/internal/tf/codegen/main.go index bc7ce6663a5..0671b559c05 100644 --- a/bundle/internal/tf/codegen/main.go +++ b/bundle/internal/tf/codegen/main.go @@ -1,3 +1,4 @@ +// Command codegen generates Go types from the Databricks Terraform provider schema. package main import ( diff --git a/bundle/internal/tf/codegen/schema/checksum.go b/bundle/internal/tf/codegen/schema/checksum.go index 7cc0678eae8..a05d509a76a 100644 --- a/bundle/internal/tf/codegen/schema/checksum.go +++ b/bundle/internal/tf/codegen/schema/checksum.go @@ -33,7 +33,7 @@ func FetchProviderChecksums(version string) (*ProviderChecksums, error) { if err != nil { return nil, fmt.Errorf("downloading SHA256SUMS for provider v%s: %w", version, err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("downloading SHA256SUMS for provider v%s: HTTP %s", version, resp.Status) @@ -94,7 +94,7 @@ func verifyProviderChecksum(version, platform, expectedChecksum string) error { if err != nil { return fmt.Errorf("downloading provider archive for checksum verification: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { return fmt.Errorf("downloading provider archive for checksum verification: HTTP %s", resp.Status) diff --git a/bundle/internal/tf/codegen/schema/generate.go b/bundle/internal/tf/codegen/schema/generate.go index 16d02b300a8..465183ca3b5 100644 --- a/bundle/internal/tf/codegen/schema/generate.go +++ b/bundle/internal/tf/codegen/schema/generate.go @@ -85,6 +85,7 @@ func (s *Schema) generateSchema(ctx context.Context, execPath string) error { return os.WriteFile(s.ProviderSchemaFile, buf, 0o644) } +// Generate produces the provider schema JSON file on disk. func (s *Schema) Generate(ctx context.Context) error { var err error diff --git a/bundle/internal/tf/codegen/schema/load.go b/bundle/internal/tf/codegen/schema/load.go index 794875c9c35..6a19eb0378e 100644 --- a/bundle/internal/tf/codegen/schema/load.go +++ b/bundle/internal/tf/codegen/schema/load.go @@ -9,7 +9,8 @@ import ( tfjson "github.com/hashicorp/terraform-json" ) -func (s *Schema) Load(ctx context.Context) (*tfjson.ProviderSchema, error) { +// Load reads the provider schema JSON file and returns the Databricks provider schema. +func (s *Schema) Load(_ context.Context) (*tfjson.ProviderSchema, error) { buf, err := os.ReadFile(s.ProviderSchemaFile) if err != nil { return nil, err diff --git a/bundle/internal/tf/codegen/schema/schema.go b/bundle/internal/tf/codegen/schema/schema.go index ecd2618c798..ce3d2e1355a 100644 --- a/bundle/internal/tf/codegen/schema/schema.go +++ b/bundle/internal/tf/codegen/schema/schema.go @@ -1,3 +1,5 @@ +// Package schema fetches and loads the Terraform provider schema used by +// the codegen step. package schema import ( @@ -10,14 +12,17 @@ import ( tfjson "github.com/hashicorp/terraform-json" ) +// DatabricksProvider is the fully qualified Terraform provider address. const DatabricksProvider = "registry.terraform.io/databricks/databricks" +// Schema describes the on-disk location of the provider schema artifacts. type Schema struct { WorkingDir string ProviderSchemaFile string } +// New creates a Schema rooted at ./tmp under the current working directory. func New() (*Schema, error) { wd, err := os.Getwd() if err != nil { @@ -36,6 +41,7 @@ func New() (*Schema, error) { }, nil } +// Load returns the parsed provider schema, fetching and generating it if needed. func Load(ctx context.Context) (*tfjson.ProviderSchema, error) { s, err := New() if err != nil { diff --git a/bundle/internal/tf/codegen/schema/version.go b/bundle/internal/tf/codegen/schema/version.go index 5c028273813..8b270d7670b 100644 --- a/bundle/internal/tf/codegen/schema/version.go +++ b/bundle/internal/tf/codegen/schema/version.go @@ -1,3 +1,4 @@ package schema -const ProviderVersion = "1.111.0" +// ProviderVersion is the version of the Databricks Terraform provider used for codegen. +const ProviderVersion = "1.113.0" diff --git a/bundle/internal/tf/schema/data_source_account_network_policies.go b/bundle/internal/tf/schema/data_source_account_network_policies.go index baf6d2841ff..2d91042f2f3 100644 --- a/bundle/internal/tf/schema/data_source_account_network_policies.go +++ b/bundle/internal/tf/schema/data_source_account_network_policies.go @@ -31,10 +31,212 @@ type DataSourceAccountNetworkPoliciesItemsEgress struct { NetworkAccess *DataSourceAccountNetworkPoliciesItemsEgressNetworkAccess `json:"network_access,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccess struct { + AllowRules []DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type DataSourceAccountNetworkPoliciesItemsIngress struct { + PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressPublicAccess `json:"public_access,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccess struct { + AllowRules []DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRun struct { + PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccess `json:"public_access,omitempty"` +} + type DataSourceAccountNetworkPoliciesItems struct { - AccountId string `json:"account_id,omitempty"` - Egress *DataSourceAccountNetworkPoliciesItemsEgress `json:"egress,omitempty"` - NetworkPolicyId string `json:"network_policy_id"` + AccountId string `json:"account_id,omitempty"` + Egress *DataSourceAccountNetworkPoliciesItemsEgress `json:"egress,omitempty"` + Ingress *DataSourceAccountNetworkPoliciesItemsIngress `json:"ingress,omitempty"` + IngressDryRun *DataSourceAccountNetworkPoliciesItemsIngressDryRun `json:"ingress_dry_run,omitempty"` + NetworkPolicyId string `json:"network_policy_id"` } type DataSourceAccountNetworkPolicies struct { diff --git a/bundle/internal/tf/schema/data_source_account_network_policy.go b/bundle/internal/tf/schema/data_source_account_network_policy.go index fc97f8217e8..e788484c537 100644 --- a/bundle/internal/tf/schema/data_source_account_network_policy.go +++ b/bundle/internal/tf/schema/data_source_account_network_policy.go @@ -31,8 +31,210 @@ type DataSourceAccountNetworkPolicyEgress struct { NetworkAccess *DataSourceAccountNetworkPolicyEgressNetworkAccess `json:"network_access,omitempty"` } +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccess struct { + AllowRules []DataSourceAccountNetworkPolicyIngressPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPolicyIngressPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type DataSourceAccountNetworkPolicyIngress struct { + PublicAccess *DataSourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccess struct { + AllowRules []DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type DataSourceAccountNetworkPolicyIngressDryRun struct { + PublicAccess *DataSourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` +} + type DataSourceAccountNetworkPolicy struct { - AccountId string `json:"account_id,omitempty"` - Egress *DataSourceAccountNetworkPolicyEgress `json:"egress,omitempty"` - NetworkPolicyId string `json:"network_policy_id"` + AccountId string `json:"account_id,omitempty"` + Egress *DataSourceAccountNetworkPolicyEgress `json:"egress,omitempty"` + Ingress *DataSourceAccountNetworkPolicyIngress `json:"ingress,omitempty"` + IngressDryRun *DataSourceAccountNetworkPolicyIngressDryRun `json:"ingress_dry_run,omitempty"` + NetworkPolicyId string `json:"network_policy_id"` } diff --git a/bundle/internal/tf/schema/data_source_account_setting_v2.go b/bundle/internal/tf/schema/data_source_account_setting_v2.go index cc0bd80985f..3279de8eabb 100644 --- a/bundle/internal/tf/schema/data_source_account_setting_v2.go +++ b/bundle/internal/tf/schema/data_source_account_setting_v2.go @@ -93,7 +93,8 @@ type DataSourceAccountSettingV2EffectivePersonalCompute struct { } type DataSourceAccountSettingV2EffectiveRestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type DataSourceAccountSettingV2EffectiveStringVal struct { @@ -109,7 +110,8 @@ type DataSourceAccountSettingV2PersonalCompute struct { } type DataSourceAccountSettingV2RestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type DataSourceAccountSettingV2StringVal struct { diff --git a/bundle/internal/tf/schema/data_source_app.go b/bundle/internal/tf/schema/data_source_app.go index d5eea13d880..3c3df220cbd 100644 --- a/bundle/internal/tf/schema/data_source_app.go +++ b/bundle/internal/tf/schema/data_source_app.go @@ -105,6 +105,8 @@ type DataSourceAppAppPendingDeployment struct { } type DataSourceAppAppResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type DataSourceAppAppResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/data_source_app_space.go b/bundle/internal/tf/schema/data_source_app_space.go index 52af0646fc5..3a1e124d5ce 100644 --- a/bundle/internal/tf/schema/data_source_app_space.go +++ b/bundle/internal/tf/schema/data_source_app_space.go @@ -7,6 +7,8 @@ type DataSourceAppSpaceProviderConfig struct { } type DataSourceAppSpaceResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type DataSourceAppSpaceResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/data_source_app_spaces.go b/bundle/internal/tf/schema/data_source_app_spaces.go index ed12ae7801e..a00255ad025 100644 --- a/bundle/internal/tf/schema/data_source_app_spaces.go +++ b/bundle/internal/tf/schema/data_source_app_spaces.go @@ -11,6 +11,8 @@ type DataSourceAppSpacesSpacesProviderConfig struct { } type DataSourceAppSpacesSpacesResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type DataSourceAppSpacesSpacesResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/data_source_apps.go b/bundle/internal/tf/schema/data_source_apps.go index 2edf0658219..9f775036f84 100644 --- a/bundle/internal/tf/schema/data_source_apps.go +++ b/bundle/internal/tf/schema/data_source_apps.go @@ -105,6 +105,8 @@ type DataSourceAppsAppPendingDeployment struct { } type DataSourceAppsAppResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type DataSourceAppsAppResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/data_source_catalog.go b/bundle/internal/tf/schema/data_source_catalog.go index 269282abdff..5c293cd0e60 100644 --- a/bundle/internal/tf/schema/data_source_catalog.go +++ b/bundle/internal/tf/schema/data_source_catalog.go @@ -8,6 +8,18 @@ type DataSourceCatalogCatalogInfoEffectivePredictiveOptimizationFlag struct { Value string `json:"value"` } +type DataSourceCatalogCatalogInfoManagedEncryptionSettingsAzureEncryptionSettings struct { + AzureCmkAccessConnectorId string `json:"azure_cmk_access_connector_id,omitempty"` + AzureCmkManagedIdentityId string `json:"azure_cmk_managed_identity_id,omitempty"` + AzureTenantId string `json:"azure_tenant_id"` +} + +type DataSourceCatalogCatalogInfoManagedEncryptionSettings struct { + AzureKeyVaultKeyId string `json:"azure_key_vault_key_id,omitempty"` + CustomerManagedKeyId string `json:"customer_managed_key_id,omitempty"` + AzureEncryptionSettings *DataSourceCatalogCatalogInfoManagedEncryptionSettingsAzureEncryptionSettings `json:"azure_encryption_settings,omitempty"` +} + type DataSourceCatalogCatalogInfoProvisioningInfo struct { State string `json:"state,omitempty"` } @@ -35,6 +47,7 @@ type DataSourceCatalogCatalogInfo struct { UpdatedAt int `json:"updated_at,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` EffectivePredictiveOptimizationFlag *DataSourceCatalogCatalogInfoEffectivePredictiveOptimizationFlag `json:"effective_predictive_optimization_flag,omitempty"` + ManagedEncryptionSettings *DataSourceCatalogCatalogInfoManagedEncryptionSettings `json:"managed_encryption_settings,omitempty"` ProvisioningInfo *DataSourceCatalogCatalogInfoProvisioningInfo `json:"provisioning_info,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_current_config.go b/bundle/internal/tf/schema/data_source_current_config.go index c59aacc6d9f..0dc751a724d 100644 --- a/bundle/internal/tf/schema/data_source_current_config.go +++ b/bundle/internal/tf/schema/data_source_current_config.go @@ -8,7 +8,9 @@ type DataSourceCurrentConfigProviderConfig struct { type DataSourceCurrentConfig struct { AccountId string `json:"account_id,omitempty"` + Api string `json:"api,omitempty"` AuthType string `json:"auth_type,omitempty"` + Cloud string `json:"cloud,omitempty"` CloudType string `json:"cloud_type,omitempty"` Host string `json:"host,omitempty"` Id string `json:"id,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_environments_default_workspace_base_environment.go b/bundle/internal/tf/schema/data_source_environments_default_workspace_base_environment.go new file mode 100644 index 00000000000..d44ad8ff58e --- /dev/null +++ b/bundle/internal/tf/schema/data_source_environments_default_workspace_base_environment.go @@ -0,0 +1,14 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourceEnvironmentsDefaultWorkspaceBaseEnvironment struct { + CpuWorkspaceBaseEnvironment string `json:"cpu_workspace_base_environment,omitempty"` + GpuWorkspaceBaseEnvironment string `json:"gpu_workspace_base_environment,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig `json:"provider_config,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_environments_workspace_base_environment.go b/bundle/internal/tf/schema/data_source_environments_workspace_base_environment.go new file mode 100644 index 00000000000..83354027dd7 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_environments_workspace_base_environment.go @@ -0,0 +1,23 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourceEnvironmentsWorkspaceBaseEnvironment struct { + BaseEnvironmentType string `json:"base_environment_type,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatorUserId string `json:"creator_user_id,omitempty"` + DisplayName string `json:"display_name,omitempty"` + EffectiveBaseEnvironmentType string `json:"effective_base_environment_type,omitempty"` + Filepath string `json:"filepath,omitempty"` + IsDefault bool `json:"is_default,omitempty"` + LastUpdatedUserId string `json:"last_updated_user_id,omitempty"` + Message string `json:"message,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig `json:"provider_config,omitempty"` + Status string `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_environments_workspace_base_environments.go b/bundle/internal/tf/schema/data_source_environments_workspace_base_environments.go new file mode 100644 index 00000000000..cb4a39cd2db --- /dev/null +++ b/bundle/internal/tf/schema/data_source_environments_workspace_base_environments.go @@ -0,0 +1,33 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceEnvironmentsWorkspaceBaseEnvironmentsProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironmentsProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironments struct { + BaseEnvironmentType string `json:"base_environment_type,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatorUserId string `json:"creator_user_id,omitempty"` + DisplayName string `json:"display_name,omitempty"` + EffectiveBaseEnvironmentType string `json:"effective_base_environment_type,omitempty"` + Filepath string `json:"filepath,omitempty"` + IsDefault bool `json:"is_default,omitempty"` + LastUpdatedUserId string `json:"last_updated_user_id,omitempty"` + Message string `json:"message,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironmentsProviderConfig `json:"provider_config,omitempty"` + Status string `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} + +type DataSourceEnvironmentsWorkspaceBaseEnvironments struct { + PageSize int `json:"page_size,omitempty"` + ProviderConfig *DataSourceEnvironmentsWorkspaceBaseEnvironmentsProviderConfig `json:"provider_config,omitempty"` + WorkspaceBaseEnvironments []DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironments `json:"workspace_base_environments,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_external_location.go b/bundle/internal/tf/schema/data_source_external_location.go index f818a7cbafa..54038eb3205 100644 --- a/bundle/internal/tf/schema/data_source_external_location.go +++ b/bundle/internal/tf/schema/data_source_external_location.go @@ -2,6 +2,49 @@ package schema +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedAqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` + ResourceGroup string `json:"resource_group,omitempty"` + SubscriptionId string `json:"subscription_id,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedPubsub struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + SubscriptionName string `json:"subscription_name,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedSqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedAqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` + ResourceGroup string `json:"resource_group,omitempty"` + SubscriptionId string `json:"subscription_id,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedPubsub struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + SubscriptionName string `json:"subscription_name,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedSqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` +} + +type DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueue struct { + ManagedAqs *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedAqs `json:"managed_aqs,omitempty"` + ManagedPubsub *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedPubsub `json:"managed_pubsub,omitempty"` + ManagedSqs *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueManagedSqs `json:"managed_sqs,omitempty"` + ProvidedAqs *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedAqs `json:"provided_aqs,omitempty"` + ProvidedPubsub *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedPubsub `json:"provided_pubsub,omitempty"` + ProvidedSqs *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueueProvidedSqs `json:"provided_sqs,omitempty"` +} + type DataSourceExternalLocationExternalLocationInfoEncryptionDetailsSseEncryptionDetails struct { Algorithm string `json:"algorithm,omitempty"` AwsKmsKeyArn string `json:"aws_kms_key_arn,omitempty"` @@ -55,25 +98,26 @@ type DataSourceExternalLocationExternalLocationInfoFileEventQueue struct { } type DataSourceExternalLocationExternalLocationInfo struct { - BrowseOnly bool `json:"browse_only,omitempty"` - Comment string `json:"comment,omitempty"` - CreatedAt int `json:"created_at,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - CredentialId string `json:"credential_id,omitempty"` - CredentialName string `json:"credential_name,omitempty"` - EffectiveEnableFileEvents bool `json:"effective_enable_file_events,omitempty"` - EnableFileEvents bool `json:"enable_file_events,omitempty"` - Fallback bool `json:"fallback,omitempty"` - IsolationMode string `json:"isolation_mode,omitempty"` - MetastoreId string `json:"metastore_id,omitempty"` - Name string `json:"name,omitempty"` - Owner string `json:"owner,omitempty"` - ReadOnly bool `json:"read_only,omitempty"` - UpdatedAt int `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Url string `json:"url,omitempty"` - EncryptionDetails *DataSourceExternalLocationExternalLocationInfoEncryptionDetails `json:"encryption_details,omitempty"` - FileEventQueue *DataSourceExternalLocationExternalLocationInfoFileEventQueue `json:"file_event_queue,omitempty"` + BrowseOnly bool `json:"browse_only,omitempty"` + Comment string `json:"comment,omitempty"` + CreatedAt int `json:"created_at,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + CredentialId string `json:"credential_id,omitempty"` + CredentialName string `json:"credential_name,omitempty"` + EffectiveEnableFileEvents bool `json:"effective_enable_file_events,omitempty"` + EnableFileEvents bool `json:"enable_file_events,omitempty"` + Fallback bool `json:"fallback,omitempty"` + IsolationMode string `json:"isolation_mode,omitempty"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + UpdatedAt int `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Url string `json:"url,omitempty"` + EffectiveFileEventQueue *DataSourceExternalLocationExternalLocationInfoEffectiveFileEventQueue `json:"effective_file_event_queue,omitempty"` + EncryptionDetails *DataSourceExternalLocationExternalLocationInfoEncryptionDetails `json:"encryption_details,omitempty"` + FileEventQueue *DataSourceExternalLocationExternalLocationInfoFileEventQueue `json:"file_event_queue,omitempty"` } type DataSourceExternalLocationProviderConfig struct { diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_feature.go b/bundle/internal/tf/schema/data_source_feature_engineering_feature.go index abbe14f3354..7cb9e275dc1 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_feature.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_feature.go @@ -2,14 +2,116 @@ package schema +type DataSourceFeatureEngineeringFeatureEntities struct { + Name string `json:"name"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxCountDistinct struct { + Input string `json:"input"` + RelativeSd int `json:"relative_sd,omitempty"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxPercentile struct { + Accuracy int `json:"accuracy,omitempty"` + Input string `json:"input"` + Percentile int `json:"percentile"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionAvg struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionCountFunction struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionFirst struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionLast struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionMax struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionMin struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevPop struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevSamp struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionSum struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowContinuous struct { + Offset string `json:"offset,omitempty"` + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowSliding struct { + SlideDuration string `json:"slide_duration"` + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowTumbling struct { + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindow struct { + Continuous *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowContinuous `json:"continuous,omitempty"` + Sliding *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowSliding `json:"sliding,omitempty"` + Tumbling *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowTumbling `json:"tumbling,omitempty"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionVarPop struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionVarSamp struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeatureFunctionAggregationFunction struct { + ApproxCountDistinct *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxCountDistinct `json:"approx_count_distinct,omitempty"` + ApproxPercentile *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxPercentile `json:"approx_percentile,omitempty"` + Avg *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionAvg `json:"avg,omitempty"` + CountFunction *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionCountFunction `json:"count_function,omitempty"` + First *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionFirst `json:"first,omitempty"` + Last *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionLast `json:"last,omitempty"` + Max *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionMax `json:"max,omitempty"` + Min *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionMin `json:"min,omitempty"` + StddevPop *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevPop `json:"stddev_pop,omitempty"` + StddevSamp *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevSamp `json:"stddev_samp,omitempty"` + Sum *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionSum `json:"sum,omitempty"` + TimeWindow *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindow `json:"time_window,omitempty"` + VarPop *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionVarPop `json:"var_pop,omitempty"` + VarSamp *DataSourceFeatureEngineeringFeatureFunctionAggregationFunctionVarSamp `json:"var_samp,omitempty"` +} + +type DataSourceFeatureEngineeringFeatureFunctionColumnSelection struct { + Column string `json:"column"` +} + type DataSourceFeatureEngineeringFeatureFunctionExtraParameters struct { Key string `json:"key"` Value string `json:"value"` } type DataSourceFeatureEngineeringFeatureFunction struct { - ExtraParameters []DataSourceFeatureEngineeringFeatureFunctionExtraParameters `json:"extra_parameters,omitempty"` - FunctionType string `json:"function_type"` + AggregationFunction *DataSourceFeatureEngineeringFeatureFunctionAggregationFunction `json:"aggregation_function,omitempty"` + ColumnSelection *DataSourceFeatureEngineeringFeatureFunctionColumnSelection `json:"column_selection,omitempty"` + ExtraParameters []DataSourceFeatureEngineeringFeatureFunctionExtraParameters `json:"extra_parameters,omitempty"` + FunctionType string `json:"function_type,omitempty"` } type DataSourceFeatureEngineeringFeatureLineageContextJobContext struct { @@ -28,10 +130,10 @@ type DataSourceFeatureEngineeringFeatureProviderConfig struct { type DataSourceFeatureEngineeringFeatureSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } @@ -45,13 +147,28 @@ type DataSourceFeatureEngineeringFeatureSourceKafkaSourceTimeseriesColumnIdentif type DataSourceFeatureEngineeringFeatureSourceKafkaSource struct { EntityColumnIdentifiers []DataSourceFeatureEngineeringFeatureSourceKafkaSourceEntityColumnIdentifiers `json:"entity_column_identifiers,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` Name string `json:"name"` TimeseriesColumnIdentifier *DataSourceFeatureEngineeringFeatureSourceKafkaSourceTimeseriesColumnIdentifier `json:"timeseries_column_identifier,omitempty"` } +type DataSourceFeatureEngineeringFeatureSourceRequestSourceFlatSchemaFields struct { + DataType string `json:"data_type"` + Name string `json:"name"` +} + +type DataSourceFeatureEngineeringFeatureSourceRequestSourceFlatSchema struct { + Fields []DataSourceFeatureEngineeringFeatureSourceRequestSourceFlatSchemaFields `json:"fields,omitempty"` +} + +type DataSourceFeatureEngineeringFeatureSourceRequestSource struct { + FlatSchema *DataSourceFeatureEngineeringFeatureSourceRequestSourceFlatSchema `json:"flat_schema,omitempty"` +} + type DataSourceFeatureEngineeringFeatureSource struct { DeltaTableSource *DataSourceFeatureEngineeringFeatureSourceDeltaTableSource `json:"delta_table_source,omitempty"` KafkaSource *DataSourceFeatureEngineeringFeatureSourceKafkaSource `json:"kafka_source,omitempty"` + RequestSource *DataSourceFeatureEngineeringFeatureSourceRequestSource `json:"request_source,omitempty"` } type DataSourceFeatureEngineeringFeatureTimeWindowContinuous struct { @@ -74,14 +191,20 @@ type DataSourceFeatureEngineeringFeatureTimeWindow struct { Tumbling *DataSourceFeatureEngineeringFeatureTimeWindowTumbling `json:"tumbling,omitempty"` } +type DataSourceFeatureEngineeringFeatureTimeseriesColumn struct { + Name string `json:"name"` +} + type DataSourceFeatureEngineeringFeature struct { - Description string `json:"description,omitempty"` - FilterCondition string `json:"filter_condition,omitempty"` - FullName string `json:"full_name"` - Function *DataSourceFeatureEngineeringFeatureFunction `json:"function,omitempty"` - Inputs []string `json:"inputs,omitempty"` - LineageContext *DataSourceFeatureEngineeringFeatureLineageContext `json:"lineage_context,omitempty"` - ProviderConfig *DataSourceFeatureEngineeringFeatureProviderConfig `json:"provider_config,omitempty"` - Source *DataSourceFeatureEngineeringFeatureSource `json:"source,omitempty"` - TimeWindow *DataSourceFeatureEngineeringFeatureTimeWindow `json:"time_window,omitempty"` + Description string `json:"description,omitempty"` + Entities []DataSourceFeatureEngineeringFeatureEntities `json:"entities,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` + FullName string `json:"full_name"` + Function *DataSourceFeatureEngineeringFeatureFunction `json:"function,omitempty"` + Inputs []string `json:"inputs,omitempty"` + LineageContext *DataSourceFeatureEngineeringFeatureLineageContext `json:"lineage_context,omitempty"` + ProviderConfig *DataSourceFeatureEngineeringFeatureProviderConfig `json:"provider_config,omitempty"` + Source *DataSourceFeatureEngineeringFeatureSource `json:"source,omitempty"` + TimeWindow *DataSourceFeatureEngineeringFeatureTimeWindow `json:"time_window,omitempty"` + TimeseriesColumn *DataSourceFeatureEngineeringFeatureTimeseriesColumn `json:"timeseries_column,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_features.go b/bundle/internal/tf/schema/data_source_feature_engineering_features.go index b13f5660351..cc7a00b22d6 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_features.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_features.go @@ -2,14 +2,116 @@ package schema +type DataSourceFeatureEngineeringFeaturesFeaturesEntities struct { + Name string `json:"name"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionApproxCountDistinct struct { + Input string `json:"input"` + RelativeSd int `json:"relative_sd,omitempty"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionApproxPercentile struct { + Accuracy int `json:"accuracy,omitempty"` + Input string `json:"input"` + Percentile int `json:"percentile"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionAvg struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionCountFunction struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionFirst struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionLast struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionMax struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionMin struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionStddevPop struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionStddevSamp struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionSum struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowContinuous struct { + Offset string `json:"offset,omitempty"` + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowSliding struct { + SlideDuration string `json:"slide_duration"` + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowTumbling struct { + WindowDuration string `json:"window_duration"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindow struct { + Continuous *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowContinuous `json:"continuous,omitempty"` + Sliding *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowSliding `json:"sliding,omitempty"` + Tumbling *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindowTumbling `json:"tumbling,omitempty"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionVarPop struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionVarSamp struct { + Input string `json:"input"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunction struct { + ApproxCountDistinct *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionApproxCountDistinct `json:"approx_count_distinct,omitempty"` + ApproxPercentile *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionApproxPercentile `json:"approx_percentile,omitempty"` + Avg *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionAvg `json:"avg,omitempty"` + CountFunction *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionCountFunction `json:"count_function,omitempty"` + First *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionFirst `json:"first,omitempty"` + Last *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionLast `json:"last,omitempty"` + Max *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionMax `json:"max,omitempty"` + Min *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionMin `json:"min,omitempty"` + StddevPop *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionStddevPop `json:"stddev_pop,omitempty"` + StddevSamp *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionStddevSamp `json:"stddev_samp,omitempty"` + Sum *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionSum `json:"sum,omitempty"` + TimeWindow *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionTimeWindow `json:"time_window,omitempty"` + VarPop *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionVarPop `json:"var_pop,omitempty"` + VarSamp *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunctionVarSamp `json:"var_samp,omitempty"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesFunctionColumnSelection struct { + Column string `json:"column"` +} + type DataSourceFeatureEngineeringFeaturesFeaturesFunctionExtraParameters struct { Key string `json:"key"` Value string `json:"value"` } type DataSourceFeatureEngineeringFeaturesFeaturesFunction struct { - ExtraParameters []DataSourceFeatureEngineeringFeaturesFeaturesFunctionExtraParameters `json:"extra_parameters,omitempty"` - FunctionType string `json:"function_type"` + AggregationFunction *DataSourceFeatureEngineeringFeaturesFeaturesFunctionAggregationFunction `json:"aggregation_function,omitempty"` + ColumnSelection *DataSourceFeatureEngineeringFeaturesFeaturesFunctionColumnSelection `json:"column_selection,omitempty"` + ExtraParameters []DataSourceFeatureEngineeringFeaturesFeaturesFunctionExtraParameters `json:"extra_parameters,omitempty"` + FunctionType string `json:"function_type,omitempty"` } type DataSourceFeatureEngineeringFeaturesFeaturesLineageContextJobContext struct { @@ -28,10 +130,10 @@ type DataSourceFeatureEngineeringFeaturesFeaturesProviderConfig struct { type DataSourceFeatureEngineeringFeaturesFeaturesSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } @@ -45,13 +147,28 @@ type DataSourceFeatureEngineeringFeaturesFeaturesSourceKafkaSourceTimeseriesColu type DataSourceFeatureEngineeringFeaturesFeaturesSourceKafkaSource struct { EntityColumnIdentifiers []DataSourceFeatureEngineeringFeaturesFeaturesSourceKafkaSourceEntityColumnIdentifiers `json:"entity_column_identifiers,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` Name string `json:"name"` TimeseriesColumnIdentifier *DataSourceFeatureEngineeringFeaturesFeaturesSourceKafkaSourceTimeseriesColumnIdentifier `json:"timeseries_column_identifier,omitempty"` } +type DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSourceFlatSchemaFields struct { + DataType string `json:"data_type"` + Name string `json:"name"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSourceFlatSchema struct { + Fields []DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSourceFlatSchemaFields `json:"fields,omitempty"` +} + +type DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSource struct { + FlatSchema *DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSourceFlatSchema `json:"flat_schema,omitempty"` +} + type DataSourceFeatureEngineeringFeaturesFeaturesSource struct { DeltaTableSource *DataSourceFeatureEngineeringFeaturesFeaturesSourceDeltaTableSource `json:"delta_table_source,omitempty"` KafkaSource *DataSourceFeatureEngineeringFeaturesFeaturesSourceKafkaSource `json:"kafka_source,omitempty"` + RequestSource *DataSourceFeatureEngineeringFeaturesFeaturesSourceRequestSource `json:"request_source,omitempty"` } type DataSourceFeatureEngineeringFeaturesFeaturesTimeWindowContinuous struct { @@ -74,16 +191,22 @@ type DataSourceFeatureEngineeringFeaturesFeaturesTimeWindow struct { Tumbling *DataSourceFeatureEngineeringFeaturesFeaturesTimeWindowTumbling `json:"tumbling,omitempty"` } +type DataSourceFeatureEngineeringFeaturesFeaturesTimeseriesColumn struct { + Name string `json:"name"` +} + type DataSourceFeatureEngineeringFeaturesFeatures struct { - Description string `json:"description,omitempty"` - FilterCondition string `json:"filter_condition,omitempty"` - FullName string `json:"full_name"` - Function *DataSourceFeatureEngineeringFeaturesFeaturesFunction `json:"function,omitempty"` - Inputs []string `json:"inputs,omitempty"` - LineageContext *DataSourceFeatureEngineeringFeaturesFeaturesLineageContext `json:"lineage_context,omitempty"` - ProviderConfig *DataSourceFeatureEngineeringFeaturesFeaturesProviderConfig `json:"provider_config,omitempty"` - Source *DataSourceFeatureEngineeringFeaturesFeaturesSource `json:"source,omitempty"` - TimeWindow *DataSourceFeatureEngineeringFeaturesFeaturesTimeWindow `json:"time_window,omitempty"` + Description string `json:"description,omitempty"` + Entities []DataSourceFeatureEngineeringFeaturesFeaturesEntities `json:"entities,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` + FullName string `json:"full_name"` + Function *DataSourceFeatureEngineeringFeaturesFeaturesFunction `json:"function,omitempty"` + Inputs []string `json:"inputs,omitempty"` + LineageContext *DataSourceFeatureEngineeringFeaturesFeaturesLineageContext `json:"lineage_context,omitempty"` + ProviderConfig *DataSourceFeatureEngineeringFeaturesFeaturesProviderConfig `json:"provider_config,omitempty"` + Source *DataSourceFeatureEngineeringFeaturesFeaturesSource `json:"source,omitempty"` + TimeWindow *DataSourceFeatureEngineeringFeaturesFeaturesTimeWindow `json:"time_window,omitempty"` + TimeseriesColumn *DataSourceFeatureEngineeringFeaturesFeaturesTimeseriesColumn `json:"timeseries_column,omitempty"` } type DataSourceFeatureEngineeringFeaturesProviderConfig struct { diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_kafka_config.go b/bundle/internal/tf/schema/data_source_feature_engineering_kafka_config.go index 527ebe6b8b6..e16324ca395 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_kafka_config.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_kafka_config.go @@ -8,10 +8,10 @@ type DataSourceFeatureEngineeringKafkaConfigAuthConfig struct { type DataSourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_kafka_configs.go b/bundle/internal/tf/schema/data_source_feature_engineering_kafka_configs.go index 851d2af64b9..41016125441 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_kafka_configs.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_kafka_configs.go @@ -8,10 +8,10 @@ type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsAuthConfig struct { type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsBackfillSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_materialized_feature.go b/bundle/internal/tf/schema/data_source_feature_engineering_materialized_feature.go index b03524484e0..bf0000ae622 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_materialized_feature.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_materialized_feature.go @@ -22,6 +22,7 @@ type DataSourceFeatureEngineeringMaterializedFeatureProviderConfig struct { type DataSourceFeatureEngineeringMaterializedFeature struct { CronSchedule string `json:"cron_schedule,omitempty"` FeatureName string `json:"feature_name,omitempty"` + IsOnline bool `json:"is_online,omitempty"` LastMaterializationTime string `json:"last_materialization_time,omitempty"` MaterializedFeatureId string `json:"materialized_feature_id"` OfflineStoreConfig *DataSourceFeatureEngineeringMaterializedFeatureOfflineStoreConfig `json:"offline_store_config,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_feature_engineering_materialized_features.go b/bundle/internal/tf/schema/data_source_feature_engineering_materialized_features.go index c3b7ac9b1bc..1b56f945487 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_materialized_features.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_materialized_features.go @@ -22,6 +22,7 @@ type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeaturesProvide type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeatures struct { CronSchedule string `json:"cron_schedule,omitempty"` FeatureName string `json:"feature_name,omitempty"` + IsOnline bool `json:"is_online,omitempty"` LastMaterializationTime string `json:"last_materialization_time,omitempty"` MaterializedFeatureId string `json:"materialized_feature_id"` OfflineStoreConfig *DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeaturesOfflineStoreConfig `json:"offline_store_config,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_group.go b/bundle/internal/tf/schema/data_source_group.go index 26d8199a137..c68aee75fb4 100644 --- a/bundle/internal/tf/schema/data_source_group.go +++ b/bundle/internal/tf/schema/data_source_group.go @@ -10,6 +10,7 @@ type DataSourceGroup struct { AclPrincipalId string `json:"acl_principal_id,omitempty"` AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` + Api string `json:"api,omitempty"` ChildGroups []string `json:"child_groups,omitempty"` DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` DisplayName string `json:"display_name"` diff --git a/bundle/internal/tf/schema/data_source_postgres_catalog.go b/bundle/internal/tf/schema/data_source_postgres_catalog.go new file mode 100644 index 00000000000..1a7df9c533f --- /dev/null +++ b/bundle/internal/tf/schema/data_source_postgres_catalog.go @@ -0,0 +1,29 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourcePostgresCatalogProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourcePostgresCatalogSpec struct { + Branch string `json:"branch,omitempty"` + CreateDatabaseIfMissing bool `json:"create_database_if_missing,omitempty"` + PostgresDatabase string `json:"postgres_database"` +} + +type DataSourcePostgresCatalogStatus struct { + Branch string `json:"branch,omitempty"` + PostgresDatabase string `json:"postgres_database,omitempty"` + Project string `json:"project,omitempty"` +} + +type DataSourcePostgresCatalog struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourcePostgresCatalogProviderConfig `json:"provider_config,omitempty"` + Spec *DataSourcePostgresCatalogSpec `json:"spec,omitempty"` + Status *DataSourcePostgresCatalogStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_postgres_project.go b/bundle/internal/tf/schema/data_source_postgres_project.go index 657482acf48..8a9c3ccc69e 100644 --- a/bundle/internal/tf/schema/data_source_postgres_project.go +++ b/bundle/internal/tf/schema/data_source_postgres_project.go @@ -32,6 +32,7 @@ type DataSourcePostgresProjectSpecDefaultEndpointSettings struct { type DataSourcePostgresProjectSpec struct { BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []DataSourcePostgresProjectSpecCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *DataSourcePostgresProjectSpecDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` @@ -56,6 +57,7 @@ type DataSourcePostgresProjectStatus struct { BranchLogicalSizeLimitBytes int `json:"branch_logical_size_limit_bytes,omitempty"` BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []DataSourcePostgresProjectStatusCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *DataSourcePostgresProjectStatusDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_postgres_projects.go b/bundle/internal/tf/schema/data_source_postgres_projects.go index a91cfdae5b4..ca5aa51cf85 100644 --- a/bundle/internal/tf/schema/data_source_postgres_projects.go +++ b/bundle/internal/tf/schema/data_source_postgres_projects.go @@ -32,6 +32,7 @@ type DataSourcePostgresProjectsProjectsSpecDefaultEndpointSettings struct { type DataSourcePostgresProjectsProjectsSpec struct { BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []DataSourcePostgresProjectsProjectsSpecCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *DataSourcePostgresProjectsProjectsSpecDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` @@ -56,6 +57,7 @@ type DataSourcePostgresProjectsProjectsStatus struct { BranchLogicalSizeLimitBytes int `json:"branch_logical_size_limit_bytes,omitempty"` BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []DataSourcePostgresProjectsProjectsStatusCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *DataSourcePostgresProjectsProjectsStatusDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_postgres_role.go b/bundle/internal/tf/schema/data_source_postgres_role.go new file mode 100644 index 00000000000..2c012a8d605 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_postgres_role.go @@ -0,0 +1,45 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourcePostgresRoleProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourcePostgresRoleSpecAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type DataSourcePostgresRoleSpec struct { + Attributes *DataSourcePostgresRoleSpecAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type DataSourcePostgresRoleStatusAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type DataSourcePostgresRoleStatus struct { + Attributes *DataSourcePostgresRoleStatusAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type DataSourcePostgresRole struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name"` + Parent string `json:"parent,omitempty"` + ProviderConfig *DataSourcePostgresRoleProviderConfig `json:"provider_config,omitempty"` + Spec *DataSourcePostgresRoleSpec `json:"spec,omitempty"` + Status *DataSourcePostgresRoleStatus `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_postgres_roles.go b/bundle/internal/tf/schema/data_source_postgres_roles.go new file mode 100644 index 00000000000..828e472d383 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_postgres_roles.go @@ -0,0 +1,56 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourcePostgresRolesProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourcePostgresRolesRolesProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourcePostgresRolesRolesSpecAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type DataSourcePostgresRolesRolesSpec struct { + Attributes *DataSourcePostgresRolesRolesSpecAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type DataSourcePostgresRolesRolesStatusAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type DataSourcePostgresRolesRolesStatus struct { + Attributes *DataSourcePostgresRolesRolesStatusAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type DataSourcePostgresRolesRoles struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name"` + Parent string `json:"parent,omitempty"` + ProviderConfig *DataSourcePostgresRolesRolesProviderConfig `json:"provider_config,omitempty"` + Spec *DataSourcePostgresRolesRolesSpec `json:"spec,omitempty"` + Status *DataSourcePostgresRolesRolesStatus `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} + +type DataSourcePostgresRoles struct { + PageSize int `json:"page_size,omitempty"` + Parent string `json:"parent"` + ProviderConfig *DataSourcePostgresRolesProviderConfig `json:"provider_config,omitempty"` + Roles []DataSourcePostgresRolesRoles `json:"roles,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_postgres_synced_table.go b/bundle/internal/tf/schema/data_source_postgres_synced_table.go new file mode 100644 index 00000000000..ea8899ba077 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_postgres_synced_table.go @@ -0,0 +1,65 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourcePostgresSyncedTableProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type DataSourcePostgresSyncedTableSpecNewPipelineSpec struct { + BudgetPolicyId string `json:"budget_policy_id,omitempty"` + StorageCatalog string `json:"storage_catalog,omitempty"` + StorageSchema string `json:"storage_schema,omitempty"` +} + +type DataSourcePostgresSyncedTableSpec struct { + Branch string `json:"branch,omitempty"` + CreateDatabaseObjectsIfMissing bool `json:"create_database_objects_if_missing,omitempty"` + ExistingPipelineId string `json:"existing_pipeline_id,omitempty"` + NewPipelineSpec *DataSourcePostgresSyncedTableSpecNewPipelineSpec `json:"new_pipeline_spec,omitempty"` + PostgresDatabase string `json:"postgres_database,omitempty"` + PrimaryKeyColumns []string `json:"primary_key_columns,omitempty"` + SchedulingPolicy string `json:"scheduling_policy,omitempty"` + SourceTableFullName string `json:"source_table_full_name,omitempty"` + TimeseriesKey string `json:"timeseries_key,omitempty"` +} + +type DataSourcePostgresSyncedTableStatusLastSyncDeltaTableSyncInfo struct { + DeltaCommitTime string `json:"delta_commit_time,omitempty"` + DeltaCommitVersion int `json:"delta_commit_version,omitempty"` +} + +type DataSourcePostgresSyncedTableStatusLastSync struct { + DeltaTableSyncInfo *DataSourcePostgresSyncedTableStatusLastSyncDeltaTableSyncInfo `json:"delta_table_sync_info,omitempty"` + SyncEndTime string `json:"sync_end_time,omitempty"` + SyncStartTime string `json:"sync_start_time,omitempty"` +} + +type DataSourcePostgresSyncedTableStatusOngoingSyncProgress struct { + EstimatedCompletionTimeSeconds int `json:"estimated_completion_time_seconds,omitempty"` + LatestVersionCurrentlyProcessing int `json:"latest_version_currently_processing,omitempty"` + SyncProgressCompletion int `json:"sync_progress_completion,omitempty"` + SyncedRowCount int `json:"synced_row_count,omitempty"` + TotalRowCount int `json:"total_row_count,omitempty"` +} + +type DataSourcePostgresSyncedTableStatus struct { + DetailedState string `json:"detailed_state,omitempty"` + LastProcessedCommitVersion int `json:"last_processed_commit_version,omitempty"` + LastSync *DataSourcePostgresSyncedTableStatusLastSync `json:"last_sync,omitempty"` + LastSyncTime string `json:"last_sync_time,omitempty"` + Message string `json:"message,omitempty"` + OngoingSyncProgress *DataSourcePostgresSyncedTableStatusOngoingSyncProgress `json:"ongoing_sync_progress,omitempty"` + PipelineId string `json:"pipeline_id,omitempty"` + ProvisioningPhase string `json:"provisioning_phase,omitempty"` + UnityCatalogProvisioningState string `json:"unity_catalog_provisioning_state,omitempty"` +} + +type DataSourcePostgresSyncedTable struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourcePostgresSyncedTableProviderConfig `json:"provider_config,omitempty"` + Spec *DataSourcePostgresSyncedTableSpec `json:"spec,omitempty"` + Status *DataSourcePostgresSyncedTableStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_user.go b/bundle/internal/tf/schema/data_source_user.go index d70694148b3..4fc7924aae9 100644 --- a/bundle/internal/tf/schema/data_source_user.go +++ b/bundle/internal/tf/schema/data_source_user.go @@ -10,6 +10,7 @@ type DataSourceUser struct { AclPrincipalId string `json:"acl_principal_id,omitempty"` Active bool `json:"active,omitempty"` Alphanumeric string `json:"alphanumeric,omitempty"` + Api string `json:"api,omitempty"` ApplicationId string `json:"application_id,omitempty"` DisplayName string `json:"display_name,omitempty"` ExternalId string `json:"external_id,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_users.go b/bundle/internal/tf/schema/data_source_users.go index 71b6059ac54..96326f7a291 100644 --- a/bundle/internal/tf/schema/data_source_users.go +++ b/bundle/internal/tf/schema/data_source_users.go @@ -2,6 +2,10 @@ package schema +type DataSourceUsersProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type DataSourceUsersUsersEmails struct { Display string `json:"display,omitempty"` Primary bool `json:"primary,omitempty"` @@ -54,7 +58,9 @@ type DataSourceUsersUsers struct { } type DataSourceUsers struct { - ExtraAttributes string `json:"extra_attributes,omitempty"` - Filter string `json:"filter,omitempty"` - Users []DataSourceUsersUsers `json:"users,omitempty"` + Api string `json:"api,omitempty"` + ExtraAttributes string `json:"extra_attributes,omitempty"` + Filter string `json:"filter,omitempty"` + ProviderConfig *DataSourceUsersProviderConfig `json:"provider_config,omitempty"` + Users []DataSourceUsersUsers `json:"users,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_workspace_setting_v2.go b/bundle/internal/tf/schema/data_source_workspace_setting_v2.go index 08673011250..f65716e9af8 100644 --- a/bundle/internal/tf/schema/data_source_workspace_setting_v2.go +++ b/bundle/internal/tf/schema/data_source_workspace_setting_v2.go @@ -93,7 +93,8 @@ type DataSourceWorkspaceSettingV2EffectivePersonalCompute struct { } type DataSourceWorkspaceSettingV2EffectiveRestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type DataSourceWorkspaceSettingV2EffectiveStringVal struct { @@ -113,7 +114,8 @@ type DataSourceWorkspaceSettingV2ProviderConfig struct { } type DataSourceWorkspaceSettingV2RestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type DataSourceWorkspaceSettingV2StringVal struct { diff --git a/bundle/internal/tf/schema/data_sources.go b/bundle/internal/tf/schema/data_sources.go index 0aecdaed4eb..8dde049fe16 100644 --- a/bundle/internal/tf/schema/data_sources.go +++ b/bundle/internal/tf/schema/data_sources.go @@ -3,271 +3,285 @@ package schema type DataSources struct { - AccountFederationPolicies map[string]any `json:"databricks_account_federation_policies,omitempty"` - AccountFederationPolicy map[string]any `json:"databricks_account_federation_policy,omitempty"` - AccountNetworkPolicies map[string]any `json:"databricks_account_network_policies,omitempty"` - AccountNetworkPolicy map[string]any `json:"databricks_account_network_policy,omitempty"` - AccountSettingUserPreferenceV2 map[string]any `json:"databricks_account_setting_user_preference_v2,omitempty"` - AccountSettingV2 map[string]any `json:"databricks_account_setting_v2,omitempty"` - AlertV2 map[string]any `json:"databricks_alert_v2,omitempty"` - AlertsV2 map[string]any `json:"databricks_alerts_v2,omitempty"` - App map[string]any `json:"databricks_app,omitempty"` - AppSpace map[string]any `json:"databricks_app_space,omitempty"` - AppSpaces map[string]any `json:"databricks_app_spaces,omitempty"` - Apps map[string]any `json:"databricks_apps,omitempty"` - AppsSettingsCustomTemplate map[string]any `json:"databricks_apps_settings_custom_template,omitempty"` - AppsSettingsCustomTemplates map[string]any `json:"databricks_apps_settings_custom_templates,omitempty"` - AwsAssumeRolePolicy map[string]any `json:"databricks_aws_assume_role_policy,omitempty"` - AwsBucketPolicy map[string]any `json:"databricks_aws_bucket_policy,omitempty"` - AwsCrossaccountPolicy map[string]any `json:"databricks_aws_crossaccount_policy,omitempty"` - AwsUnityCatalogAssumeRolePolicy map[string]any `json:"databricks_aws_unity_catalog_assume_role_policy,omitempty"` - AwsUnityCatalogPolicy map[string]any `json:"databricks_aws_unity_catalog_policy,omitempty"` - BudgetPolicies map[string]any `json:"databricks_budget_policies,omitempty"` - BudgetPolicy map[string]any `json:"databricks_budget_policy,omitempty"` - Catalog map[string]any `json:"databricks_catalog,omitempty"` - Catalogs map[string]any `json:"databricks_catalogs,omitempty"` - Cluster map[string]any `json:"databricks_cluster,omitempty"` - ClusterPolicy map[string]any `json:"databricks_cluster_policy,omitempty"` - Clusters map[string]any `json:"databricks_clusters,omitempty"` - CurrentConfig map[string]any `json:"databricks_current_config,omitempty"` - CurrentMetastore map[string]any `json:"databricks_current_metastore,omitempty"` - CurrentUser map[string]any `json:"databricks_current_user,omitempty"` - Dashboards map[string]any `json:"databricks_dashboards,omitempty"` - DataClassificationCatalogConfig map[string]any `json:"databricks_data_classification_catalog_config,omitempty"` - DataQualityMonitor map[string]any `json:"databricks_data_quality_monitor,omitempty"` - DataQualityMonitors map[string]any `json:"databricks_data_quality_monitors,omitempty"` - DataQualityRefresh map[string]any `json:"databricks_data_quality_refresh,omitempty"` - DataQualityRefreshes map[string]any `json:"databricks_data_quality_refreshes,omitempty"` - DatabaseDatabaseCatalog map[string]any `json:"databricks_database_database_catalog,omitempty"` - DatabaseDatabaseCatalogs map[string]any `json:"databricks_database_database_catalogs,omitempty"` - DatabaseInstance map[string]any `json:"databricks_database_instance,omitempty"` - DatabaseInstances map[string]any `json:"databricks_database_instances,omitempty"` - DatabaseSyncedDatabaseTable map[string]any `json:"databricks_database_synced_database_table,omitempty"` - DatabaseSyncedDatabaseTables map[string]any `json:"databricks_database_synced_database_tables,omitempty"` - DbfsFile map[string]any `json:"databricks_dbfs_file,omitempty"` - DbfsFilePaths map[string]any `json:"databricks_dbfs_file_paths,omitempty"` - Directory map[string]any `json:"databricks_directory,omitempty"` - Endpoint map[string]any `json:"databricks_endpoint,omitempty"` - Endpoints map[string]any `json:"databricks_endpoints,omitempty"` - EntityTagAssignment map[string]any `json:"databricks_entity_tag_assignment,omitempty"` - EntityTagAssignments map[string]any `json:"databricks_entity_tag_assignments,omitempty"` - ExternalLocation map[string]any `json:"databricks_external_location,omitempty"` - ExternalLocations map[string]any `json:"databricks_external_locations,omitempty"` - ExternalMetadata map[string]any `json:"databricks_external_metadata,omitempty"` - ExternalMetadatas map[string]any `json:"databricks_external_metadatas,omitempty"` - FeatureEngineeringFeature map[string]any `json:"databricks_feature_engineering_feature,omitempty"` - FeatureEngineeringFeatures map[string]any `json:"databricks_feature_engineering_features,omitempty"` - FeatureEngineeringKafkaConfig map[string]any `json:"databricks_feature_engineering_kafka_config,omitempty"` - FeatureEngineeringKafkaConfigs map[string]any `json:"databricks_feature_engineering_kafka_configs,omitempty"` - FeatureEngineeringMaterializedFeature map[string]any `json:"databricks_feature_engineering_materialized_feature,omitempty"` - FeatureEngineeringMaterializedFeatures map[string]any `json:"databricks_feature_engineering_materialized_features,omitempty"` - Functions map[string]any `json:"databricks_functions,omitempty"` - Group map[string]any `json:"databricks_group,omitempty"` - InstancePool map[string]any `json:"databricks_instance_pool,omitempty"` - InstanceProfiles map[string]any `json:"databricks_instance_profiles,omitempty"` - Job map[string]any `json:"databricks_job,omitempty"` - Jobs map[string]any `json:"databricks_jobs,omitempty"` - KnowledgeAssistant map[string]any `json:"databricks_knowledge_assistant,omitempty"` - KnowledgeAssistantKnowledgeSource map[string]any `json:"databricks_knowledge_assistant_knowledge_source,omitempty"` - KnowledgeAssistantKnowledgeSources map[string]any `json:"databricks_knowledge_assistant_knowledge_sources,omitempty"` - KnowledgeAssistants map[string]any `json:"databricks_knowledge_assistants,omitempty"` - MaterializedFeaturesFeatureTag map[string]any `json:"databricks_materialized_features_feature_tag,omitempty"` - MaterializedFeaturesFeatureTags map[string]any `json:"databricks_materialized_features_feature_tags,omitempty"` - Metastore map[string]any `json:"databricks_metastore,omitempty"` - Metastores map[string]any `json:"databricks_metastores,omitempty"` - MlflowExperiment map[string]any `json:"databricks_mlflow_experiment,omitempty"` - MlflowModel map[string]any `json:"databricks_mlflow_model,omitempty"` - MlflowModels map[string]any `json:"databricks_mlflow_models,omitempty"` - MwsCredentials map[string]any `json:"databricks_mws_credentials,omitempty"` - MwsNetworkConnectivityConfig map[string]any `json:"databricks_mws_network_connectivity_config,omitempty"` - MwsNetworkConnectivityConfigs map[string]any `json:"databricks_mws_network_connectivity_configs,omitempty"` - MwsWorkspaces map[string]any `json:"databricks_mws_workspaces,omitempty"` - NodeType map[string]any `json:"databricks_node_type,omitempty"` - Notebook map[string]any `json:"databricks_notebook,omitempty"` - NotebookPaths map[string]any `json:"databricks_notebook_paths,omitempty"` - NotificationDestinations map[string]any `json:"databricks_notification_destinations,omitempty"` - OnlineStore map[string]any `json:"databricks_online_store,omitempty"` - OnlineStores map[string]any `json:"databricks_online_stores,omitempty"` - Pipelines map[string]any `json:"databricks_pipelines,omitempty"` - PolicyInfo map[string]any `json:"databricks_policy_info,omitempty"` - PolicyInfos map[string]any `json:"databricks_policy_infos,omitempty"` - PostgresBranch map[string]any `json:"databricks_postgres_branch,omitempty"` - PostgresBranches map[string]any `json:"databricks_postgres_branches,omitempty"` - PostgresDatabase map[string]any `json:"databricks_postgres_database,omitempty"` - PostgresDatabases map[string]any `json:"databricks_postgres_databases,omitempty"` - PostgresEndpoint map[string]any `json:"databricks_postgres_endpoint,omitempty"` - PostgresEndpoints map[string]any `json:"databricks_postgres_endpoints,omitempty"` - PostgresProject map[string]any `json:"databricks_postgres_project,omitempty"` - PostgresProjects map[string]any `json:"databricks_postgres_projects,omitempty"` - QualityMonitorV2 map[string]any `json:"databricks_quality_monitor_v2,omitempty"` - QualityMonitorsV2 map[string]any `json:"databricks_quality_monitors_v2,omitempty"` - RegisteredModel map[string]any `json:"databricks_registered_model,omitempty"` - RegisteredModelVersions map[string]any `json:"databricks_registered_model_versions,omitempty"` - RfaAccessRequestDestinations map[string]any `json:"databricks_rfa_access_request_destinations,omitempty"` - Schema map[string]any `json:"databricks_schema,omitempty"` - Schemas map[string]any `json:"databricks_schemas,omitempty"` - ServicePrincipal map[string]any `json:"databricks_service_principal,omitempty"` - ServicePrincipalFederationPolicies map[string]any `json:"databricks_service_principal_federation_policies,omitempty"` - ServicePrincipalFederationPolicy map[string]any `json:"databricks_service_principal_federation_policy,omitempty"` - ServicePrincipals map[string]any `json:"databricks_service_principals,omitempty"` - ServingEndpoints map[string]any `json:"databricks_serving_endpoints,omitempty"` - Share map[string]any `json:"databricks_share,omitempty"` - Shares map[string]any `json:"databricks_shares,omitempty"` - SparkVersion map[string]any `json:"databricks_spark_version,omitempty"` - SqlWarehouse map[string]any `json:"databricks_sql_warehouse,omitempty"` - SqlWarehouses map[string]any `json:"databricks_sql_warehouses,omitempty"` - StorageCredential map[string]any `json:"databricks_storage_credential,omitempty"` - StorageCredentials map[string]any `json:"databricks_storage_credentials,omitempty"` - Table map[string]any `json:"databricks_table,omitempty"` - Tables map[string]any `json:"databricks_tables,omitempty"` - TagPolicies map[string]any `json:"databricks_tag_policies,omitempty"` - TagPolicy map[string]any `json:"databricks_tag_policy,omitempty"` - User map[string]any `json:"databricks_user,omitempty"` - Users map[string]any `json:"databricks_users,omitempty"` - Views map[string]any `json:"databricks_views,omitempty"` - Volume map[string]any `json:"databricks_volume,omitempty"` - Volumes map[string]any `json:"databricks_volumes,omitempty"` - WarehousesDefaultWarehouseOverride map[string]any `json:"databricks_warehouses_default_warehouse_override,omitempty"` - WarehousesDefaultWarehouseOverrides map[string]any `json:"databricks_warehouses_default_warehouse_overrides,omitempty"` - WorkspaceEntityTagAssignment map[string]any `json:"databricks_workspace_entity_tag_assignment,omitempty"` - WorkspaceEntityTagAssignments map[string]any `json:"databricks_workspace_entity_tag_assignments,omitempty"` - WorkspaceNetworkOption map[string]any `json:"databricks_workspace_network_option,omitempty"` - WorkspaceSettingV2 map[string]any `json:"databricks_workspace_setting_v2,omitempty"` - Zones map[string]any `json:"databricks_zones,omitempty"` + AccountFederationPolicies map[string]any `json:"databricks_account_federation_policies,omitempty"` + AccountFederationPolicy map[string]any `json:"databricks_account_federation_policy,omitempty"` + AccountNetworkPolicies map[string]any `json:"databricks_account_network_policies,omitempty"` + AccountNetworkPolicy map[string]any `json:"databricks_account_network_policy,omitempty"` + AccountSettingUserPreferenceV2 map[string]any `json:"databricks_account_setting_user_preference_v2,omitempty"` + AccountSettingV2 map[string]any `json:"databricks_account_setting_v2,omitempty"` + AlertV2 map[string]any `json:"databricks_alert_v2,omitempty"` + AlertsV2 map[string]any `json:"databricks_alerts_v2,omitempty"` + App map[string]any `json:"databricks_app,omitempty"` + AppSpace map[string]any `json:"databricks_app_space,omitempty"` + AppSpaces map[string]any `json:"databricks_app_spaces,omitempty"` + Apps map[string]any `json:"databricks_apps,omitempty"` + AppsSettingsCustomTemplate map[string]any `json:"databricks_apps_settings_custom_template,omitempty"` + AppsSettingsCustomTemplates map[string]any `json:"databricks_apps_settings_custom_templates,omitempty"` + AwsAssumeRolePolicy map[string]any `json:"databricks_aws_assume_role_policy,omitempty"` + AwsBucketPolicy map[string]any `json:"databricks_aws_bucket_policy,omitempty"` + AwsCrossaccountPolicy map[string]any `json:"databricks_aws_crossaccount_policy,omitempty"` + AwsUnityCatalogAssumeRolePolicy map[string]any `json:"databricks_aws_unity_catalog_assume_role_policy,omitempty"` + AwsUnityCatalogPolicy map[string]any `json:"databricks_aws_unity_catalog_policy,omitempty"` + BudgetPolicies map[string]any `json:"databricks_budget_policies,omitempty"` + BudgetPolicy map[string]any `json:"databricks_budget_policy,omitempty"` + Catalog map[string]any `json:"databricks_catalog,omitempty"` + Catalogs map[string]any `json:"databricks_catalogs,omitempty"` + Cluster map[string]any `json:"databricks_cluster,omitempty"` + ClusterPolicy map[string]any `json:"databricks_cluster_policy,omitempty"` + Clusters map[string]any `json:"databricks_clusters,omitempty"` + CurrentConfig map[string]any `json:"databricks_current_config,omitempty"` + CurrentMetastore map[string]any `json:"databricks_current_metastore,omitempty"` + CurrentUser map[string]any `json:"databricks_current_user,omitempty"` + Dashboards map[string]any `json:"databricks_dashboards,omitempty"` + DataClassificationCatalogConfig map[string]any `json:"databricks_data_classification_catalog_config,omitempty"` + DataQualityMonitor map[string]any `json:"databricks_data_quality_monitor,omitempty"` + DataQualityMonitors map[string]any `json:"databricks_data_quality_monitors,omitempty"` + DataQualityRefresh map[string]any `json:"databricks_data_quality_refresh,omitempty"` + DataQualityRefreshes map[string]any `json:"databricks_data_quality_refreshes,omitempty"` + DatabaseDatabaseCatalog map[string]any `json:"databricks_database_database_catalog,omitempty"` + DatabaseDatabaseCatalogs map[string]any `json:"databricks_database_database_catalogs,omitempty"` + DatabaseInstance map[string]any `json:"databricks_database_instance,omitempty"` + DatabaseInstances map[string]any `json:"databricks_database_instances,omitempty"` + DatabaseSyncedDatabaseTable map[string]any `json:"databricks_database_synced_database_table,omitempty"` + DatabaseSyncedDatabaseTables map[string]any `json:"databricks_database_synced_database_tables,omitempty"` + DbfsFile map[string]any `json:"databricks_dbfs_file,omitempty"` + DbfsFilePaths map[string]any `json:"databricks_dbfs_file_paths,omitempty"` + Directory map[string]any `json:"databricks_directory,omitempty"` + Endpoint map[string]any `json:"databricks_endpoint,omitempty"` + Endpoints map[string]any `json:"databricks_endpoints,omitempty"` + EntityTagAssignment map[string]any `json:"databricks_entity_tag_assignment,omitempty"` + EntityTagAssignments map[string]any `json:"databricks_entity_tag_assignments,omitempty"` + EnvironmentsDefaultWorkspaceBaseEnvironment map[string]any `json:"databricks_environments_default_workspace_base_environment,omitempty"` + EnvironmentsWorkspaceBaseEnvironment map[string]any `json:"databricks_environments_workspace_base_environment,omitempty"` + EnvironmentsWorkspaceBaseEnvironments map[string]any `json:"databricks_environments_workspace_base_environments,omitempty"` + ExternalLocation map[string]any `json:"databricks_external_location,omitempty"` + ExternalLocations map[string]any `json:"databricks_external_locations,omitempty"` + ExternalMetadata map[string]any `json:"databricks_external_metadata,omitempty"` + ExternalMetadatas map[string]any `json:"databricks_external_metadatas,omitempty"` + FeatureEngineeringFeature map[string]any `json:"databricks_feature_engineering_feature,omitempty"` + FeatureEngineeringFeatures map[string]any `json:"databricks_feature_engineering_features,omitempty"` + FeatureEngineeringKafkaConfig map[string]any `json:"databricks_feature_engineering_kafka_config,omitempty"` + FeatureEngineeringKafkaConfigs map[string]any `json:"databricks_feature_engineering_kafka_configs,omitempty"` + FeatureEngineeringMaterializedFeature map[string]any `json:"databricks_feature_engineering_materialized_feature,omitempty"` + FeatureEngineeringMaterializedFeatures map[string]any `json:"databricks_feature_engineering_materialized_features,omitempty"` + Functions map[string]any `json:"databricks_functions,omitempty"` + Group map[string]any `json:"databricks_group,omitempty"` + InstancePool map[string]any `json:"databricks_instance_pool,omitempty"` + InstanceProfiles map[string]any `json:"databricks_instance_profiles,omitempty"` + Job map[string]any `json:"databricks_job,omitempty"` + Jobs map[string]any `json:"databricks_jobs,omitempty"` + KnowledgeAssistant map[string]any `json:"databricks_knowledge_assistant,omitempty"` + KnowledgeAssistantKnowledgeSource map[string]any `json:"databricks_knowledge_assistant_knowledge_source,omitempty"` + KnowledgeAssistantKnowledgeSources map[string]any `json:"databricks_knowledge_assistant_knowledge_sources,omitempty"` + KnowledgeAssistants map[string]any `json:"databricks_knowledge_assistants,omitempty"` + MaterializedFeaturesFeatureTag map[string]any `json:"databricks_materialized_features_feature_tag,omitempty"` + MaterializedFeaturesFeatureTags map[string]any `json:"databricks_materialized_features_feature_tags,omitempty"` + Metastore map[string]any `json:"databricks_metastore,omitempty"` + Metastores map[string]any `json:"databricks_metastores,omitempty"` + MlflowExperiment map[string]any `json:"databricks_mlflow_experiment,omitempty"` + MlflowModel map[string]any `json:"databricks_mlflow_model,omitempty"` + MlflowModels map[string]any `json:"databricks_mlflow_models,omitempty"` + MwsCredentials map[string]any `json:"databricks_mws_credentials,omitempty"` + MwsNetworkConnectivityConfig map[string]any `json:"databricks_mws_network_connectivity_config,omitempty"` + MwsNetworkConnectivityConfigs map[string]any `json:"databricks_mws_network_connectivity_configs,omitempty"` + MwsWorkspaces map[string]any `json:"databricks_mws_workspaces,omitempty"` + NodeType map[string]any `json:"databricks_node_type,omitempty"` + Notebook map[string]any `json:"databricks_notebook,omitempty"` + NotebookPaths map[string]any `json:"databricks_notebook_paths,omitempty"` + NotificationDestinations map[string]any `json:"databricks_notification_destinations,omitempty"` + OnlineStore map[string]any `json:"databricks_online_store,omitempty"` + OnlineStores map[string]any `json:"databricks_online_stores,omitempty"` + Pipelines map[string]any `json:"databricks_pipelines,omitempty"` + PolicyInfo map[string]any `json:"databricks_policy_info,omitempty"` + PolicyInfos map[string]any `json:"databricks_policy_infos,omitempty"` + PostgresBranch map[string]any `json:"databricks_postgres_branch,omitempty"` + PostgresBranches map[string]any `json:"databricks_postgres_branches,omitempty"` + PostgresCatalog map[string]any `json:"databricks_postgres_catalog,omitempty"` + PostgresDatabase map[string]any `json:"databricks_postgres_database,omitempty"` + PostgresDatabases map[string]any `json:"databricks_postgres_databases,omitempty"` + PostgresEndpoint map[string]any `json:"databricks_postgres_endpoint,omitempty"` + PostgresEndpoints map[string]any `json:"databricks_postgres_endpoints,omitempty"` + PostgresProject map[string]any `json:"databricks_postgres_project,omitempty"` + PostgresProjects map[string]any `json:"databricks_postgres_projects,omitempty"` + PostgresRole map[string]any `json:"databricks_postgres_role,omitempty"` + PostgresRoles map[string]any `json:"databricks_postgres_roles,omitempty"` + PostgresSyncedTable map[string]any `json:"databricks_postgres_synced_table,omitempty"` + QualityMonitorV2 map[string]any `json:"databricks_quality_monitor_v2,omitempty"` + QualityMonitorsV2 map[string]any `json:"databricks_quality_monitors_v2,omitempty"` + RegisteredModel map[string]any `json:"databricks_registered_model,omitempty"` + RegisteredModelVersions map[string]any `json:"databricks_registered_model_versions,omitempty"` + RfaAccessRequestDestinations map[string]any `json:"databricks_rfa_access_request_destinations,omitempty"` + Schema map[string]any `json:"databricks_schema,omitempty"` + Schemas map[string]any `json:"databricks_schemas,omitempty"` + ServicePrincipal map[string]any `json:"databricks_service_principal,omitempty"` + ServicePrincipalFederationPolicies map[string]any `json:"databricks_service_principal_federation_policies,omitempty"` + ServicePrincipalFederationPolicy map[string]any `json:"databricks_service_principal_federation_policy,omitempty"` + ServicePrincipals map[string]any `json:"databricks_service_principals,omitempty"` + ServingEndpoints map[string]any `json:"databricks_serving_endpoints,omitempty"` + Share map[string]any `json:"databricks_share,omitempty"` + Shares map[string]any `json:"databricks_shares,omitempty"` + SparkVersion map[string]any `json:"databricks_spark_version,omitempty"` + SqlWarehouse map[string]any `json:"databricks_sql_warehouse,omitempty"` + SqlWarehouses map[string]any `json:"databricks_sql_warehouses,omitempty"` + StorageCredential map[string]any `json:"databricks_storage_credential,omitempty"` + StorageCredentials map[string]any `json:"databricks_storage_credentials,omitempty"` + Table map[string]any `json:"databricks_table,omitempty"` + Tables map[string]any `json:"databricks_tables,omitempty"` + TagPolicies map[string]any `json:"databricks_tag_policies,omitempty"` + TagPolicy map[string]any `json:"databricks_tag_policy,omitempty"` + User map[string]any `json:"databricks_user,omitempty"` + Users map[string]any `json:"databricks_users,omitempty"` + Views map[string]any `json:"databricks_views,omitempty"` + Volume map[string]any `json:"databricks_volume,omitempty"` + Volumes map[string]any `json:"databricks_volumes,omitempty"` + WarehousesDefaultWarehouseOverride map[string]any `json:"databricks_warehouses_default_warehouse_override,omitempty"` + WarehousesDefaultWarehouseOverrides map[string]any `json:"databricks_warehouses_default_warehouse_overrides,omitempty"` + WorkspaceEntityTagAssignment map[string]any `json:"databricks_workspace_entity_tag_assignment,omitempty"` + WorkspaceEntityTagAssignments map[string]any `json:"databricks_workspace_entity_tag_assignments,omitempty"` + WorkspaceNetworkOption map[string]any `json:"databricks_workspace_network_option,omitempty"` + WorkspaceSettingV2 map[string]any `json:"databricks_workspace_setting_v2,omitempty"` + Zones map[string]any `json:"databricks_zones,omitempty"` } func NewDataSources() *DataSources { return &DataSources{ - AccountFederationPolicies: make(map[string]any), - AccountFederationPolicy: make(map[string]any), - AccountNetworkPolicies: make(map[string]any), - AccountNetworkPolicy: make(map[string]any), - AccountSettingUserPreferenceV2: make(map[string]any), - AccountSettingV2: make(map[string]any), - AlertV2: make(map[string]any), - AlertsV2: make(map[string]any), - App: make(map[string]any), - AppSpace: make(map[string]any), - AppSpaces: make(map[string]any), - Apps: make(map[string]any), - AppsSettingsCustomTemplate: make(map[string]any), - AppsSettingsCustomTemplates: make(map[string]any), - AwsAssumeRolePolicy: make(map[string]any), - AwsBucketPolicy: make(map[string]any), - AwsCrossaccountPolicy: make(map[string]any), - AwsUnityCatalogAssumeRolePolicy: make(map[string]any), - AwsUnityCatalogPolicy: make(map[string]any), - BudgetPolicies: make(map[string]any), - BudgetPolicy: make(map[string]any), - Catalog: make(map[string]any), - Catalogs: make(map[string]any), - Cluster: make(map[string]any), - ClusterPolicy: make(map[string]any), - Clusters: make(map[string]any), - CurrentConfig: make(map[string]any), - CurrentMetastore: make(map[string]any), - CurrentUser: make(map[string]any), - Dashboards: make(map[string]any), - DataClassificationCatalogConfig: make(map[string]any), - DataQualityMonitor: make(map[string]any), - DataQualityMonitors: make(map[string]any), - DataQualityRefresh: make(map[string]any), - DataQualityRefreshes: make(map[string]any), - DatabaseDatabaseCatalog: make(map[string]any), - DatabaseDatabaseCatalogs: make(map[string]any), - DatabaseInstance: make(map[string]any), - DatabaseInstances: make(map[string]any), - DatabaseSyncedDatabaseTable: make(map[string]any), - DatabaseSyncedDatabaseTables: make(map[string]any), - DbfsFile: make(map[string]any), - DbfsFilePaths: make(map[string]any), - Directory: make(map[string]any), - Endpoint: make(map[string]any), - Endpoints: make(map[string]any), - EntityTagAssignment: make(map[string]any), - EntityTagAssignments: make(map[string]any), - ExternalLocation: make(map[string]any), - ExternalLocations: make(map[string]any), - ExternalMetadata: make(map[string]any), - ExternalMetadatas: make(map[string]any), - FeatureEngineeringFeature: make(map[string]any), - FeatureEngineeringFeatures: make(map[string]any), - FeatureEngineeringKafkaConfig: make(map[string]any), - FeatureEngineeringKafkaConfigs: make(map[string]any), - FeatureEngineeringMaterializedFeature: make(map[string]any), - FeatureEngineeringMaterializedFeatures: make(map[string]any), - Functions: make(map[string]any), - Group: make(map[string]any), - InstancePool: make(map[string]any), - InstanceProfiles: make(map[string]any), - Job: make(map[string]any), - Jobs: make(map[string]any), - KnowledgeAssistant: make(map[string]any), - KnowledgeAssistantKnowledgeSource: make(map[string]any), - KnowledgeAssistantKnowledgeSources: make(map[string]any), - KnowledgeAssistants: make(map[string]any), - MaterializedFeaturesFeatureTag: make(map[string]any), - MaterializedFeaturesFeatureTags: make(map[string]any), - Metastore: make(map[string]any), - Metastores: make(map[string]any), - MlflowExperiment: make(map[string]any), - MlflowModel: make(map[string]any), - MlflowModels: make(map[string]any), - MwsCredentials: make(map[string]any), - MwsNetworkConnectivityConfig: make(map[string]any), - MwsNetworkConnectivityConfigs: make(map[string]any), - MwsWorkspaces: make(map[string]any), - NodeType: make(map[string]any), - Notebook: make(map[string]any), - NotebookPaths: make(map[string]any), - NotificationDestinations: make(map[string]any), - OnlineStore: make(map[string]any), - OnlineStores: make(map[string]any), - Pipelines: make(map[string]any), - PolicyInfo: make(map[string]any), - PolicyInfos: make(map[string]any), - PostgresBranch: make(map[string]any), - PostgresBranches: make(map[string]any), - PostgresDatabase: make(map[string]any), - PostgresDatabases: make(map[string]any), - PostgresEndpoint: make(map[string]any), - PostgresEndpoints: make(map[string]any), - PostgresProject: make(map[string]any), - PostgresProjects: make(map[string]any), - QualityMonitorV2: make(map[string]any), - QualityMonitorsV2: make(map[string]any), - RegisteredModel: make(map[string]any), - RegisteredModelVersions: make(map[string]any), - RfaAccessRequestDestinations: make(map[string]any), - Schema: make(map[string]any), - Schemas: make(map[string]any), - ServicePrincipal: make(map[string]any), - ServicePrincipalFederationPolicies: make(map[string]any), - ServicePrincipalFederationPolicy: make(map[string]any), - ServicePrincipals: make(map[string]any), - ServingEndpoints: make(map[string]any), - Share: make(map[string]any), - Shares: make(map[string]any), - SparkVersion: make(map[string]any), - SqlWarehouse: make(map[string]any), - SqlWarehouses: make(map[string]any), - StorageCredential: make(map[string]any), - StorageCredentials: make(map[string]any), - Table: make(map[string]any), - Tables: make(map[string]any), - TagPolicies: make(map[string]any), - TagPolicy: make(map[string]any), - User: make(map[string]any), - Users: make(map[string]any), - Views: make(map[string]any), - Volume: make(map[string]any), - Volumes: make(map[string]any), - WarehousesDefaultWarehouseOverride: make(map[string]any), - WarehousesDefaultWarehouseOverrides: make(map[string]any), - WorkspaceEntityTagAssignment: make(map[string]any), - WorkspaceEntityTagAssignments: make(map[string]any), - WorkspaceNetworkOption: make(map[string]any), - WorkspaceSettingV2: make(map[string]any), - Zones: make(map[string]any), + AccountFederationPolicies: make(map[string]any), + AccountFederationPolicy: make(map[string]any), + AccountNetworkPolicies: make(map[string]any), + AccountNetworkPolicy: make(map[string]any), + AccountSettingUserPreferenceV2: make(map[string]any), + AccountSettingV2: make(map[string]any), + AlertV2: make(map[string]any), + AlertsV2: make(map[string]any), + App: make(map[string]any), + AppSpace: make(map[string]any), + AppSpaces: make(map[string]any), + Apps: make(map[string]any), + AppsSettingsCustomTemplate: make(map[string]any), + AppsSettingsCustomTemplates: make(map[string]any), + AwsAssumeRolePolicy: make(map[string]any), + AwsBucketPolicy: make(map[string]any), + AwsCrossaccountPolicy: make(map[string]any), + AwsUnityCatalogAssumeRolePolicy: make(map[string]any), + AwsUnityCatalogPolicy: make(map[string]any), + BudgetPolicies: make(map[string]any), + BudgetPolicy: make(map[string]any), + Catalog: make(map[string]any), + Catalogs: make(map[string]any), + Cluster: make(map[string]any), + ClusterPolicy: make(map[string]any), + Clusters: make(map[string]any), + CurrentConfig: make(map[string]any), + CurrentMetastore: make(map[string]any), + CurrentUser: make(map[string]any), + Dashboards: make(map[string]any), + DataClassificationCatalogConfig: make(map[string]any), + DataQualityMonitor: make(map[string]any), + DataQualityMonitors: make(map[string]any), + DataQualityRefresh: make(map[string]any), + DataQualityRefreshes: make(map[string]any), + DatabaseDatabaseCatalog: make(map[string]any), + DatabaseDatabaseCatalogs: make(map[string]any), + DatabaseInstance: make(map[string]any), + DatabaseInstances: make(map[string]any), + DatabaseSyncedDatabaseTable: make(map[string]any), + DatabaseSyncedDatabaseTables: make(map[string]any), + DbfsFile: make(map[string]any), + DbfsFilePaths: make(map[string]any), + Directory: make(map[string]any), + Endpoint: make(map[string]any), + Endpoints: make(map[string]any), + EntityTagAssignment: make(map[string]any), + EntityTagAssignments: make(map[string]any), + EnvironmentsDefaultWorkspaceBaseEnvironment: make(map[string]any), + EnvironmentsWorkspaceBaseEnvironment: make(map[string]any), + EnvironmentsWorkspaceBaseEnvironments: make(map[string]any), + ExternalLocation: make(map[string]any), + ExternalLocations: make(map[string]any), + ExternalMetadata: make(map[string]any), + ExternalMetadatas: make(map[string]any), + FeatureEngineeringFeature: make(map[string]any), + FeatureEngineeringFeatures: make(map[string]any), + FeatureEngineeringKafkaConfig: make(map[string]any), + FeatureEngineeringKafkaConfigs: make(map[string]any), + FeatureEngineeringMaterializedFeature: make(map[string]any), + FeatureEngineeringMaterializedFeatures: make(map[string]any), + Functions: make(map[string]any), + Group: make(map[string]any), + InstancePool: make(map[string]any), + InstanceProfiles: make(map[string]any), + Job: make(map[string]any), + Jobs: make(map[string]any), + KnowledgeAssistant: make(map[string]any), + KnowledgeAssistantKnowledgeSource: make(map[string]any), + KnowledgeAssistantKnowledgeSources: make(map[string]any), + KnowledgeAssistants: make(map[string]any), + MaterializedFeaturesFeatureTag: make(map[string]any), + MaterializedFeaturesFeatureTags: make(map[string]any), + Metastore: make(map[string]any), + Metastores: make(map[string]any), + MlflowExperiment: make(map[string]any), + MlflowModel: make(map[string]any), + MlflowModels: make(map[string]any), + MwsCredentials: make(map[string]any), + MwsNetworkConnectivityConfig: make(map[string]any), + MwsNetworkConnectivityConfigs: make(map[string]any), + MwsWorkspaces: make(map[string]any), + NodeType: make(map[string]any), + Notebook: make(map[string]any), + NotebookPaths: make(map[string]any), + NotificationDestinations: make(map[string]any), + OnlineStore: make(map[string]any), + OnlineStores: make(map[string]any), + Pipelines: make(map[string]any), + PolicyInfo: make(map[string]any), + PolicyInfos: make(map[string]any), + PostgresBranch: make(map[string]any), + PostgresBranches: make(map[string]any), + PostgresCatalog: make(map[string]any), + PostgresDatabase: make(map[string]any), + PostgresDatabases: make(map[string]any), + PostgresEndpoint: make(map[string]any), + PostgresEndpoints: make(map[string]any), + PostgresProject: make(map[string]any), + PostgresProjects: make(map[string]any), + PostgresRole: make(map[string]any), + PostgresRoles: make(map[string]any), + PostgresSyncedTable: make(map[string]any), + QualityMonitorV2: make(map[string]any), + QualityMonitorsV2: make(map[string]any), + RegisteredModel: make(map[string]any), + RegisteredModelVersions: make(map[string]any), + RfaAccessRequestDestinations: make(map[string]any), + Schema: make(map[string]any), + Schemas: make(map[string]any), + ServicePrincipal: make(map[string]any), + ServicePrincipalFederationPolicies: make(map[string]any), + ServicePrincipalFederationPolicy: make(map[string]any), + ServicePrincipals: make(map[string]any), + ServingEndpoints: make(map[string]any), + Share: make(map[string]any), + Shares: make(map[string]any), + SparkVersion: make(map[string]any), + SqlWarehouse: make(map[string]any), + SqlWarehouses: make(map[string]any), + StorageCredential: make(map[string]any), + StorageCredentials: make(map[string]any), + Table: make(map[string]any), + Tables: make(map[string]any), + TagPolicies: make(map[string]any), + TagPolicy: make(map[string]any), + User: make(map[string]any), + Users: make(map[string]any), + Views: make(map[string]any), + Volume: make(map[string]any), + Volumes: make(map[string]any), + WarehousesDefaultWarehouseOverride: make(map[string]any), + WarehousesDefaultWarehouseOverrides: make(map[string]any), + WorkspaceEntityTagAssignment: make(map[string]any), + WorkspaceEntityTagAssignments: make(map[string]any), + WorkspaceNetworkOption: make(map[string]any), + WorkspaceSettingV2: make(map[string]any), + Zones: make(map[string]any), } } diff --git a/bundle/internal/tf/schema/resource_access_control_rule_set.go b/bundle/internal/tf/schema/resource_access_control_rule_set.go index 775c0708bdb..10f84ef066f 100644 --- a/bundle/internal/tf/schema/resource_access_control_rule_set.go +++ b/bundle/internal/tf/schema/resource_access_control_rule_set.go @@ -7,9 +7,15 @@ type ResourceAccessControlRuleSetGrantRules struct { Role string `json:"role"` } +type ResourceAccessControlRuleSetProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceAccessControlRuleSet struct { - Etag string `json:"etag,omitempty"` - Id string `json:"id,omitempty"` - Name string `json:"name"` - GrantRules []ResourceAccessControlRuleSetGrantRules `json:"grant_rules,omitempty"` + Api string `json:"api,omitempty"` + Etag string `json:"etag,omitempty"` + Id string `json:"id,omitempty"` + Name string `json:"name"` + GrantRules []ResourceAccessControlRuleSetGrantRules `json:"grant_rules,omitempty"` + ProviderConfig *ResourceAccessControlRuleSetProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_account_network_policy.go b/bundle/internal/tf/schema/resource_account_network_policy.go index 8ed2f25007b..9cb5c8524dc 100644 --- a/bundle/internal/tf/schema/resource_account_network_policy.go +++ b/bundle/internal/tf/schema/resource_account_network_policy.go @@ -31,8 +31,210 @@ type ResourceAccountNetworkPolicyEgress struct { NetworkAccess *ResourceAccountNetworkPolicyEgressNetworkAccess `json:"network_access,omitempty"` } +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRules struct { + Authentication *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRules struct { + Authentication *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccess struct { + AllowRules []ResourceAccountNetworkPolicyIngressPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []ResourceAccountNetworkPolicyIngressPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type ResourceAccountNetworkPolicyIngress struct { + PublicAccess *ResourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRules struct { + Authentication *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination struct { + AllDestinations bool `json:"all_destinations,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges struct { + IpRanges []string `json:"ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOrigin struct { + AllIpRanges bool `json:"all_ip_ranges,omitempty"` + ExcludedIpRanges *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges `json:"excluded_ip_ranges,omitempty"` + IncludedIpRanges *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginIncludedIpRanges `json:"included_ip_ranges,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRules struct { + Authentication *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccess struct { + AllowRules []ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + +type ResourceAccountNetworkPolicyIngressDryRun struct { + PublicAccess *ResourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` +} + type ResourceAccountNetworkPolicy struct { - AccountId string `json:"account_id,omitempty"` - Egress *ResourceAccountNetworkPolicyEgress `json:"egress,omitempty"` - NetworkPolicyId string `json:"network_policy_id,omitempty"` + AccountId string `json:"account_id,omitempty"` + Egress *ResourceAccountNetworkPolicyEgress `json:"egress,omitempty"` + Ingress *ResourceAccountNetworkPolicyIngress `json:"ingress,omitempty"` + IngressDryRun *ResourceAccountNetworkPolicyIngressDryRun `json:"ingress_dry_run,omitempty"` + NetworkPolicyId string `json:"network_policy_id,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_account_setting_v2.go b/bundle/internal/tf/schema/resource_account_setting_v2.go index ec90345010a..aed9ba93944 100644 --- a/bundle/internal/tf/schema/resource_account_setting_v2.go +++ b/bundle/internal/tf/schema/resource_account_setting_v2.go @@ -93,7 +93,8 @@ type ResourceAccountSettingV2EffectivePersonalCompute struct { } type ResourceAccountSettingV2EffectiveRestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type ResourceAccountSettingV2EffectiveStringVal struct { @@ -109,7 +110,8 @@ type ResourceAccountSettingV2PersonalCompute struct { } type ResourceAccountSettingV2RestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type ResourceAccountSettingV2StringVal struct { diff --git a/bundle/internal/tf/schema/resource_app.go b/bundle/internal/tf/schema/resource_app.go index 4dec84d54ea..02a1c2c3a6f 100644 --- a/bundle/internal/tf/schema/resource_app.go +++ b/bundle/internal/tf/schema/resource_app.go @@ -105,10 +105,12 @@ type ResourceAppPendingDeployment struct { } type ResourceAppProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAppResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type ResourceAppResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/resource_app_space.go b/bundle/internal/tf/schema/resource_app_space.go index be9bc844aeb..a05ee123585 100644 --- a/bundle/internal/tf/schema/resource_app_space.go +++ b/bundle/internal/tf/schema/resource_app_space.go @@ -7,6 +7,8 @@ type ResourceAppSpaceProviderConfig struct { } type ResourceAppSpaceResourcesApp struct { + Name string `json:"name,omitempty"` + Permission string `json:"permission,omitempty"` } type ResourceAppSpaceResourcesDatabase struct { diff --git a/bundle/internal/tf/schema/resource_catalog.go b/bundle/internal/tf/schema/resource_catalog.go index 571edfb83a4..1245cded7a6 100644 --- a/bundle/internal/tf/schema/resource_catalog.go +++ b/bundle/internal/tf/schema/resource_catalog.go @@ -8,6 +8,18 @@ type ResourceCatalogEffectivePredictiveOptimizationFlag struct { Value string `json:"value"` } +type ResourceCatalogManagedEncryptionSettingsAzureEncryptionSettings struct { + AzureCmkAccessConnectorId string `json:"azure_cmk_access_connector_id,omitempty"` + AzureCmkManagedIdentityId string `json:"azure_cmk_managed_identity_id,omitempty"` + AzureTenantId string `json:"azure_tenant_id"` +} + +type ResourceCatalogManagedEncryptionSettings struct { + AzureKeyVaultKeyId string `json:"azure_key_vault_key_id,omitempty"` + CustomerManagedKeyId string `json:"customer_managed_key_id,omitempty"` + AzureEncryptionSettings *ResourceCatalogManagedEncryptionSettingsAzureEncryptionSettings `json:"azure_encryption_settings,omitempty"` +} + type ResourceCatalogProviderConfig struct { WorkspaceId string `json:"workspace_id"` } @@ -41,6 +53,7 @@ type ResourceCatalog struct { UpdatedAt int `json:"updated_at,omitempty"` UpdatedBy string `json:"updated_by,omitempty"` EffectivePredictiveOptimizationFlag *ResourceCatalogEffectivePredictiveOptimizationFlag `json:"effective_predictive_optimization_flag,omitempty"` + ManagedEncryptionSettings *ResourceCatalogManagedEncryptionSettings `json:"managed_encryption_settings,omitempty"` ProviderConfig *ResourceCatalogProviderConfig `json:"provider_config,omitempty"` ProvisioningInfo *ResourceCatalogProvisioningInfo `json:"provisioning_info,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_credential.go b/bundle/internal/tf/schema/resource_credential.go index 9d47219ea98..bd8aae572c6 100644 --- a/bundle/internal/tf/schema/resource_credential.go +++ b/bundle/internal/tf/schema/resource_credential.go @@ -26,6 +26,10 @@ type ResourceCredentialDatabricksGcpServiceAccount struct { PrivateKeyId string `json:"private_key_id,omitempty"` } +type ResourceCredentialProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceCredential struct { Comment string `json:"comment,omitempty"` CreatedAt int `json:"created_at,omitempty"` @@ -49,4 +53,5 @@ type ResourceCredential struct { AzureManagedIdentity *ResourceCredentialAzureManagedIdentity `json:"azure_managed_identity,omitempty"` AzureServicePrincipal *ResourceCredentialAzureServicePrincipal `json:"azure_service_principal,omitempty"` DatabricksGcpServiceAccount *ResourceCredentialDatabricksGcpServiceAccount `json:"databricks_gcp_service_account,omitempty"` + ProviderConfig *ResourceCredentialProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_environments_default_workspace_base_environment.go b/bundle/internal/tf/schema/resource_environments_default_workspace_base_environment.go new file mode 100644 index 00000000000..4ea78fddc95 --- /dev/null +++ b/bundle/internal/tf/schema/resource_environments_default_workspace_base_environment.go @@ -0,0 +1,14 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type ResourceEnvironmentsDefaultWorkspaceBaseEnvironment struct { + CpuWorkspaceBaseEnvironment string `json:"cpu_workspace_base_environment,omitempty"` + GpuWorkspaceBaseEnvironment string `json:"gpu_workspace_base_environment,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig *ResourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig `json:"provider_config,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go b/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go new file mode 100644 index 00000000000..59b497b77ba --- /dev/null +++ b/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go @@ -0,0 +1,24 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type ResourceEnvironmentsWorkspaceBaseEnvironment struct { + BaseEnvironmentType string `json:"base_environment_type,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatorUserId string `json:"creator_user_id,omitempty"` + DisplayName string `json:"display_name"` + EffectiveBaseEnvironmentType string `json:"effective_base_environment_type,omitempty"` + Filepath string `json:"filepath,omitempty"` + IsDefault bool `json:"is_default,omitempty"` + LastUpdatedUserId string `json:"last_updated_user_id,omitempty"` + Message string `json:"message,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig *ResourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig `json:"provider_config,omitempty"` + Status string `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + WorkspaceBaseEnvironmentId string `json:"workspace_base_environment_id,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_external_location.go b/bundle/internal/tf/schema/resource_external_location.go index c865fea7a71..ef4fb962aef 100644 --- a/bundle/internal/tf/schema/resource_external_location.go +++ b/bundle/internal/tf/schema/resource_external_location.go @@ -2,6 +2,49 @@ package schema +type ResourceExternalLocationEffectiveFileEventQueueManagedAqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` + ResourceGroup string `json:"resource_group,omitempty"` + SubscriptionId string `json:"subscription_id,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueueManagedPubsub struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + SubscriptionName string `json:"subscription_name,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueueManagedSqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueueProvidedAqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` + ResourceGroup string `json:"resource_group,omitempty"` + SubscriptionId string `json:"subscription_id,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueueProvidedPubsub struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + SubscriptionName string `json:"subscription_name,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueueProvidedSqs struct { + ManagedResourceId string `json:"managed_resource_id,omitempty"` + QueueUrl string `json:"queue_url,omitempty"` +} + +type ResourceExternalLocationEffectiveFileEventQueue struct { + ManagedAqs *ResourceExternalLocationEffectiveFileEventQueueManagedAqs `json:"managed_aqs,omitempty"` + ManagedPubsub *ResourceExternalLocationEffectiveFileEventQueueManagedPubsub `json:"managed_pubsub,omitempty"` + ManagedSqs *ResourceExternalLocationEffectiveFileEventQueueManagedSqs `json:"managed_sqs,omitempty"` + ProvidedAqs *ResourceExternalLocationEffectiveFileEventQueueProvidedAqs `json:"provided_aqs,omitempty"` + ProvidedPubsub *ResourceExternalLocationEffectiveFileEventQueueProvidedPubsub `json:"provided_pubsub,omitempty"` + ProvidedSqs *ResourceExternalLocationEffectiveFileEventQueueProvidedSqs `json:"provided_sqs,omitempty"` +} + type ResourceExternalLocationEncryptionDetailsSseEncryptionDetails struct { Algorithm string `json:"algorithm,omitempty"` AwsKmsKeyArn string `json:"aws_kms_key_arn,omitempty"` @@ -59,28 +102,29 @@ type ResourceExternalLocationProviderConfig struct { } type ResourceExternalLocation struct { - BrowseOnly bool `json:"browse_only,omitempty"` - Comment string `json:"comment,omitempty"` - CreatedAt int `json:"created_at,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - CredentialId string `json:"credential_id,omitempty"` - CredentialName string `json:"credential_name"` - EffectiveEnableFileEvents bool `json:"effective_enable_file_events,omitempty"` - EnableFileEvents bool `json:"enable_file_events,omitempty"` - Fallback bool `json:"fallback,omitempty"` - ForceDestroy bool `json:"force_destroy,omitempty"` - ForceUpdate bool `json:"force_update,omitempty"` - Id string `json:"id,omitempty"` - IsolationMode string `json:"isolation_mode,omitempty"` - MetastoreId string `json:"metastore_id,omitempty"` - Name string `json:"name"` - Owner string `json:"owner,omitempty"` - ReadOnly bool `json:"read_only,omitempty"` - SkipValidation bool `json:"skip_validation,omitempty"` - UpdatedAt int `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` - Url string `json:"url"` - EncryptionDetails *ResourceExternalLocationEncryptionDetails `json:"encryption_details,omitempty"` - FileEventQueue *ResourceExternalLocationFileEventQueue `json:"file_event_queue,omitempty"` - ProviderConfig *ResourceExternalLocationProviderConfig `json:"provider_config,omitempty"` + BrowseOnly bool `json:"browse_only,omitempty"` + Comment string `json:"comment,omitempty"` + CreatedAt int `json:"created_at,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + CredentialId string `json:"credential_id,omitempty"` + CredentialName string `json:"credential_name"` + EffectiveEnableFileEvents bool `json:"effective_enable_file_events,omitempty"` + EnableFileEvents bool `json:"enable_file_events,omitempty"` + Fallback bool `json:"fallback,omitempty"` + ForceDestroy bool `json:"force_destroy,omitempty"` + ForceUpdate bool `json:"force_update,omitempty"` + Id string `json:"id,omitempty"` + IsolationMode string `json:"isolation_mode,omitempty"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name"` + Owner string `json:"owner,omitempty"` + ReadOnly bool `json:"read_only,omitempty"` + SkipValidation bool `json:"skip_validation,omitempty"` + UpdatedAt int `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Url string `json:"url"` + EffectiveFileEventQueue *ResourceExternalLocationEffectiveFileEventQueue `json:"effective_file_event_queue,omitempty"` + EncryptionDetails *ResourceExternalLocationEncryptionDetails `json:"encryption_details,omitempty"` + FileEventQueue *ResourceExternalLocationFileEventQueue `json:"file_event_queue,omitempty"` + ProviderConfig *ResourceExternalLocationProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_feature_engineering_feature.go b/bundle/internal/tf/schema/resource_feature_engineering_feature.go index d43ef24bf67..1a99477f647 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_feature.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_feature.go @@ -2,14 +2,116 @@ package schema +type ResourceFeatureEngineeringFeatureEntities struct { + Name string `json:"name"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxCountDistinct struct { + Input string `json:"input"` + RelativeSd int `json:"relative_sd,omitempty"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxPercentile struct { + Accuracy int `json:"accuracy,omitempty"` + Input string `json:"input"` + Percentile int `json:"percentile"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionAvg struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionCountFunction struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionFirst struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionLast struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionMax struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionMin struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevPop struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevSamp struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionSum struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowContinuous struct { + Offset string `json:"offset,omitempty"` + WindowDuration string `json:"window_duration"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowSliding struct { + SlideDuration string `json:"slide_duration"` + WindowDuration string `json:"window_duration"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowTumbling struct { + WindowDuration string `json:"window_duration"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindow struct { + Continuous *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowContinuous `json:"continuous,omitempty"` + Sliding *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowSliding `json:"sliding,omitempty"` + Tumbling *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindowTumbling `json:"tumbling,omitempty"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionVarPop struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunctionVarSamp struct { + Input string `json:"input"` +} + +type ResourceFeatureEngineeringFeatureFunctionAggregationFunction struct { + ApproxCountDistinct *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxCountDistinct `json:"approx_count_distinct,omitempty"` + ApproxPercentile *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionApproxPercentile `json:"approx_percentile,omitempty"` + Avg *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionAvg `json:"avg,omitempty"` + CountFunction *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionCountFunction `json:"count_function,omitempty"` + First *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionFirst `json:"first,omitempty"` + Last *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionLast `json:"last,omitempty"` + Max *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionMax `json:"max,omitempty"` + Min *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionMin `json:"min,omitempty"` + StddevPop *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevPop `json:"stddev_pop,omitempty"` + StddevSamp *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionStddevSamp `json:"stddev_samp,omitempty"` + Sum *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionSum `json:"sum,omitempty"` + TimeWindow *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionTimeWindow `json:"time_window,omitempty"` + VarPop *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionVarPop `json:"var_pop,omitempty"` + VarSamp *ResourceFeatureEngineeringFeatureFunctionAggregationFunctionVarSamp `json:"var_samp,omitempty"` +} + +type ResourceFeatureEngineeringFeatureFunctionColumnSelection struct { + Column string `json:"column"` +} + type ResourceFeatureEngineeringFeatureFunctionExtraParameters struct { Key string `json:"key"` Value string `json:"value"` } type ResourceFeatureEngineeringFeatureFunction struct { - ExtraParameters []ResourceFeatureEngineeringFeatureFunctionExtraParameters `json:"extra_parameters,omitempty"` - FunctionType string `json:"function_type"` + AggregationFunction *ResourceFeatureEngineeringFeatureFunctionAggregationFunction `json:"aggregation_function,omitempty"` + ColumnSelection *ResourceFeatureEngineeringFeatureFunctionColumnSelection `json:"column_selection,omitempty"` + ExtraParameters []ResourceFeatureEngineeringFeatureFunctionExtraParameters `json:"extra_parameters,omitempty"` + FunctionType string `json:"function_type,omitempty"` } type ResourceFeatureEngineeringFeatureLineageContextJobContext struct { @@ -28,10 +130,10 @@ type ResourceFeatureEngineeringFeatureProviderConfig struct { type ResourceFeatureEngineeringFeatureSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } @@ -45,13 +147,28 @@ type ResourceFeatureEngineeringFeatureSourceKafkaSourceTimeseriesColumnIdentifie type ResourceFeatureEngineeringFeatureSourceKafkaSource struct { EntityColumnIdentifiers []ResourceFeatureEngineeringFeatureSourceKafkaSourceEntityColumnIdentifiers `json:"entity_column_identifiers,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` Name string `json:"name"` TimeseriesColumnIdentifier *ResourceFeatureEngineeringFeatureSourceKafkaSourceTimeseriesColumnIdentifier `json:"timeseries_column_identifier,omitempty"` } +type ResourceFeatureEngineeringFeatureSourceRequestSourceFlatSchemaFields struct { + DataType string `json:"data_type"` + Name string `json:"name"` +} + +type ResourceFeatureEngineeringFeatureSourceRequestSourceFlatSchema struct { + Fields []ResourceFeatureEngineeringFeatureSourceRequestSourceFlatSchemaFields `json:"fields,omitempty"` +} + +type ResourceFeatureEngineeringFeatureSourceRequestSource struct { + FlatSchema *ResourceFeatureEngineeringFeatureSourceRequestSourceFlatSchema `json:"flat_schema,omitempty"` +} + type ResourceFeatureEngineeringFeatureSource struct { DeltaTableSource *ResourceFeatureEngineeringFeatureSourceDeltaTableSource `json:"delta_table_source,omitempty"` KafkaSource *ResourceFeatureEngineeringFeatureSourceKafkaSource `json:"kafka_source,omitempty"` + RequestSource *ResourceFeatureEngineeringFeatureSourceRequestSource `json:"request_source,omitempty"` } type ResourceFeatureEngineeringFeatureTimeWindowContinuous struct { @@ -74,14 +191,20 @@ type ResourceFeatureEngineeringFeatureTimeWindow struct { Tumbling *ResourceFeatureEngineeringFeatureTimeWindowTumbling `json:"tumbling,omitempty"` } +type ResourceFeatureEngineeringFeatureTimeseriesColumn struct { + Name string `json:"name"` +} + type ResourceFeatureEngineeringFeature struct { - Description string `json:"description,omitempty"` - FilterCondition string `json:"filter_condition,omitempty"` - FullName string `json:"full_name"` - Function *ResourceFeatureEngineeringFeatureFunction `json:"function,omitempty"` - Inputs []string `json:"inputs"` - LineageContext *ResourceFeatureEngineeringFeatureLineageContext `json:"lineage_context,omitempty"` - ProviderConfig *ResourceFeatureEngineeringFeatureProviderConfig `json:"provider_config,omitempty"` - Source *ResourceFeatureEngineeringFeatureSource `json:"source,omitempty"` - TimeWindow *ResourceFeatureEngineeringFeatureTimeWindow `json:"time_window,omitempty"` + Description string `json:"description,omitempty"` + Entities []ResourceFeatureEngineeringFeatureEntities `json:"entities,omitempty"` + FilterCondition string `json:"filter_condition,omitempty"` + FullName string `json:"full_name"` + Function *ResourceFeatureEngineeringFeatureFunction `json:"function,omitempty"` + Inputs []string `json:"inputs,omitempty"` + LineageContext *ResourceFeatureEngineeringFeatureLineageContext `json:"lineage_context,omitempty"` + ProviderConfig *ResourceFeatureEngineeringFeatureProviderConfig `json:"provider_config,omitempty"` + Source *ResourceFeatureEngineeringFeatureSource `json:"source,omitempty"` + TimeWindow *ResourceFeatureEngineeringFeatureTimeWindow `json:"time_window,omitempty"` + TimeseriesColumn *ResourceFeatureEngineeringFeatureTimeseriesColumn `json:"timeseries_column,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go b/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go index 0417830c0d3..864741b6132 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go @@ -8,10 +8,10 @@ type ResourceFeatureEngineeringKafkaConfigAuthConfig struct { type ResourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource struct { DataframeSchema string `json:"dataframe_schema,omitempty"` - EntityColumns []string `json:"entity_columns"` + EntityColumns []string `json:"entity_columns,omitempty"` FilterCondition string `json:"filter_condition,omitempty"` FullName string `json:"full_name"` - TimeseriesColumn string `json:"timeseries_column"` + TimeseriesColumn string `json:"timeseries_column,omitempty"` TransformationSql string `json:"transformation_sql,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go b/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go index 9a528e8f6d2..ddc305f0aa6 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go @@ -22,6 +22,7 @@ type ResourceFeatureEngineeringMaterializedFeatureProviderConfig struct { type ResourceFeatureEngineeringMaterializedFeature struct { CronSchedule string `json:"cron_schedule,omitempty"` FeatureName string `json:"feature_name"` + IsOnline bool `json:"is_online,omitempty"` LastMaterializationTime string `json:"last_materialization_time,omitempty"` MaterializedFeatureId string `json:"materialized_feature_id,omitempty"` OfflineStoreConfig *ResourceFeatureEngineeringMaterializedFeatureOfflineStoreConfig `json:"offline_store_config,omitempty"` diff --git a/bundle/internal/tf/schema/resource_group.go b/bundle/internal/tf/schema/resource_group.go index 8e44cd4d2bd..4a9eb9285ce 100644 --- a/bundle/internal/tf/schema/resource_group.go +++ b/bundle/internal/tf/schema/resource_group.go @@ -2,16 +2,22 @@ package schema +type ResourceGroupProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceGroup struct { - AclPrincipalId string `json:"acl_principal_id,omitempty"` - AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` - AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` - DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` - DisplayName string `json:"display_name"` - ExternalId string `json:"external_id,omitempty"` - Force bool `json:"force,omitempty"` - Id string `json:"id,omitempty"` - Url string `json:"url,omitempty"` - WorkspaceAccess bool `json:"workspace_access,omitempty"` - WorkspaceConsume bool `json:"workspace_consume,omitempty"` + AclPrincipalId string `json:"acl_principal_id,omitempty"` + AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` + AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` + Api string `json:"api,omitempty"` + DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` + DisplayName string `json:"display_name"` + ExternalId string `json:"external_id,omitempty"` + Force bool `json:"force,omitempty"` + Id string `json:"id,omitempty"` + Url string `json:"url,omitempty"` + WorkspaceAccess bool `json:"workspace_access,omitempty"` + WorkspaceConsume bool `json:"workspace_consume,omitempty"` + ProviderConfig *ResourceGroupProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_group_instance_profile.go b/bundle/internal/tf/schema/resource_group_instance_profile.go index 725ea5679ce..3ea5402c12b 100644 --- a/bundle/internal/tf/schema/resource_group_instance_profile.go +++ b/bundle/internal/tf/schema/resource_group_instance_profile.go @@ -2,8 +2,14 @@ package schema +type ResourceGroupInstanceProfileProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceGroupInstanceProfile struct { - GroupId string `json:"group_id"` - Id string `json:"id,omitempty"` - InstanceProfileId string `json:"instance_profile_id"` + Api string `json:"api,omitempty"` + GroupId string `json:"group_id"` + Id string `json:"id,omitempty"` + InstanceProfileId string `json:"instance_profile_id"` + ProviderConfig *ResourceGroupInstanceProfileProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_group_member.go b/bundle/internal/tf/schema/resource_group_member.go index 155c9ddd639..d849082ee2d 100644 --- a/bundle/internal/tf/schema/resource_group_member.go +++ b/bundle/internal/tf/schema/resource_group_member.go @@ -2,8 +2,14 @@ package schema +type ResourceGroupMemberProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceGroupMember struct { - GroupId string `json:"group_id"` - Id string `json:"id,omitempty"` - MemberId string `json:"member_id"` + Api string `json:"api,omitempty"` + GroupId string `json:"group_id"` + Id string `json:"id,omitempty"` + MemberId string `json:"member_id"` + ProviderConfig *ResourceGroupMemberProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_group_role.go b/bundle/internal/tf/schema/resource_group_role.go index 3603d4b52ae..9f2ec718685 100644 --- a/bundle/internal/tf/schema/resource_group_role.go +++ b/bundle/internal/tf/schema/resource_group_role.go @@ -2,8 +2,14 @@ package schema +type ResourceGroupRoleProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceGroupRole struct { - GroupId string `json:"group_id"` - Id string `json:"id,omitempty"` - Role string `json:"role"` + Api string `json:"api,omitempty"` + GroupId string `json:"group_id"` + Id string `json:"id,omitempty"` + Role string `json:"role"` + ProviderConfig *ResourceGroupRoleProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_job.go b/bundle/internal/tf/schema/resource_job.go index f4334b22401..d6603c62fba 100644 --- a/bundle/internal/tf/schema/resource_job.go +++ b/bundle/internal/tf/schema/resource_job.go @@ -630,6 +630,18 @@ type ResourceJobSparkSubmitTask struct { Parameters []string `json:"parameters,omitempty"` } +type ResourceJobTaskAlertTaskSubscribers struct { + DestinationId string `json:"destination_id,omitempty"` + UserName string `json:"user_name,omitempty"` +} + +type ResourceJobTaskAlertTask struct { + AlertId string `json:"alert_id,omitempty"` + WarehouseId string `json:"warehouse_id,omitempty"` + WorkspacePath string `json:"workspace_path,omitempty"` + Subscribers []ResourceJobTaskAlertTaskSubscribers `json:"subscribers,omitempty"` +} + type ResourceJobTaskCleanRoomsNotebookTask struct { CleanRoomName string `json:"clean_room_name"` Etag string `json:"etag,omitempty"` @@ -699,6 +711,18 @@ type ResourceJobTaskEmailNotifications struct { OnSuccess []string `json:"on_success,omitempty"` } +type ResourceJobTaskForEachTaskTaskAlertTaskSubscribers struct { + DestinationId string `json:"destination_id,omitempty"` + UserName string `json:"user_name,omitempty"` +} + +type ResourceJobTaskForEachTaskTaskAlertTask struct { + AlertId string `json:"alert_id,omitempty"` + WarehouseId string `json:"warehouse_id,omitempty"` + WorkspacePath string `json:"workspace_path,omitempty"` + Subscribers []ResourceJobTaskForEachTaskTaskAlertTaskSubscribers `json:"subscribers,omitempty"` +} + type ResourceJobTaskForEachTaskTaskCleanRoomsNotebookTask struct { CleanRoomName string `json:"clean_room_name"` Etag string `json:"etag,omitempty"` @@ -1212,6 +1236,7 @@ type ResourceJobTaskForEachTaskTask struct { RunIf string `json:"run_if,omitempty"` TaskKey string `json:"task_key"` TimeoutSeconds int `json:"timeout_seconds,omitempty"` + AlertTask *ResourceJobTaskForEachTaskTaskAlertTask `json:"alert_task,omitempty"` CleanRoomsNotebookTask *ResourceJobTaskForEachTaskTaskCleanRoomsNotebookTask `json:"clean_rooms_notebook_task,omitempty"` Compute *ResourceJobTaskForEachTaskTaskCompute `json:"compute,omitempty"` ConditionTask *ResourceJobTaskForEachTaskTaskConditionTask `json:"condition_task,omitempty"` @@ -1689,6 +1714,7 @@ type ResourceJobTask struct { RunIf string `json:"run_if,omitempty"` TaskKey string `json:"task_key"` TimeoutSeconds int `json:"timeout_seconds,omitempty"` + AlertTask *ResourceJobTaskAlertTask `json:"alert_task,omitempty"` CleanRoomsNotebookTask *ResourceJobTaskCleanRoomsNotebookTask `json:"clean_rooms_notebook_task,omitempty"` Compute *ResourceJobTaskCompute `json:"compute,omitempty"` ConditionTask *ResourceJobTaskConditionTask `json:"condition_task,omitempty"` diff --git a/bundle/internal/tf/schema/resource_metastore.go b/bundle/internal/tf/schema/resource_metastore.go index 456864f6c1a..1fe6064fabe 100644 --- a/bundle/internal/tf/schema/resource_metastore.go +++ b/bundle/internal/tf/schema/resource_metastore.go @@ -2,26 +2,32 @@ package schema +type ResourceMetastoreProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceMetastore struct { - Cloud string `json:"cloud,omitempty"` - CreatedAt int `json:"created_at,omitempty"` - CreatedBy string `json:"created_by,omitempty"` - DefaultDataAccessConfigId string `json:"default_data_access_config_id,omitempty"` - DeltaSharingOrganizationName string `json:"delta_sharing_organization_name,omitempty"` - DeltaSharingRecipientTokenLifetimeInSeconds int `json:"delta_sharing_recipient_token_lifetime_in_seconds,omitempty"` - DeltaSharingScope string `json:"delta_sharing_scope,omitempty"` - ExternalAccessEnabled bool `json:"external_access_enabled,omitempty"` - ForceDestroy bool `json:"force_destroy,omitempty"` - GlobalMetastoreId string `json:"global_metastore_id,omitempty"` - Id string `json:"id,omitempty"` - MetastoreId string `json:"metastore_id,omitempty"` - Name string `json:"name,omitempty"` - Owner string `json:"owner,omitempty"` - PrivilegeModelVersion string `json:"privilege_model_version,omitempty"` - Region string `json:"region,omitempty"` - StorageRoot string `json:"storage_root,omitempty"` - StorageRootCredentialId string `json:"storage_root_credential_id,omitempty"` - StorageRootCredentialName string `json:"storage_root_credential_name,omitempty"` - UpdatedAt int `json:"updated_at,omitempty"` - UpdatedBy string `json:"updated_by,omitempty"` + Api string `json:"api,omitempty"` + Cloud string `json:"cloud,omitempty"` + CreatedAt int `json:"created_at,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + DefaultDataAccessConfigId string `json:"default_data_access_config_id,omitempty"` + DeltaSharingOrganizationName string `json:"delta_sharing_organization_name,omitempty"` + DeltaSharingRecipientTokenLifetimeInSeconds int `json:"delta_sharing_recipient_token_lifetime_in_seconds,omitempty"` + DeltaSharingScope string `json:"delta_sharing_scope,omitempty"` + ExternalAccessEnabled bool `json:"external_access_enabled,omitempty"` + ForceDestroy bool `json:"force_destroy,omitempty"` + GlobalMetastoreId string `json:"global_metastore_id,omitempty"` + Id string `json:"id,omitempty"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + PrivilegeModelVersion string `json:"privilege_model_version,omitempty"` + Region string `json:"region,omitempty"` + StorageRoot string `json:"storage_root,omitempty"` + StorageRootCredentialId string `json:"storage_root_credential_id,omitempty"` + StorageRootCredentialName string `json:"storage_root_credential_name,omitempty"` + UpdatedAt int `json:"updated_at,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + ProviderConfig *ResourceMetastoreProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_metastore_assignment.go b/bundle/internal/tf/schema/resource_metastore_assignment.go index 8329f603006..2127c1cec01 100644 --- a/bundle/internal/tf/schema/resource_metastore_assignment.go +++ b/bundle/internal/tf/schema/resource_metastore_assignment.go @@ -2,9 +2,15 @@ package schema +type ResourceMetastoreAssignmentProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceMetastoreAssignment struct { - DefaultCatalogName string `json:"default_catalog_name,omitempty"` - Id string `json:"id,omitempty"` - MetastoreId string `json:"metastore_id"` - WorkspaceId int `json:"workspace_id"` + Api string `json:"api,omitempty"` + DefaultCatalogName string `json:"default_catalog_name,omitempty"` + Id string `json:"id,omitempty"` + MetastoreId string `json:"metastore_id"` + WorkspaceId int `json:"workspace_id"` + ProviderConfig *ResourceMetastoreAssignmentProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_metastore_data_access.go b/bundle/internal/tf/schema/resource_metastore_data_access.go index ef8c34aa769..7c79c665ef5 100644 --- a/bundle/internal/tf/schema/resource_metastore_data_access.go +++ b/bundle/internal/tf/schema/resource_metastore_data_access.go @@ -37,7 +37,12 @@ type ResourceMetastoreDataAccessGcpServiceAccountKey struct { PrivateKeyId string `json:"private_key_id"` } +type ResourceMetastoreDataAccessProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceMetastoreDataAccess struct { + Api string `json:"api,omitempty"` Comment string `json:"comment,omitempty"` ForceDestroy bool `json:"force_destroy,omitempty"` ForceUpdate bool `json:"force_update,omitempty"` @@ -55,4 +60,5 @@ type ResourceMetastoreDataAccess struct { CloudflareApiToken *ResourceMetastoreDataAccessCloudflareApiToken `json:"cloudflare_api_token,omitempty"` DatabricksGcpServiceAccount *ResourceMetastoreDataAccessDatabricksGcpServiceAccount `json:"databricks_gcp_service_account,omitempty"` GcpServiceAccountKey *ResourceMetastoreDataAccessGcpServiceAccountKey `json:"gcp_service_account_key,omitempty"` + ProviderConfig *ResourceMetastoreDataAccessProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_mws_ncc_private_endpoint_rule.go b/bundle/internal/tf/schema/resource_mws_ncc_private_endpoint_rule.go index ad6e9938a2c..dd35f714b8b 100644 --- a/bundle/internal/tf/schema/resource_mws_ncc_private_endpoint_rule.go +++ b/bundle/internal/tf/schema/resource_mws_ncc_private_endpoint_rule.go @@ -2,23 +2,29 @@ package schema +type ResourceMwsNccPrivateEndpointRuleGcpEndpoint struct { + PscEndpointUri string `json:"psc_endpoint_uri,omitempty"` + ServiceAttachment string `json:"service_attachment,omitempty"` +} + type ResourceMwsNccPrivateEndpointRule struct { - AccountId string `json:"account_id,omitempty"` - ConnectionState string `json:"connection_state,omitempty"` - CreationTime int `json:"creation_time,omitempty"` - Deactivated bool `json:"deactivated,omitempty"` - DeactivatedAt int `json:"deactivated_at,omitempty"` - DomainNames []string `json:"domain_names,omitempty"` - Enabled bool `json:"enabled,omitempty"` - EndpointName string `json:"endpoint_name,omitempty"` - EndpointService string `json:"endpoint_service,omitempty"` - ErrorMessage string `json:"error_message,omitempty"` - GroupId string `json:"group_id,omitempty"` - Id string `json:"id,omitempty"` - NetworkConnectivityConfigId string `json:"network_connectivity_config_id"` - ResourceId string `json:"resource_id,omitempty"` - ResourceNames []string `json:"resource_names,omitempty"` - RuleId string `json:"rule_id,omitempty"` - UpdatedTime int `json:"updated_time,omitempty"` - VpcEndpointId string `json:"vpc_endpoint_id,omitempty"` + AccountId string `json:"account_id,omitempty"` + ConnectionState string `json:"connection_state,omitempty"` + CreationTime int `json:"creation_time,omitempty"` + Deactivated bool `json:"deactivated,omitempty"` + DeactivatedAt int `json:"deactivated_at,omitempty"` + DomainNames []string `json:"domain_names,omitempty"` + Enabled bool `json:"enabled,omitempty"` + EndpointName string `json:"endpoint_name,omitempty"` + EndpointService string `json:"endpoint_service,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + GroupId string `json:"group_id,omitempty"` + Id string `json:"id,omitempty"` + NetworkConnectivityConfigId string `json:"network_connectivity_config_id"` + ResourceId string `json:"resource_id,omitempty"` + ResourceNames []string `json:"resource_names,omitempty"` + RuleId string `json:"rule_id,omitempty"` + UpdatedTime int `json:"updated_time,omitempty"` + VpcEndpointId string `json:"vpc_endpoint_id,omitempty"` + GcpEndpoint *ResourceMwsNccPrivateEndpointRuleGcpEndpoint `json:"gcp_endpoint,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_pipeline.go b/bundle/internal/tf/schema/resource_pipeline.go index eb4577e4b25..e7bf6bb91c9 100644 --- a/bundle/internal/tf/schema/resource_pipeline.go +++ b/bundle/internal/tf/schema/resource_pipeline.go @@ -165,6 +165,12 @@ type ResourcePipelineGatewayDefinition struct { ConnectionParameters *ResourcePipelineGatewayDefinitionConnectionParameters `json:"connection_parameters,omitempty"` } +type ResourcePipelineIngestionDefinitionDataStagingOptions struct { + CatalogName string `json:"catalog_name"` + SchemaName string `json:"schema_name"` + VolumeName string `json:"volume_name,omitempty"` +} + type ResourcePipelineIngestionDefinitionFullRefreshWindow struct { DaysOfWeek []string `json:"days_of_week,omitempty"` StartHour int `json:"start_hour"` @@ -214,6 +220,81 @@ type ResourcePipelineIngestionDefinitionObjectsReport struct { TableConfiguration *ResourcePipelineIngestionDefinitionObjectsReportTableConfiguration `json:"table_configuration,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters struct { + ModifiedAfter string `json:"modified_after,omitempty"` + ModifiedBefore string `json:"modified_before,omitempty"` + PathFilter string `json:"path_filter,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptionsFileIngestionOptions struct { + CorruptRecordColumn string `json:"corrupt_record_column,omitempty"` + Format string `json:"format,omitempty"` + FormatOptions map[string]string `json:"format_options,omitempty"` + IgnoreCorruptFiles bool `json:"ignore_corrupt_files,omitempty"` + InferColumnTypes bool `json:"infer_column_types,omitempty"` + ReaderCaseSensitive bool `json:"reader_case_sensitive,omitempty"` + RescuedDataColumn string `json:"rescued_data_column,omitempty"` + SchemaEvolutionMode string `json:"schema_evolution_mode,omitempty"` + SchemaHints string `json:"schema_hints,omitempty"` + SingleVariantColumn string `json:"single_variant_column,omitempty"` + FileFilters []ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters `json:"file_filters,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptions struct { + EntityType string `json:"entity_type,omitempty"` + Url string `json:"url,omitempty"` + FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGoogleAdsOptions struct { + LookbackWindowDays int `json:"lookback_window_days,omitempty"` + ManagerAccountId string `json:"manager_account_id"` + SyncStartDate string `json:"sync_start_date,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters struct { + ModifiedAfter string `json:"modified_after,omitempty"` + ModifiedBefore string `json:"modified_before,omitempty"` + PathFilter string `json:"path_filter,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptions struct { + CorruptRecordColumn string `json:"corrupt_record_column,omitempty"` + Format string `json:"format,omitempty"` + FormatOptions map[string]string `json:"format_options,omitempty"` + IgnoreCorruptFiles bool `json:"ignore_corrupt_files,omitempty"` + InferColumnTypes bool `json:"infer_column_types,omitempty"` + ReaderCaseSensitive bool `json:"reader_case_sensitive,omitempty"` + RescuedDataColumn string `json:"rescued_data_column,omitempty"` + SchemaEvolutionMode string `json:"schema_evolution_mode,omitempty"` + SchemaHints string `json:"schema_hints,omitempty"` + SingleVariantColumn string `json:"single_variant_column,omitempty"` + FileFilters []ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters `json:"file_filters,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptions struct { + EntityType string `json:"entity_type,omitempty"` + Url string `json:"url,omitempty"` + FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsTiktokAdsOptions struct { + DataLevel string `json:"data_level,omitempty"` + Dimensions []string `json:"dimensions,omitempty"` + LookbackWindowDays int `json:"lookback_window_days,omitempty"` + Metrics []string `json:"metrics,omitempty"` + QueryLifetime bool `json:"query_lifetime,omitempty"` + ReportType string `json:"report_type,omitempty"` + SyncStartDate string `json:"sync_start_date,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptions struct { + GdriveOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptions `json:"gdrive_options,omitempty"` + GoogleAdsOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGoogleAdsOptions `json:"google_ads_options,omitempty"` + SharepointOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptions `json:"sharepoint_options,omitempty"` + TiktokAdsOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsTiktokAdsOptions `json:"tiktok_ads_options,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsSchemaTableConfigurationAutoFullRefreshPolicy struct { Enabled bool `json:"enabled"` MinIntervalHours int `json:"min_interval_hours,omitempty"` @@ -254,9 +335,85 @@ type ResourcePipelineIngestionDefinitionObjectsSchema struct { DestinationSchema string `json:"destination_schema"` SourceCatalog string `json:"source_catalog,omitempty"` SourceSchema string `json:"source_schema"` + ConnectorOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptions `json:"connector_options,omitempty"` TableConfiguration *ResourcePipelineIngestionDefinitionObjectsSchemaTableConfiguration `json:"table_configuration,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters struct { + ModifiedAfter string `json:"modified_after,omitempty"` + ModifiedBefore string `json:"modified_before,omitempty"` + PathFilter string `json:"path_filter,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptionsFileIngestionOptions struct { + CorruptRecordColumn string `json:"corrupt_record_column,omitempty"` + Format string `json:"format,omitempty"` + FormatOptions map[string]string `json:"format_options,omitempty"` + IgnoreCorruptFiles bool `json:"ignore_corrupt_files,omitempty"` + InferColumnTypes bool `json:"infer_column_types,omitempty"` + ReaderCaseSensitive bool `json:"reader_case_sensitive,omitempty"` + RescuedDataColumn string `json:"rescued_data_column,omitempty"` + SchemaEvolutionMode string `json:"schema_evolution_mode,omitempty"` + SchemaHints string `json:"schema_hints,omitempty"` + SingleVariantColumn string `json:"single_variant_column,omitempty"` + FileFilters []ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters `json:"file_filters,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptions struct { + EntityType string `json:"entity_type,omitempty"` + Url string `json:"url,omitempty"` + FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGoogleAdsOptions struct { + LookbackWindowDays int `json:"lookback_window_days,omitempty"` + ManagerAccountId string `json:"manager_account_id"` + SyncStartDate string `json:"sync_start_date,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters struct { + ModifiedAfter string `json:"modified_after,omitempty"` + ModifiedBefore string `json:"modified_before,omitempty"` + PathFilter string `json:"path_filter,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptions struct { + CorruptRecordColumn string `json:"corrupt_record_column,omitempty"` + Format string `json:"format,omitempty"` + FormatOptions map[string]string `json:"format_options,omitempty"` + IgnoreCorruptFiles bool `json:"ignore_corrupt_files,omitempty"` + InferColumnTypes bool `json:"infer_column_types,omitempty"` + ReaderCaseSensitive bool `json:"reader_case_sensitive,omitempty"` + RescuedDataColumn string `json:"rescued_data_column,omitempty"` + SchemaEvolutionMode string `json:"schema_evolution_mode,omitempty"` + SchemaHints string `json:"schema_hints,omitempty"` + SingleVariantColumn string `json:"single_variant_column,omitempty"` + FileFilters []ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters `json:"file_filters,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptions struct { + EntityType string `json:"entity_type,omitempty"` + Url string `json:"url,omitempty"` + FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsTiktokAdsOptions struct { + DataLevel string `json:"data_level,omitempty"` + Dimensions []string `json:"dimensions,omitempty"` + LookbackWindowDays int `json:"lookback_window_days,omitempty"` + Metrics []string `json:"metrics,omitempty"` + QueryLifetime bool `json:"query_lifetime,omitempty"` + ReportType string `json:"report_type,omitempty"` + SyncStartDate string `json:"sync_start_date,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptions struct { + GdriveOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptions `json:"gdrive_options,omitempty"` + GoogleAdsOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGoogleAdsOptions `json:"google_ads_options,omitempty"` + SharepointOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptions `json:"sharepoint_options,omitempty"` + TiktokAdsOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsTiktokAdsOptions `json:"tiktok_ads_options,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsTableTableConfigurationAutoFullRefreshPolicy struct { Enabled bool `json:"enabled"` MinIntervalHours int `json:"min_interval_hours,omitempty"` @@ -299,6 +456,7 @@ type ResourcePipelineIngestionDefinitionObjectsTable struct { SourceCatalog string `json:"source_catalog,omitempty"` SourceSchema string `json:"source_schema,omitempty"` SourceTable string `json:"source_table"` + ConnectorOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptions `json:"connector_options,omitempty"` TableConfiguration *ResourcePipelineIngestionDefinitionObjectsTableTableConfiguration `json:"table_configuration,omitempty"` } @@ -363,10 +521,12 @@ type ResourcePipelineIngestionDefinitionTableConfiguration struct { type ResourcePipelineIngestionDefinition struct { ConnectionName string `json:"connection_name,omitempty"` + ConnectorType string `json:"connector_type,omitempty"` IngestFromUcForeignCatalog bool `json:"ingest_from_uc_foreign_catalog,omitempty"` IngestionGatewayId string `json:"ingestion_gateway_id,omitempty"` NetsuiteJarPath string `json:"netsuite_jar_path,omitempty"` SourceType string `json:"source_type,omitempty"` + DataStagingOptions *ResourcePipelineIngestionDefinitionDataStagingOptions `json:"data_staging_options,omitempty"` FullRefreshWindow *ResourcePipelineIngestionDefinitionFullRefreshWindow `json:"full_refresh_window,omitempty"` Objects []ResourcePipelineIngestionDefinitionObjects `json:"objects,omitempty"` SourceConfigurations []ResourcePipelineIngestionDefinitionSourceConfigurations `json:"source_configurations,omitempty"` diff --git a/bundle/internal/tf/schema/resource_postgres_catalog.go b/bundle/internal/tf/schema/resource_postgres_catalog.go new file mode 100644 index 00000000000..637bdaad2c9 --- /dev/null +++ b/bundle/internal/tf/schema/resource_postgres_catalog.go @@ -0,0 +1,30 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourcePostgresCatalogProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type ResourcePostgresCatalogSpec struct { + Branch string `json:"branch,omitempty"` + CreateDatabaseIfMissing bool `json:"create_database_if_missing,omitempty"` + PostgresDatabase string `json:"postgres_database"` +} + +type ResourcePostgresCatalogStatus struct { + Branch string `json:"branch,omitempty"` + PostgresDatabase string `json:"postgres_database,omitempty"` + Project string `json:"project,omitempty"` +} + +type ResourcePostgresCatalog struct { + CatalogId string `json:"catalog_id"` + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig *ResourcePostgresCatalogProviderConfig `json:"provider_config,omitempty"` + Spec *ResourcePostgresCatalogSpec `json:"spec,omitempty"` + Status *ResourcePostgresCatalogStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_postgres_project.go b/bundle/internal/tf/schema/resource_postgres_project.go index 8560bbf6a45..1df7ebf5115 100644 --- a/bundle/internal/tf/schema/resource_postgres_project.go +++ b/bundle/internal/tf/schema/resource_postgres_project.go @@ -32,6 +32,7 @@ type ResourcePostgresProjectSpecDefaultEndpointSettings struct { type ResourcePostgresProjectSpec struct { BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []ResourcePostgresProjectSpecCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *ResourcePostgresProjectSpecDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` @@ -56,6 +57,7 @@ type ResourcePostgresProjectStatus struct { BranchLogicalSizeLimitBytes int `json:"branch_logical_size_limit_bytes,omitempty"` BudgetPolicyId string `json:"budget_policy_id,omitempty"` CustomTags []ResourcePostgresProjectStatusCustomTags `json:"custom_tags,omitempty"` + DefaultBranch string `json:"default_branch,omitempty"` DefaultEndpointSettings *ResourcePostgresProjectStatusDefaultEndpointSettings `json:"default_endpoint_settings,omitempty"` DisplayName string `json:"display_name,omitempty"` EnablePgNativeLogin bool `json:"enable_pg_native_login,omitempty"` diff --git a/bundle/internal/tf/schema/resource_postgres_role.go b/bundle/internal/tf/schema/resource_postgres_role.go new file mode 100644 index 00000000000..a7dce0a9bdc --- /dev/null +++ b/bundle/internal/tf/schema/resource_postgres_role.go @@ -0,0 +1,46 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourcePostgresRoleProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type ResourcePostgresRoleSpecAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type ResourcePostgresRoleSpec struct { + Attributes *ResourcePostgresRoleSpecAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type ResourcePostgresRoleStatusAttributes struct { + Bypassrls bool `json:"bypassrls,omitempty"` + Createdb bool `json:"createdb,omitempty"` + Createrole bool `json:"createrole,omitempty"` +} + +type ResourcePostgresRoleStatus struct { + Attributes *ResourcePostgresRoleStatusAttributes `json:"attributes,omitempty"` + AuthMethod string `json:"auth_method,omitempty"` + IdentityType string `json:"identity_type,omitempty"` + MembershipRoles []string `json:"membership_roles,omitempty"` + PostgresRole string `json:"postgres_role,omitempty"` +} + +type ResourcePostgresRole struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + ProviderConfig *ResourcePostgresRoleProviderConfig `json:"provider_config,omitempty"` + RoleId string `json:"role_id,omitempty"` + Spec *ResourcePostgresRoleSpec `json:"spec,omitempty"` + Status *ResourcePostgresRoleStatus `json:"status,omitempty"` + UpdateTime string `json:"update_time,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_postgres_synced_table.go b/bundle/internal/tf/schema/resource_postgres_synced_table.go new file mode 100644 index 00000000000..eed810c301a --- /dev/null +++ b/bundle/internal/tf/schema/resource_postgres_synced_table.go @@ -0,0 +1,66 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourcePostgresSyncedTableProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + +type ResourcePostgresSyncedTableSpecNewPipelineSpec struct { + BudgetPolicyId string `json:"budget_policy_id,omitempty"` + StorageCatalog string `json:"storage_catalog,omitempty"` + StorageSchema string `json:"storage_schema,omitempty"` +} + +type ResourcePostgresSyncedTableSpec struct { + Branch string `json:"branch,omitempty"` + CreateDatabaseObjectsIfMissing bool `json:"create_database_objects_if_missing,omitempty"` + ExistingPipelineId string `json:"existing_pipeline_id,omitempty"` + NewPipelineSpec *ResourcePostgresSyncedTableSpecNewPipelineSpec `json:"new_pipeline_spec,omitempty"` + PostgresDatabase string `json:"postgres_database,omitempty"` + PrimaryKeyColumns []string `json:"primary_key_columns,omitempty"` + SchedulingPolicy string `json:"scheduling_policy,omitempty"` + SourceTableFullName string `json:"source_table_full_name,omitempty"` + TimeseriesKey string `json:"timeseries_key,omitempty"` +} + +type ResourcePostgresSyncedTableStatusLastSyncDeltaTableSyncInfo struct { + DeltaCommitTime string `json:"delta_commit_time,omitempty"` + DeltaCommitVersion int `json:"delta_commit_version,omitempty"` +} + +type ResourcePostgresSyncedTableStatusLastSync struct { + DeltaTableSyncInfo *ResourcePostgresSyncedTableStatusLastSyncDeltaTableSyncInfo `json:"delta_table_sync_info,omitempty"` + SyncEndTime string `json:"sync_end_time,omitempty"` + SyncStartTime string `json:"sync_start_time,omitempty"` +} + +type ResourcePostgresSyncedTableStatusOngoingSyncProgress struct { + EstimatedCompletionTimeSeconds int `json:"estimated_completion_time_seconds,omitempty"` + LatestVersionCurrentlyProcessing int `json:"latest_version_currently_processing,omitempty"` + SyncProgressCompletion int `json:"sync_progress_completion,omitempty"` + SyncedRowCount int `json:"synced_row_count,omitempty"` + TotalRowCount int `json:"total_row_count,omitempty"` +} + +type ResourcePostgresSyncedTableStatus struct { + DetailedState string `json:"detailed_state,omitempty"` + LastProcessedCommitVersion int `json:"last_processed_commit_version,omitempty"` + LastSync *ResourcePostgresSyncedTableStatusLastSync `json:"last_sync,omitempty"` + LastSyncTime string `json:"last_sync_time,omitempty"` + Message string `json:"message,omitempty"` + OngoingSyncProgress *ResourcePostgresSyncedTableStatusOngoingSyncProgress `json:"ongoing_sync_progress,omitempty"` + PipelineId string `json:"pipeline_id,omitempty"` + ProvisioningPhase string `json:"provisioning_phase,omitempty"` + UnityCatalogProvisioningState string `json:"unity_catalog_provisioning_state,omitempty"` +} + +type ResourcePostgresSyncedTable struct { + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig *ResourcePostgresSyncedTableProviderConfig `json:"provider_config,omitempty"` + Spec *ResourcePostgresSyncedTableSpec `json:"spec,omitempty"` + Status *ResourcePostgresSyncedTableStatus `json:"status,omitempty"` + SyncedTableId string `json:"synced_table_id"` + Uid string `json:"uid,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go b/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go index 6b5b949cccf..b539201216e 100644 --- a/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go +++ b/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go @@ -7,7 +7,8 @@ type ResourceRestrictWorkspaceAdminsSettingProviderConfig struct { } type ResourceRestrictWorkspaceAdminsSettingRestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type ResourceRestrictWorkspaceAdminsSetting struct { diff --git a/bundle/internal/tf/schema/resource_service_principal.go b/bundle/internal/tf/schema/resource_service_principal.go index 64971da55a0..231eccf3b15 100644 --- a/bundle/internal/tf/schema/resource_service_principal.go +++ b/bundle/internal/tf/schema/resource_service_principal.go @@ -2,22 +2,28 @@ package schema +type ResourceServicePrincipalProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceServicePrincipal struct { - AclPrincipalId string `json:"acl_principal_id,omitempty"` - Active bool `json:"active,omitempty"` - AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` - AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` - ApplicationId string `json:"application_id,omitempty"` - DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` - DisableAsUserDeletion bool `json:"disable_as_user_deletion,omitempty"` - DisplayName string `json:"display_name,omitempty"` - ExternalId string `json:"external_id,omitempty"` - Force bool `json:"force,omitempty"` - ForceDeleteHomeDir bool `json:"force_delete_home_dir,omitempty"` - ForceDeleteRepos bool `json:"force_delete_repos,omitempty"` - Home string `json:"home,omitempty"` - Id string `json:"id,omitempty"` - Repos string `json:"repos,omitempty"` - WorkspaceAccess bool `json:"workspace_access,omitempty"` - WorkspaceConsume bool `json:"workspace_consume,omitempty"` + AclPrincipalId string `json:"acl_principal_id,omitempty"` + Active bool `json:"active,omitempty"` + AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` + AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` + Api string `json:"api,omitempty"` + ApplicationId string `json:"application_id,omitempty"` + DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` + DisableAsUserDeletion bool `json:"disable_as_user_deletion,omitempty"` + DisplayName string `json:"display_name,omitempty"` + ExternalId string `json:"external_id,omitempty"` + Force bool `json:"force,omitempty"` + ForceDeleteHomeDir bool `json:"force_delete_home_dir,omitempty"` + ForceDeleteRepos bool `json:"force_delete_repos,omitempty"` + Home string `json:"home,omitempty"` + Id string `json:"id,omitempty"` + Repos string `json:"repos,omitempty"` + WorkspaceAccess bool `json:"workspace_access,omitempty"` + WorkspaceConsume bool `json:"workspace_consume,omitempty"` + ProviderConfig *ResourceServicePrincipalProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_service_principal_role.go b/bundle/internal/tf/schema/resource_service_principal_role.go index 999c3ad0178..3da75ea081e 100644 --- a/bundle/internal/tf/schema/resource_service_principal_role.go +++ b/bundle/internal/tf/schema/resource_service_principal_role.go @@ -2,8 +2,14 @@ package schema +type ResourceServicePrincipalRoleProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceServicePrincipalRole struct { - Id string `json:"id,omitempty"` - Role string `json:"role"` - ServicePrincipalId string `json:"service_principal_id"` + Api string `json:"api,omitempty"` + Id string `json:"id,omitempty"` + Role string `json:"role"` + ServicePrincipalId string `json:"service_principal_id"` + ProviderConfig *ResourceServicePrincipalRoleProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_service_principal_secret.go b/bundle/internal/tf/schema/resource_service_principal_secret.go index f08b3cd4902..7e6559cf908 100644 --- a/bundle/internal/tf/schema/resource_service_principal_secret.go +++ b/bundle/internal/tf/schema/resource_service_principal_secret.go @@ -7,6 +7,7 @@ type ResourceServicePrincipalSecretProviderConfig struct { } type ResourceServicePrincipalSecret struct { + Api string `json:"api,omitempty"` CreateTime string `json:"create_time,omitempty"` ExpireTime string `json:"expire_time,omitempty"` Id string `json:"id,omitempty"` diff --git a/bundle/internal/tf/schema/resource_sql_permissions.go b/bundle/internal/tf/schema/resource_sql_permissions.go index 12a33e1383e..8a931793806 100644 --- a/bundle/internal/tf/schema/resource_sql_permissions.go +++ b/bundle/internal/tf/schema/resource_sql_permissions.go @@ -7,6 +7,10 @@ type ResourceSqlPermissionsPrivilegeAssignments struct { Privileges []string `json:"privileges"` } +type ResourceSqlPermissionsProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceSqlPermissions struct { AnonymousFunction bool `json:"anonymous_function,omitempty"` AnyFile bool `json:"any_file,omitempty"` @@ -17,4 +21,5 @@ type ResourceSqlPermissions struct { Table string `json:"table,omitempty"` View string `json:"view,omitempty"` PrivilegeAssignments []ResourceSqlPermissionsPrivilegeAssignments `json:"privilege_assignments,omitempty"` + ProviderConfig *ResourceSqlPermissionsProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_storage_credential.go b/bundle/internal/tf/schema/resource_storage_credential.go index 7278c2193d2..5f0485eff04 100644 --- a/bundle/internal/tf/schema/resource_storage_credential.go +++ b/bundle/internal/tf/schema/resource_storage_credential.go @@ -37,7 +37,12 @@ type ResourceStorageCredentialGcpServiceAccountKey struct { PrivateKeyId string `json:"private_key_id"` } +type ResourceStorageCredentialProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceStorageCredential struct { + Api string `json:"api,omitempty"` Comment string `json:"comment,omitempty"` ForceDestroy bool `json:"force_destroy,omitempty"` ForceUpdate bool `json:"force_update,omitempty"` @@ -55,4 +60,5 @@ type ResourceStorageCredential struct { CloudflareApiToken *ResourceStorageCredentialCloudflareApiToken `json:"cloudflare_api_token,omitempty"` DatabricksGcpServiceAccount *ResourceStorageCredentialDatabricksGcpServiceAccount `json:"databricks_gcp_service_account,omitempty"` GcpServiceAccountKey *ResourceStorageCredentialGcpServiceAccountKey `json:"gcp_service_account_key,omitempty"` + ProviderConfig *ResourceStorageCredentialProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_user.go b/bundle/internal/tf/schema/resource_user.go index 628dedf7b3d..0ad0aa5ca51 100644 --- a/bundle/internal/tf/schema/resource_user.go +++ b/bundle/internal/tf/schema/resource_user.go @@ -2,22 +2,28 @@ package schema +type ResourceUserProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceUser struct { - AclPrincipalId string `json:"acl_principal_id,omitempty"` - Active bool `json:"active,omitempty"` - AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` - AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` - DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` - DisableAsUserDeletion bool `json:"disable_as_user_deletion,omitempty"` - DisplayName string `json:"display_name,omitempty"` - ExternalId string `json:"external_id,omitempty"` - Force bool `json:"force,omitempty"` - ForceDeleteHomeDir bool `json:"force_delete_home_dir,omitempty"` - ForceDeleteRepos bool `json:"force_delete_repos,omitempty"` - Home string `json:"home,omitempty"` - Id string `json:"id,omitempty"` - Repos string `json:"repos,omitempty"` - UserName string `json:"user_name"` - WorkspaceAccess bool `json:"workspace_access,omitempty"` - WorkspaceConsume bool `json:"workspace_consume,omitempty"` + AclPrincipalId string `json:"acl_principal_id,omitempty"` + Active bool `json:"active,omitempty"` + AllowClusterCreate bool `json:"allow_cluster_create,omitempty"` + AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"` + Api string `json:"api,omitempty"` + DatabricksSqlAccess bool `json:"databricks_sql_access,omitempty"` + DisableAsUserDeletion bool `json:"disable_as_user_deletion,omitempty"` + DisplayName string `json:"display_name,omitempty"` + ExternalId string `json:"external_id,omitempty"` + Force bool `json:"force,omitempty"` + ForceDeleteHomeDir bool `json:"force_delete_home_dir,omitempty"` + ForceDeleteRepos bool `json:"force_delete_repos,omitempty"` + Home string `json:"home,omitempty"` + Id string `json:"id,omitempty"` + Repos string `json:"repos,omitempty"` + UserName string `json:"user_name"` + WorkspaceAccess bool `json:"workspace_access,omitempty"` + WorkspaceConsume bool `json:"workspace_consume,omitempty"` + ProviderConfig *ResourceUserProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_user_instance_profile.go b/bundle/internal/tf/schema/resource_user_instance_profile.go index d5cdaf64a47..59b27276fe5 100644 --- a/bundle/internal/tf/schema/resource_user_instance_profile.go +++ b/bundle/internal/tf/schema/resource_user_instance_profile.go @@ -2,8 +2,14 @@ package schema +type ResourceUserInstanceProfileProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceUserInstanceProfile struct { - Id string `json:"id,omitempty"` - InstanceProfileId string `json:"instance_profile_id"` - UserId string `json:"user_id"` + Api string `json:"api,omitempty"` + Id string `json:"id,omitempty"` + InstanceProfileId string `json:"instance_profile_id"` + UserId string `json:"user_id"` + ProviderConfig *ResourceUserInstanceProfileProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_user_role.go b/bundle/internal/tf/schema/resource_user_role.go index 6420f7515d6..75b88ba1358 100644 --- a/bundle/internal/tf/schema/resource_user_role.go +++ b/bundle/internal/tf/schema/resource_user_role.go @@ -2,8 +2,14 @@ package schema +type ResourceUserRoleProviderConfig struct { + WorkspaceId string `json:"workspace_id"` +} + type ResourceUserRole struct { - Id string `json:"id,omitempty"` - Role string `json:"role"` - UserId string `json:"user_id"` + Api string `json:"api,omitempty"` + Id string `json:"id,omitempty"` + Role string `json:"role"` + UserId string `json:"user_id"` + ProviderConfig *ResourceUserRoleProviderConfig `json:"provider_config,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_vector_search_index.go b/bundle/internal/tf/schema/resource_vector_search_index.go index 96cfafac04a..bd45fe44c07 100644 --- a/bundle/internal/tf/schema/resource_vector_search_index.go +++ b/bundle/internal/tf/schema/resource_vector_search_index.go @@ -47,6 +47,7 @@ type ResourceVectorSearchIndex struct { Creator string `json:"creator,omitempty"` EndpointName string `json:"endpoint_name"` Id string `json:"id,omitempty"` + IndexSubtype string `json:"index_subtype,omitempty"` IndexType string `json:"index_type"` Name string `json:"name"` PrimaryKey string `json:"primary_key"` diff --git a/bundle/internal/tf/schema/resource_workspace_setting_v2.go b/bundle/internal/tf/schema/resource_workspace_setting_v2.go index 83fa36cffbf..6384c00ae46 100644 --- a/bundle/internal/tf/schema/resource_workspace_setting_v2.go +++ b/bundle/internal/tf/schema/resource_workspace_setting_v2.go @@ -93,7 +93,8 @@ type ResourceWorkspaceSettingV2EffectivePersonalCompute struct { } type ResourceWorkspaceSettingV2EffectiveRestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type ResourceWorkspaceSettingV2EffectiveStringVal struct { @@ -113,7 +114,8 @@ type ResourceWorkspaceSettingV2ProviderConfig struct { } type ResourceWorkspaceSettingV2RestrictWorkspaceAdmins struct { - Status string `json:"status"` + DisableGovTagCreation bool `json:"disable_gov_tag_creation,omitempty"` + Status string `json:"status"` } type ResourceWorkspaceSettingV2StringVal struct { diff --git a/bundle/internal/tf/schema/resources.go b/bundle/internal/tf/schema/resources.go index 3a50d1b4b6a..a12b555092f 100644 --- a/bundle/internal/tf/schema/resources.go +++ b/bundle/internal/tf/schema/resources.go @@ -48,6 +48,8 @@ type Resources struct { EnhancedSecurityMonitoringWorkspaceSetting map[string]any `json:"databricks_enhanced_security_monitoring_workspace_setting,omitempty"` Entitlements map[string]any `json:"databricks_entitlements,omitempty"` EntityTagAssignment map[string]any `json:"databricks_entity_tag_assignment,omitempty"` + EnvironmentsDefaultWorkspaceBaseEnvironment map[string]any `json:"databricks_environments_default_workspace_base_environment,omitempty"` + EnvironmentsWorkspaceBaseEnvironment map[string]any `json:"databricks_environments_workspace_base_environment,omitempty"` ExternalLocation map[string]any `json:"databricks_external_location,omitempty"` ExternalMetadata map[string]any `json:"databricks_external_metadata,omitempty"` FeatureEngineeringFeature map[string]any `json:"databricks_feature_engineering_feature,omitempty"` @@ -102,9 +104,12 @@ type Resources struct { Pipeline map[string]any `json:"databricks_pipeline,omitempty"` PolicyInfo map[string]any `json:"databricks_policy_info,omitempty"` PostgresBranch map[string]any `json:"databricks_postgres_branch,omitempty"` + PostgresCatalog map[string]any `json:"databricks_postgres_catalog,omitempty"` PostgresDatabase map[string]any `json:"databricks_postgres_database,omitempty"` PostgresEndpoint map[string]any `json:"databricks_postgres_endpoint,omitempty"` PostgresProject map[string]any `json:"databricks_postgres_project,omitempty"` + PostgresRole map[string]any `json:"databricks_postgres_role,omitempty"` + PostgresSyncedTable map[string]any `json:"databricks_postgres_synced_table,omitempty"` Provider map[string]any `json:"databricks_provider,omitempty"` QualityMonitor map[string]any `json:"databricks_quality_monitor,omitempty"` QualityMonitorV2 map[string]any `json:"databricks_quality_monitor_v2,omitempty"` @@ -197,109 +202,114 @@ func NewResources() *Resources { DisableLegacyFeaturesSetting: make(map[string]any), Endpoint: make(map[string]any), EnhancedSecurityMonitoringWorkspaceSetting: make(map[string]any), - Entitlements: make(map[string]any), - EntityTagAssignment: make(map[string]any), - ExternalLocation: make(map[string]any), - ExternalMetadata: make(map[string]any), - FeatureEngineeringFeature: make(map[string]any), - FeatureEngineeringKafkaConfig: make(map[string]any), - FeatureEngineeringMaterializedFeature: make(map[string]any), - File: make(map[string]any), - GitCredential: make(map[string]any), - GlobalInitScript: make(map[string]any), - Grant: make(map[string]any), - Grants: make(map[string]any), - Group: make(map[string]any), - GroupInstanceProfile: make(map[string]any), - GroupMember: make(map[string]any), - GroupRole: make(map[string]any), - InstancePool: make(map[string]any), - InstanceProfile: make(map[string]any), - IpAccessList: make(map[string]any), - Job: make(map[string]any), - KnowledgeAssistant: make(map[string]any), - KnowledgeAssistantKnowledgeSource: make(map[string]any), - LakehouseMonitor: make(map[string]any), - Library: make(map[string]any), - MaterializedFeaturesFeatureTag: make(map[string]any), - Metastore: make(map[string]any), - MetastoreAssignment: make(map[string]any), - MetastoreDataAccess: make(map[string]any), - MlflowExperiment: make(map[string]any), - MlflowModel: make(map[string]any), - MlflowWebhook: make(map[string]any), - ModelServing: make(map[string]any), - ModelServingProvisionedThroughput: make(map[string]any), - Mount: make(map[string]any), - MwsCredentials: make(map[string]any), - MwsCustomerManagedKeys: make(map[string]any), - MwsLogDelivery: make(map[string]any), - MwsNccBinding: make(map[string]any), - MwsNccPrivateEndpointRule: make(map[string]any), - MwsNetworkConnectivityConfig: make(map[string]any), - MwsNetworks: make(map[string]any), - MwsPermissionAssignment: make(map[string]any), - MwsPrivateAccessSettings: make(map[string]any), - MwsStorageConfigurations: make(map[string]any), - MwsVpcEndpoint: make(map[string]any), - MwsWorkspaces: make(map[string]any), - Notebook: make(map[string]any), - NotificationDestination: make(map[string]any), - OboToken: make(map[string]any), - OnlineStore: make(map[string]any), - OnlineTable: make(map[string]any), - PermissionAssignment: make(map[string]any), - Permissions: make(map[string]any), - Pipeline: make(map[string]any), - PolicyInfo: make(map[string]any), - PostgresBranch: make(map[string]any), - PostgresDatabase: make(map[string]any), - PostgresEndpoint: make(map[string]any), - PostgresProject: make(map[string]any), - Provider: make(map[string]any), - QualityMonitor: make(map[string]any), - QualityMonitorV2: make(map[string]any), - Query: make(map[string]any), - Recipient: make(map[string]any), - RegisteredModel: make(map[string]any), - Repo: make(map[string]any), - RestrictWorkspaceAdminsSetting: make(map[string]any), - RfaAccessRequestDestinations: make(map[string]any), - Schema: make(map[string]any), - Secret: make(map[string]any), - SecretAcl: make(map[string]any), - SecretScope: make(map[string]any), - ServicePrincipal: make(map[string]any), - ServicePrincipalFederationPolicy: make(map[string]any), - ServicePrincipalRole: make(map[string]any), - ServicePrincipalSecret: make(map[string]any), - Share: make(map[string]any), - SqlAlert: make(map[string]any), - SqlDashboard: make(map[string]any), - SqlEndpoint: make(map[string]any), - SqlGlobalConfig: make(map[string]any), - SqlPermissions: make(map[string]any), - SqlQuery: make(map[string]any), - SqlTable: make(map[string]any), - SqlVisualization: make(map[string]any), - SqlWidget: make(map[string]any), - StorageCredential: make(map[string]any), - SystemSchema: make(map[string]any), - Table: make(map[string]any), - TagPolicy: make(map[string]any), - Token: make(map[string]any), - User: make(map[string]any), - UserInstanceProfile: make(map[string]any), - UserRole: make(map[string]any), - VectorSearchEndpoint: make(map[string]any), - VectorSearchIndex: make(map[string]any), - Volume: make(map[string]any), - WarehousesDefaultWarehouseOverride: make(map[string]any), - WorkspaceBinding: make(map[string]any), - WorkspaceConf: make(map[string]any), - WorkspaceEntityTagAssignment: make(map[string]any), - WorkspaceFile: make(map[string]any), - WorkspaceNetworkOption: make(map[string]any), - WorkspaceSettingV2: make(map[string]any), + Entitlements: make(map[string]any), + EntityTagAssignment: make(map[string]any), + EnvironmentsDefaultWorkspaceBaseEnvironment: make(map[string]any), + EnvironmentsWorkspaceBaseEnvironment: make(map[string]any), + ExternalLocation: make(map[string]any), + ExternalMetadata: make(map[string]any), + FeatureEngineeringFeature: make(map[string]any), + FeatureEngineeringKafkaConfig: make(map[string]any), + FeatureEngineeringMaterializedFeature: make(map[string]any), + File: make(map[string]any), + GitCredential: make(map[string]any), + GlobalInitScript: make(map[string]any), + Grant: make(map[string]any), + Grants: make(map[string]any), + Group: make(map[string]any), + GroupInstanceProfile: make(map[string]any), + GroupMember: make(map[string]any), + GroupRole: make(map[string]any), + InstancePool: make(map[string]any), + InstanceProfile: make(map[string]any), + IpAccessList: make(map[string]any), + Job: make(map[string]any), + KnowledgeAssistant: make(map[string]any), + KnowledgeAssistantKnowledgeSource: make(map[string]any), + LakehouseMonitor: make(map[string]any), + Library: make(map[string]any), + MaterializedFeaturesFeatureTag: make(map[string]any), + Metastore: make(map[string]any), + MetastoreAssignment: make(map[string]any), + MetastoreDataAccess: make(map[string]any), + MlflowExperiment: make(map[string]any), + MlflowModel: make(map[string]any), + MlflowWebhook: make(map[string]any), + ModelServing: make(map[string]any), + ModelServingProvisionedThroughput: make(map[string]any), + Mount: make(map[string]any), + MwsCredentials: make(map[string]any), + MwsCustomerManagedKeys: make(map[string]any), + MwsLogDelivery: make(map[string]any), + MwsNccBinding: make(map[string]any), + MwsNccPrivateEndpointRule: make(map[string]any), + MwsNetworkConnectivityConfig: make(map[string]any), + MwsNetworks: make(map[string]any), + MwsPermissionAssignment: make(map[string]any), + MwsPrivateAccessSettings: make(map[string]any), + MwsStorageConfigurations: make(map[string]any), + MwsVpcEndpoint: make(map[string]any), + MwsWorkspaces: make(map[string]any), + Notebook: make(map[string]any), + NotificationDestination: make(map[string]any), + OboToken: make(map[string]any), + OnlineStore: make(map[string]any), + OnlineTable: make(map[string]any), + PermissionAssignment: make(map[string]any), + Permissions: make(map[string]any), + Pipeline: make(map[string]any), + PolicyInfo: make(map[string]any), + PostgresBranch: make(map[string]any), + PostgresCatalog: make(map[string]any), + PostgresDatabase: make(map[string]any), + PostgresEndpoint: make(map[string]any), + PostgresProject: make(map[string]any), + PostgresRole: make(map[string]any), + PostgresSyncedTable: make(map[string]any), + Provider: make(map[string]any), + QualityMonitor: make(map[string]any), + QualityMonitorV2: make(map[string]any), + Query: make(map[string]any), + Recipient: make(map[string]any), + RegisteredModel: make(map[string]any), + Repo: make(map[string]any), + RestrictWorkspaceAdminsSetting: make(map[string]any), + RfaAccessRequestDestinations: make(map[string]any), + Schema: make(map[string]any), + Secret: make(map[string]any), + SecretAcl: make(map[string]any), + SecretScope: make(map[string]any), + ServicePrincipal: make(map[string]any), + ServicePrincipalFederationPolicy: make(map[string]any), + ServicePrincipalRole: make(map[string]any), + ServicePrincipalSecret: make(map[string]any), + Share: make(map[string]any), + SqlAlert: make(map[string]any), + SqlDashboard: make(map[string]any), + SqlEndpoint: make(map[string]any), + SqlGlobalConfig: make(map[string]any), + SqlPermissions: make(map[string]any), + SqlQuery: make(map[string]any), + SqlTable: make(map[string]any), + SqlVisualization: make(map[string]any), + SqlWidget: make(map[string]any), + StorageCredential: make(map[string]any), + SystemSchema: make(map[string]any), + Table: make(map[string]any), + TagPolicy: make(map[string]any), + Token: make(map[string]any), + User: make(map[string]any), + UserInstanceProfile: make(map[string]any), + UserRole: make(map[string]any), + VectorSearchEndpoint: make(map[string]any), + VectorSearchIndex: make(map[string]any), + Volume: make(map[string]any), + WarehousesDefaultWarehouseOverride: make(map[string]any), + WorkspaceBinding: make(map[string]any), + WorkspaceConf: make(map[string]any), + WorkspaceEntityTagAssignment: make(map[string]any), + WorkspaceFile: make(map[string]any), + WorkspaceNetworkOption: make(map[string]any), + WorkspaceSettingV2: make(map[string]any), } } diff --git a/bundle/internal/tf/schema/root.go b/bundle/internal/tf/schema/root.go index 4c19c2c4a4b..c0d0ad067cc 100644 --- a/bundle/internal/tf/schema/root.go +++ b/bundle/internal/tf/schema/root.go @@ -21,9 +21,9 @@ type Root struct { const ProviderHost = "registry.terraform.io" const ProviderSource = "databricks/databricks" -const ProviderVersion = "1.111.0" -const ProviderChecksumLinuxAmd64 = "c1b46bbaf5c4a0b253309dad072e05025e24731536719d4408bacd48dc0ccfd9" -const ProviderChecksumLinuxArm64 = "ce379c424009b01ec4762dee4d0db27cfc554d921b55a0af8e4203b3652259e9" +const ProviderVersion = "1.113.0" +const ProviderChecksumLinuxAmd64 = "4f5caaf7bea4c435ae97c28c45086c213e182b67d1fe9b13f4e91b9e0b6ad7be" +const ProviderChecksumLinuxArm64 = "69693b0bcbab3a184deb2744e8b90d5a9d1f7e19cdc414bc54a87280e37d65a9" func NewRoot() *Root { return &Root{ diff --git a/bundle/internal/validation/enum.go b/bundle/internal/validation/enum.go index d0c201e9097..df58193c898 100644 --- a/bundle/internal/validation/enum.go +++ b/bundle/internal/validation/enum.go @@ -2,13 +2,15 @@ package main import ( "bytes" + "cmp" "errors" "fmt" "go/format" + "maps" "os" "path/filepath" "reflect" - "sort" + "slices" "text/template" "github.com/databricks/cli/bundle/config" @@ -181,11 +183,7 @@ func filterTargetsAndEnvironmentsEnum(patterns map[string][]EnumPatternInfo) map // sortGroupedPatterns sorts patterns within each group and returns them as a sorted slice func sortGroupedPatternsEnum(groupedPatterns map[string][]EnumPatternInfo) [][]EnumPatternInfo { // Get sorted group keys - groupKeys := make([]string, 0, len(groupedPatterns)) - for key := range groupedPatterns { - groupKeys = append(groupKeys, key) - } - sort.Strings(groupKeys) + groupKeys := slices.Sorted(maps.Keys(groupedPatterns)) // Build sorted result result := make([][]EnumPatternInfo, 0, len(groupKeys)) @@ -193,8 +191,8 @@ func sortGroupedPatternsEnum(groupedPatterns map[string][]EnumPatternInfo) [][]E patterns := groupedPatterns[key] // Sort patterns within each group by pattern - sort.Slice(patterns, func(i, j int) bool { - return patterns[i].Pattern < patterns[j].Pattern + slices.SortFunc(patterns, func(a, b EnumPatternInfo) int { + return cmp.Compare(a.Pattern, b.Pattern) }) result = append(result, patterns) @@ -205,7 +203,7 @@ func sortGroupedPatternsEnum(groupedPatterns map[string][]EnumPatternInfo) [][]E // enumFields returns grouped enum field patterns for validation func enumFields() ([][]EnumPatternInfo, error) { - patterns, err := extractEnumFields(reflect.TypeOf(config.Root{})) + patterns, err := extractEnumFields(reflect.TypeFor[config.Root]()) if err != nil { return nil, err } diff --git a/bundle/internal/validation/generated/enum_fields.go b/bundle/internal/validation/generated/enum_fields.go index c1e098ed80f..33632c268f8 100644 --- a/bundle/internal/validation/generated/enum_fields.go +++ b/bundle/internal/validation/generated/enum_fields.go @@ -27,6 +27,7 @@ var EnumFields = map[string][]string{ "resources.apps.*.pending_deployment.mode": {"AUTO_SYNC", "SNAPSHOT"}, "resources.apps.*.pending_deployment.status.state": {"CANCELLED", "FAILED", "IN_PROGRESS", "SUCCEEDED"}, "resources.apps.*.permissions[*].level": {"CAN_MANAGE", "CAN_USE"}, + "resources.apps.*.resources[*].app.permission": {"CAN_USE"}, "resources.apps.*.resources[*].database.permission": {"CAN_CONNECT_AND_CREATE"}, "resources.apps.*.resources[*].experiment.permission": {"CAN_EDIT", "CAN_MANAGE", "CAN_READ"}, "resources.apps.*.resources[*].genie_space.permission": {"CAN_EDIT", "CAN_MANAGE", "CAN_RUN", "CAN_VIEW"}, @@ -138,21 +139,37 @@ var EnumFields = map[string][]string{ "resources.models.*.permissions[*].level": {"CAN_EDIT", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_STAGING_VERSIONS", "CAN_READ"}, - "resources.pipelines.*.clusters[*].autoscale.mode": {"ENHANCED", "LEGACY"}, - "resources.pipelines.*.clusters[*].aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, - "resources.pipelines.*.clusters[*].aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, - "resources.pipelines.*.clusters[*].azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, - "resources.pipelines.*.clusters[*].gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, - "resources.pipelines.*.deployment.kind": {"BUNDLE"}, - "resources.pipelines.*.ingestion_definition.connector_type": {"CDC", "QUERY_BASED"}, - "resources.pipelines.*.ingestion_definition.full_refresh_window.days_of_week[*]": {"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"}, - "resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, - "resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, - "resources.pipelines.*.ingestion_definition.objects[*].table.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, - "resources.pipelines.*.ingestion_definition.source_type": {"BIGQUERY", "DYNAMICS365", "FOREIGN_CATALOG", "GA4_RAW_DATA", "MANAGED_POSTGRESQL", "MYSQL", "NETSUITE", "ORACLE", "POSTGRESQL", "SALESFORCE", "SERVICENOW", "SHAREPOINT", "SQLSERVER", "TERADATA", "WORKDAY_RAAS"}, - "resources.pipelines.*.ingestion_definition.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, - "resources.pipelines.*.permissions[*].level": {"CAN_MANAGE", "CAN_RUN", "CAN_VIEW", "IS_OWNER"}, - "resources.pipelines.*.restart_window.days_of_week[*]": {"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"}, + "resources.pipelines.*.clusters[*].autoscale.mode": {"ENHANCED", "LEGACY"}, + "resources.pipelines.*.clusters[*].aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, + "resources.pipelines.*.clusters[*].aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, + "resources.pipelines.*.clusters[*].azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, + "resources.pipelines.*.clusters[*].gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, + "resources.pipelines.*.deployment.kind": {"BUNDLE"}, + "resources.pipelines.*.ingestion_definition.connector_type": {"CDC", "QUERY_BASED"}, + "resources.pipelines.*.ingestion_definition.full_refresh_window.days_of_week[*]": {"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"}, + "resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.entity_type": {"FILE", "FILE_METADATA", "PERMISSION"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.format": {"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode": {"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.entity_type": {"FILE", "FILE_METADATA", "LIST", "PERMISSION"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.format": {"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode": {"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.data_level": {"AUCTION_AD", "AUCTION_ADGROUP", "AUCTION_ADVERTISER", "AUCTION_CAMPAIGN"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.tiktok_ads_options.report_type": {"AUDIENCE", "BASIC", "BUSINESS_CENTER", "DSA", "GMV_MAX", "PLAYABLE_AD"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.entity_type": {"FILE", "FILE_METADATA", "PERMISSION"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.format": {"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.gdrive_options.file_ingestion_options.schema_evolution_mode": {"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.entity_type": {"FILE", "FILE_METADATA", "LIST", "PERMISSION"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.format": {"AVRO", "BINARYFILE", "CSV", "EXCEL", "JSON", "ORC", "PARQUET", "XML"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sharepoint_options.file_ingestion_options.schema_evolution_mode": {"ADD_NEW_COLUMNS", "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", "FAIL_ON_NEW_COLUMNS", "NONE", "RESCUE"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.data_level": {"AUCTION_AD", "AUCTION_ADGROUP", "AUCTION_ADVERTISER", "AUCTION_CAMPAIGN"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.tiktok_ads_options.report_type": {"AUDIENCE", "BASIC", "BUSINESS_CENTER", "DSA", "GMV_MAX", "PLAYABLE_AD"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, + "resources.pipelines.*.ingestion_definition.source_type": {"BIGQUERY", "DYNAMICS365", "FOREIGN_CATALOG", "GA4_RAW_DATA", "GOOGLE_DRIVE", "MANAGED_POSTGRESQL", "MYSQL", "NETSUITE", "ORACLE", "POSTGRESQL", "SALESFORCE", "SERVICENOW", "SHAREPOINT", "SQLSERVER", "TERADATA", "WORKDAY_RAAS"}, + "resources.pipelines.*.ingestion_definition.table_configuration.scd_type": {"APPEND_ONLY", "SCD_TYPE_1", "SCD_TYPE_2"}, + "resources.pipelines.*.permissions[*].level": {"CAN_MANAGE", "CAN_RUN", "CAN_VIEW", "IS_OWNER"}, + "resources.pipelines.*.restart_window.days_of_week[*]": {"FRIDAY", "MONDAY", "SATURDAY", "SUNDAY", "THURSDAY", "TUESDAY", "WEDNESDAY"}, "resources.postgres_endpoints.*.endpoint_type": {"ENDPOINT_TYPE_READ_ONLY", "ENDPOINT_TYPE_READ_WRITE"}, @@ -180,6 +197,9 @@ var EnumFields = map[string][]string{ "resources.synced_database_tables.*.spec.scheduling_policy": {"CONTINUOUS", "SNAPSHOT", "TRIGGERED"}, "resources.synced_database_tables.*.unity_catalog_provisioning_state": {"ACTIVE", "DEGRADED", "DELETING", "FAILED", "PROVISIONING", "UPDATING"}, + "resources.vector_search_endpoints.*.endpoint_type": {"STANDARD", "STORAGE_OPTIMIZED"}, + "resources.vector_search_endpoints.*.permissions[*].level": {"CAN_ATTACH_TO", "CAN_BIND", "CAN_CREATE", "CAN_CREATE_APP", "CAN_EDIT", "CAN_EDIT_METADATA", "CAN_MANAGE", "CAN_MANAGE_PRODUCTION_VERSIONS", "CAN_MANAGE_RUN", "CAN_MANAGE_STAGING_VERSIONS", "CAN_MONITOR", "CAN_MONITOR_ONLY", "CAN_QUERY", "CAN_READ", "CAN_RESTART", "CAN_RUN", "CAN_USE", "CAN_VIEW", "CAN_VIEW_METADATA", "IS_OWNER"}, + "resources.volumes.*.grants[*].privileges[*]": {"ACCESS", "ALL_PRIVILEGES", "APPLY_TAG", "BROWSE", "CREATE", "CREATE_CATALOG", "CREATE_CLEAN_ROOM", "CREATE_CONNECTION", "CREATE_EXTERNAL_LOCATION", "CREATE_EXTERNAL_TABLE", "CREATE_EXTERNAL_VOLUME", "CREATE_FOREIGN_CATALOG", "CREATE_FOREIGN_SECURABLE", "CREATE_FUNCTION", "CREATE_MANAGED_STORAGE", "CREATE_MATERIALIZED_VIEW", "CREATE_MODEL", "CREATE_PROVIDER", "CREATE_RECIPIENT", "CREATE_SCHEMA", "CREATE_SERVICE_CREDENTIAL", "CREATE_SHARE", "CREATE_STORAGE_CREDENTIAL", "CREATE_TABLE", "CREATE_VIEW", "CREATE_VOLUME", "EXECUTE", "EXECUTE_CLEAN_ROOM_TASK", "EXTERNAL_USE_SCHEMA", "MANAGE", "MANAGE_ALLOWLIST", "MODIFY", "MODIFY_CLEAN_ROOM", "READ_FILES", "READ_PRIVATE_FILES", "READ_VOLUME", "REFRESH", "SELECT", "SET_SHARE_PERMISSION", "USAGE", "USE_CATALOG", "USE_CONNECTION", "USE_MARKETPLACE_ASSETS", "USE_PROVIDER", "USE_RECIPIENT", "USE_SCHEMA", "USE_SHARE", "WRITE_FILES", "WRITE_PRIVATE_FILES", "WRITE_VOLUME"}, "resources.volumes.*.volume_type": {"EXTERNAL", "MANAGED"}, diff --git a/bundle/internal/validation/generated/required_fields.go b/bundle/internal/validation/generated/required_fields.go index d90345f83f1..db86398accb 100644 --- a/bundle/internal/validation/generated/required_fields.go +++ b/bundle/internal/validation/generated/required_fields.go @@ -37,6 +37,7 @@ var RequiredFields = map[string][]string{ "resources.apps.*.telemetry_export_destinations[*].unity_catalog": {"logs_table", "metrics_table", "traces_table"}, "resources.catalogs.*": {"name"}, + "resources.catalogs.*.managed_encryption_settings.azure_encryption_settings": {"azure_tenant_id"}, "resources.clusters.*.cluster_log_conf.dbfs": {"destination"}, "resources.clusters.*.cluster_log_conf.s3": {"destination"}, @@ -206,8 +207,10 @@ var RequiredFields = map[string][]string{ "resources.pipelines.*.ingestion_definition.objects[*].report": {"destination_catalog", "destination_schema", "source_url"}, "resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.ingestion_definition.objects[*].schema": {"destination_catalog", "destination_schema", "source_schema"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.google_ads_options": {"manager_account_id"}, "resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.ingestion_definition.objects[*].table": {"destination_catalog", "destination_schema", "source_table"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.google_ads_options": {"manager_account_id"}, "resources.pipelines.*.ingestion_definition.objects[*].table.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.ingestion_definition.table_configuration.auto_full_refresh_policy": {"enabled"}, "resources.pipelines.*.libraries[*].maven": {"coordinates"}, @@ -238,6 +241,9 @@ var RequiredFields = map[string][]string{ "resources.synced_database_tables.*": {"name"}, + "resources.vector_search_endpoints.*": {"endpoint_type", "name"}, + "resources.vector_search_endpoints.*.permissions[*]": {"level"}, + "resources.volumes.*": {"catalog_name", "name", "schema_name", "volume_type"}, "scripts.*": {"content"}, diff --git a/bundle/internal/validation/required.go b/bundle/internal/validation/required.go index 584f63ba8dd..045ac32257a 100644 --- a/bundle/internal/validation/required.go +++ b/bundle/internal/validation/required.go @@ -2,12 +2,14 @@ package main import ( "bytes" + "cmp" "fmt" "go/format" + "maps" "os" "path/filepath" "reflect" - "sort" + "slices" "strings" "text/template" @@ -137,11 +139,7 @@ func filterTargetsAndEnvironments(patterns map[string][]RequiredPatternInfo) map // sortGroupedPatterns sorts patterns within each group and returns them as a sorted slice func sortGroupedPatterns(groupedPatterns map[string][]RequiredPatternInfo) [][]RequiredPatternInfo { // Get sorted group keys - groupKeys := make([]string, 0, len(groupedPatterns)) - for key := range groupedPatterns { - groupKeys = append(groupKeys, key) - } - sort.Strings(groupKeys) + groupKeys := slices.Sorted(maps.Keys(groupedPatterns)) // Build sorted result result := make([][]RequiredPatternInfo, 0, len(groupKeys)) @@ -149,8 +147,8 @@ func sortGroupedPatterns(groupedPatterns map[string][]RequiredPatternInfo) [][]R patterns := groupedPatterns[key] // Sort patterns within each group by parent path - sort.Slice(patterns, func(i, j int) bool { - return patterns[i].Parent < patterns[j].Parent + slices.SortFunc(patterns, func(a, b RequiredPatternInfo) int { + return cmp.Compare(a.Parent, b.Parent) }) result = append(result, patterns) @@ -161,7 +159,7 @@ func sortGroupedPatterns(groupedPatterns map[string][]RequiredPatternInfo) [][]R // RequiredFields returns grouped required field patterns for validation func requiredFields() ([][]RequiredPatternInfo, error) { - patterns, err := extractRequiredFields(reflect.TypeOf(config.Root{})) + patterns, err := extractRequiredFields(reflect.TypeFor[config.Root]()) if err != nil { return nil, err } diff --git a/bundle/libraries/filer.go b/bundle/libraries/filer.go index 4b62da59352..762732262be 100644 --- a/bundle/libraries/filer.go +++ b/bundle/libraries/filer.go @@ -29,10 +29,10 @@ func GetFilerForLibraries(ctx context.Context, b *bundle.Bundle) (filer.Filer, s switch { case IsVolumesPath(artifactPath): - return filerForVolume(b, uploadPath) + return filerForVolume(ctx, b, uploadPath) default: - return filerForWorkspace(b, uploadPath) + return filerForWorkspace(ctx, b, uploadPath) } } @@ -46,10 +46,10 @@ func GetFilerForLibrariesCleanup(ctx context.Context, b *bundle.Bundle) (filer.F switch { case IsVolumesPath(artifactPath): - return filerForVolume(b, artifactPath) + return filerForVolume(ctx, b, artifactPath) default: - return filerForWorkspace(b, artifactPath) + return filerForWorkspace(ctx, b, artifactPath) } } diff --git a/bundle/libraries/filer_volume.go b/bundle/libraries/filer_volume.go index 13254eec3a7..f4b5f51f0c2 100644 --- a/bundle/libraries/filer_volume.go +++ b/bundle/libraries/filer_volume.go @@ -1,13 +1,15 @@ package libraries import ( + "context" + "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/filer" ) -func filerForVolume(b *bundle.Bundle, uploadPath string) (filer.Filer, string, diag.Diagnostics) { - w := b.WorkspaceClient() +func filerForVolume(ctx context.Context, b *bundle.Bundle, uploadPath string) (filer.Filer, string, diag.Diagnostics) { + w := b.WorkspaceClient(ctx) f, err := filer.NewFilesClient(w, uploadPath) return f, uploadPath, diag.FromErr(err) } diff --git a/bundle/libraries/filer_workspace.go b/bundle/libraries/filer_workspace.go index 0c185d69d8a..3d223c342f8 100644 --- a/bundle/libraries/filer_workspace.go +++ b/bundle/libraries/filer_workspace.go @@ -1,12 +1,14 @@ package libraries import ( + "context" + "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/filer" ) -func filerForWorkspace(b *bundle.Bundle, uploadPath string) (filer.Filer, string, diag.Diagnostics) { - f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(), uploadPath) +func filerForWorkspace(ctx context.Context, b *bundle.Bundle, uploadPath string) (filer.Filer, string, diag.Diagnostics) { + f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(ctx), uploadPath) return f, uploadPath, diag.FromErr(err) } diff --git a/bundle/libraries/libraries.go b/bundle/libraries/libraries.go index c5eb9eb8821..ba7d4272a65 100644 --- a/bundle/libraries/libraries.go +++ b/bundle/libraries/libraries.go @@ -1,6 +1,8 @@ package libraries import ( + "slices" + "github.com/databricks/cli/bundle" "github.com/databricks/databricks-sdk-go/service/jobs" ) @@ -34,10 +36,8 @@ func isEnvsWithLocalLibraries(envs []jobs.JobEnvironment) bool { continue } - for _, l := range e.Spec.Dependencies { - if IsLibraryLocal(l) { - return true - } + if slices.ContainsFunc(e.Spec.Dependencies, IsLibraryLocal) { + return true } } diff --git a/bundle/libraries/local_path.go b/bundle/libraries/local_path.go index 1a01fa2a197..2f86481b162 100644 --- a/bundle/libraries/local_path.go +++ b/bundle/libraries/local_path.go @@ -45,7 +45,7 @@ func IsLocalPath(p string) bool { // IsLibraryLocal returns true if the specified library or environment dependency // should be interpreted as a local path. // We use this to check if the dependency in environment spec is local or that library is local. -// We can't use IsLocalPath beacuse environment dependencies can be +// We can't use IsLocalPath because environment dependencies can be // a pypi package name which can be misinterpreted as a local path by IsLocalPath. func IsLibraryLocal(dep string) bool { if dep == "" { @@ -96,7 +96,7 @@ func IsLocalPathInPipFlag(dep string) (string, string, bool) { } func containsPipFlag(input string) bool { - // Trailing space means the the flag takes an argument or there's multiple arguments in input + // Trailing space means the flag takes an argument or there's multiple arguments in input // Alternatively it could be a flag with no argument and no space after it // For example: -r myfile.txt or --index-url http://myindexurl.com or -i re := regexp.MustCompile(`(^|\s+)--?[a-zA-Z0-9-]+(([\s|=]+)|$)`) diff --git a/bundle/libraries/match_test.go b/bundle/libraries/match_test.go index e19b8e1c7ca..d047785b332 100644 --- a/bundle/libraries/match_test.go +++ b/bundle/libraries/match_test.go @@ -29,7 +29,7 @@ func TestValidateEnvironments(t *testing.T) { Dependencies: []string{ "./wheel.whl", "simplejson", - "/Workspace/Users/foo@bar.com/artifacts/test.whl", + "/Workspace/Users/foo@bar.test/artifacts/test.whl", }, }, }, @@ -61,7 +61,7 @@ func TestValidateEnvironmentsNoFile(t *testing.T) { Dependencies: []string{ "./wheel.whl", "simplejson", - "/Workspace/Users/foo@bar.com/artifacts/test.whl", + "/Workspace/Users/foo@bar.test/artifacts/test.whl", }, }, }, @@ -96,7 +96,7 @@ func TestValidateTaskLibraries(t *testing.T) { Whl: "./wheel.whl", }, { - Whl: "/Workspace/Users/foo@bar.com/artifacts/test.whl", + Whl: "/Workspace/Users/foo@bar.test/artifacts/test.whl", }, }, }, @@ -129,7 +129,7 @@ func TestValidateTaskLibrariesNoFile(t *testing.T) { Whl: "./wheel.whl", }, { - Whl: "/Workspace/Users/foo@bar.com/artifacts/test.whl", + Whl: "/Workspace/Users/foo@bar.test/artifacts/test.whl", }, }, }, diff --git a/bundle/libraries/remote_path.go b/bundle/libraries/remote_path.go index d24387653a4..22784a63358 100644 --- a/bundle/libraries/remote_path.go +++ b/bundle/libraries/remote_path.go @@ -3,13 +3,14 @@ package libraries import ( "context" "fmt" + "maps" "path" "path/filepath" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" - "github.com/databricks/cli/libs/utils" ) // ReplaceWithRemotePath updates all the libraries paths to point to the remote location @@ -25,7 +26,7 @@ func ReplaceWithRemotePath(ctx context.Context, b *bundle.Bundle) (map[string][] return nil, diag.FromErr(err) } - sources := utils.SortedKeys(libs) + sources := slices.Sorted(maps.Keys(libs)) // Update all the config paths to point to the uploaded location err = b.Config.Mutate(func(v dyn.Value) (dyn.Value, error) { diff --git a/bundle/libraries/switch_to_patched_wheels.go b/bundle/libraries/switch_to_patched_wheels.go index d7f442bb58d..0a9d1846041 100644 --- a/bundle/libraries/switch_to_patched_wheels.go +++ b/bundle/libraries/switch_to_patched_wheels.go @@ -2,13 +2,14 @@ package libraries import ( "context" + "maps" "path/filepath" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/log" - "github.com/databricks/cli/libs/utils" ) type switchToPatchedWheels struct{} @@ -35,7 +36,7 @@ func (c switchToPatchedWheels) Apply(ctx context.Context, b *bundle.Bundle) diag log.Debugf(ctx, "Updating resources.jobs.%s.task[%d].libraries[%d].whl from %s to %s", jobName, taskInd, libInd, lib.Whl, repl) job.Tasks[taskInd].Libraries[libInd].Whl = repl } else { - log.Debugf(ctx, "Not updating resources.jobs.%s.task[%d].libraries[%d].whl from %s. Available replacements: %v", jobName, taskInd, libInd, lib.Whl, utils.SortedKeys(replacements)) + log.Debugf(ctx, "Not updating resources.jobs.%s.task[%d].libraries[%d].whl from %s. Available replacements: %v", jobName, taskInd, libInd, lib.Whl, slices.Sorted(maps.Keys(replacements))) } } @@ -49,7 +50,7 @@ func (c switchToPatchedWheels) Apply(ctx context.Context, b *bundle.Bundle) diag log.Debugf(ctx, "Updating resources.jobs.%s.task[%d].for_each_task.task.libraries[%d].whl from %s to %s", jobName, taskInd, libInd, lib.Whl, repl) foreachptr.Task.Libraries[libInd].Whl = repl } else { - log.Debugf(ctx, "Not updating resources.jobs.%s.task[%d].for_each_task.task.libraries[%d].whl from %s. Available replacements: %v", jobName, taskInd, libInd, lib.Whl, utils.SortedKeys(replacements)) + log.Debugf(ctx, "Not updating resources.jobs.%s.task[%d].for_each_task.task.libraries[%d].whl from %s. Available replacements: %v", jobName, taskInd, libInd, lib.Whl, slices.Sorted(maps.Keys(replacements))) } } } @@ -67,7 +68,7 @@ func (c switchToPatchedWheels) Apply(ctx context.Context, b *bundle.Bundle) diag log.Debugf(ctx, "Updating resources.jobs.%s.environments[%d].spec.dependencies[%d] from %s to %s", jobName, envInd, depInd, dep, repl) specptr.Dependencies[depInd] = repl } else { - log.Debugf(ctx, "Not updating resources.jobs.%s.environments[%d].spec.dependencies[%d] from %s. Available replacements: %v", jobName, envInd, depInd, dep, utils.SortedKeys(replacements)) + log.Debugf(ctx, "Not updating resources.jobs.%s.environments[%d].spec.dependencies[%d] from %s. Available replacements: %v", jobName, envInd, depInd, dep, slices.Sorted(maps.Keys(replacements))) } } } diff --git a/bundle/libraries/upload.go b/bundle/libraries/upload.go index 590adda4ff7..b292fe43b79 100644 --- a/bundle/libraries/upload.go +++ b/bundle/libraries/upload.go @@ -4,8 +4,10 @@ import ( "context" "errors" "fmt" + "maps" "os" "path/filepath" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/cmdio" @@ -13,7 +15,6 @@ import ( "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/filer" "github.com/databricks/cli/libs/log" - "github.com/databricks/cli/libs/utils" "golang.org/x/sync/errgroup" ) @@ -29,13 +30,6 @@ func Upload(libs map[string][]LocationToUpdate) bundle.Mutator { } } -func UploadWithClient(libs map[string][]LocationToUpdate, client filer.Filer) bundle.Mutator { - return &upload{ - libs: libs, - client: client, - } -} - type upload struct { client filer.Filer libs map[string][]LocationToUpdate @@ -58,7 +52,7 @@ func (u *upload) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { u.client = client } - sources := utils.SortedKeys(u.libs) + sources := slices.Sorted(maps.Keys(u.libs)) errs, errCtx := errgroup.WithContext(ctx) errs.SetLimit(maxFilesRequestsInFlight) diff --git a/bundle/metrics/metrics.go b/bundle/metrics/metrics.go index b564e2336b9..e8cded546fb 100644 --- a/bundle/metrics/metrics.go +++ b/bundle/metrics/metrics.go @@ -6,4 +6,5 @@ const ( ArtifactBuildCommandIsSet = "artifact_build_command_is_set" ArtifactFilesIsSet = "artifact_files_is_set" PresetsNamePrefixIsSet = "presets_name_prefix_is_set" + AppLifecycleStarted = "app_lifecycle_started" ) diff --git a/bundle/mutator_read_only.go b/bundle/mutator_read_only.go index d3157e74808..b4d55e41f16 100644 --- a/bundle/mutator_read_only.go +++ b/bundle/mutator_read_only.go @@ -32,7 +32,7 @@ func ApplyParallel(ctx context.Context, b *Bundle, mutators ...ReadOnlyMutator) contexts := make([]context.Context, len(mutators)) for ind, m := range mutators { - contexts[ind] = log.NewContext(ctx, log.GetLogger(ctx).With("mutator", m.Name())) + contexts[ind] = log.NewContext(ctx, log.GetLogger(ctx).With("mutator", m.Name())) //nolint:fatcontext // independent contexts from same parent, not nested // log right away to have deterministic order of log messages log.Debug(contexts[ind], "ApplyParallel") } diff --git a/bundle/permissions/permission_diagnostics.go b/bundle/permissions/permission_diagnostics.go index 21d997f1b7c..e25ccd5e724 100644 --- a/bundle/permissions/permission_diagnostics.go +++ b/bundle/permissions/permission_diagnostics.go @@ -3,7 +3,7 @@ package permissions import ( "context" "fmt" - "sort" + "slices" "strings" "github.com/databricks/cli/bundle" @@ -112,7 +112,7 @@ func analyzeBundlePermissions(b *bundle.Bundle) (bool, string) { assistance := "For assistance, contact the owners of this project." if otherManagers.Size() > 0 { list := otherManagers.Values() - sort.Strings(list) + slices.Sort(list) assistance = fmt.Sprintf( "For assistance, users or groups with appropriate permissions may include: %s.", strings.Join(list, ", "), diff --git a/bundle/permissions/validate_test.go b/bundle/permissions/validate_test.go index 5cd3f05104c..afaab38f3a1 100644 --- a/bundle/permissions/validate_test.go +++ b/bundle/permissions/validate_test.go @@ -44,7 +44,7 @@ func TestValidateSharedRootPermissionsForSharedError(t *testing.T) { RootPath: "/Workspace/Shared/foo/bar", }, Permissions: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, Resources: config.Resources{ Jobs: map[string]*resources.Job{ diff --git a/bundle/permissions/workspace_path_permissions.go b/bundle/permissions/workspace_path_permissions.go index 7d593d719ef..6ff1729196b 100644 --- a/bundle/permissions/workspace_path_permissions.go +++ b/bundle/permissions/workspace_path_permissions.go @@ -107,7 +107,7 @@ func convertWorkspaceObjectPermissionLevel(level workspace.WorkspaceObjectPermis func toString(p []resources.Permission) string { var sb strings.Builder for _, perm := range p { - sb.WriteString(fmt.Sprintf("- %s\n", perm.String())) + fmt.Fprintf(&sb, "- %s\n", perm.String()) } return sb.String() } diff --git a/bundle/permissions/workspace_path_permissions_test.go b/bundle/permissions/workspace_path_permissions_test.go index fe1ac6fd6a0..98e66089d34 100644 --- a/bundle/permissions/workspace_path_permissions_test.go +++ b/bundle/permissions/workspace_path_permissions_test.go @@ -18,11 +18,11 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { }{ { perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -32,11 +32,11 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { }, { perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -52,12 +52,12 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { }, { perms: []resources.Permission{ - {Level: CAN_VIEW, UserName: "foo@bar.com"}, + {Level: CAN_VIEW, UserName: "foo@bar.test"}, {Level: CAN_MANAGE, ServicePrincipalName: "sp.com"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_READ"}, }, @@ -67,11 +67,11 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { }, { perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -96,11 +96,11 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { }, { perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo2@bar.com", + UserName: "foo2@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -111,7 +111,7 @@ func TestWorkspacePathPermissionsCompare(t *testing.T) { Severity: diag.Warning, Summary: "workspace folder has permissions not configured in bundle", Detail: "The following permissions apply to the workspace folder at \"path\" " + - "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo2@bar.com\n\n" + + "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo2@bar.test\n\n" + "Add them to your bundle permissions or remove them from the folder.\n" + "See https://docs.databricks.com/dev-tools/bundles/permissions", }, @@ -136,11 +136,11 @@ func TestWorkspacePathPermissionsCompareWithHierarchy(t *testing.T) { { name: "bundle grants higher permission than workspace - no warning", perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_READ"}, }, @@ -151,11 +151,11 @@ func TestWorkspacePathPermissionsCompareWithHierarchy(t *testing.T) { { name: "bundle grants lower permission than workspace - warning", perms: []resources.Permission{ - {Level: CAN_VIEW, UserName: "foo@bar.com"}, + {Level: CAN_VIEW, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -166,7 +166,7 @@ func TestWorkspacePathPermissionsCompareWithHierarchy(t *testing.T) { Severity: diag.Warning, Summary: "workspace folder has permissions not configured in bundle", Detail: "The following permissions apply to the workspace folder at \"path\" " + - "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo@bar.com\n\n" + + "but are not configured in the bundle:\n- level: CAN_MANAGE, user_name: foo@bar.test\n\n" + "Add them to your bundle permissions or remove them from the folder.\n" + "See https://docs.databricks.com/dev-tools/bundles/permissions", }, @@ -175,11 +175,11 @@ func TestWorkspacePathPermissionsCompareWithHierarchy(t *testing.T) { { name: "bundle grants same permission as workspace - no warning", perms: []resources.Permission{ - {Level: CAN_MANAGE, UserName: "foo@bar.com"}, + {Level: CAN_MANAGE, UserName: "foo@bar.test"}, }, acl: []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_MANAGE"}, }, @@ -202,7 +202,7 @@ func TestWorkspacePathPermissionsDeduplication(t *testing.T) { // User has both inherited CAN_VIEW and explicit CAN_MANAGE acl := []workspace.WorkspaceObjectAccessControlResponse{ { - UserName: "foo@bar.com", + UserName: "foo@bar.test", AllPermissions: []workspace.WorkspaceObjectPermission{ {PermissionLevel: "CAN_READ"}, // inherited {PermissionLevel: "CAN_MANAGE"}, // explicit @@ -215,5 +215,5 @@ func TestWorkspacePathPermissionsDeduplication(t *testing.T) { // Should only have one permission entry with the highest level require.Len(t, wp.Permissions, 1) require.Equal(t, iam.PermissionLevel(CAN_MANAGE), wp.Permissions[0].Level) - require.Equal(t, "foo@bar.com", wp.Permissions[0].UserName) + require.Equal(t, "foo@bar.test", wp.Permissions[0].UserName) } diff --git a/bundle/permissions/workspace_root.go b/bundle/permissions/workspace_root.go index e8fc82813dd..78b9bfd704a 100644 --- a/bundle/permissions/workspace_root.go +++ b/bundle/permissions/workspace_root.go @@ -54,7 +54,7 @@ func giveAccessForWorkspaceRoot(ctx context.Context, b *bundle.Bundle) error { return nil } - w := b.WorkspaceClient().Workspace + w := b.WorkspaceClient(ctx).Workspace bundlePaths := paths.CollectUniqueWorkspacePathPrefixes(b.Config.Workspace) g, ctx := errgroup.WithContext(ctx) @@ -73,7 +73,7 @@ func setPermissions(ctx context.Context, w workspace.WorkspaceInterface, path st return nil } - obj, err := w.GetStatusByPath(ctx, path) + obj, err := w.GetStatusByPath(ctx, path) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { return err } diff --git a/bundle/permissions/workspace_root_test.go b/bundle/permissions/workspace_root_test.go index 1dd1c0cbfab..a3b5f5ac2d9 100644 --- a/bundle/permissions/workspace_root_test.go +++ b/bundle/permissions/workspace_root_test.go @@ -20,11 +20,11 @@ func TestApplyWorkspaceRootPermissions(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Users/foo@bar.com", - ArtifactPath: "/Users/foo@bar.com/artifacts", - FilePath: "/Users/foo@bar.com/files", - StatePath: "/Users/foo@bar.com/state", - ResourcePath: "/Users/foo@bar.com/resources", + RootPath: "/Users/foo@bar.test", + ArtifactPath: "/Users/foo@bar.test/artifacts", + FilePath: "/Users/foo@bar.test/files", + StatePath: "/Users/foo@bar.test/state", + ResourcePath: "/Users/foo@bar.test/resources", }, Permissions: []resources.Permission{ {Level: CAN_MANAGE, UserName: "TestUser"}, @@ -59,7 +59,7 @@ func TestApplyWorkspaceRootPermissions(t *testing.T) { m := mocks.NewMockWorkspaceClient(t) b.SetWorkpaceClient(m.WorkspaceClient) workspaceApi := m.GetMockWorkspaceAPI() - workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.com").Return(&workspace.ObjectInfo{ + workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.test").Return(&workspace.ObjectInfo{ ObjectId: 1234, }, nil) workspaceApi.EXPECT().SetPermissions(mock.Anything, workspace.WorkspaceObjectPermissionsRequest{ @@ -81,10 +81,10 @@ func TestApplyWorkspaceRootPermissionsForAllPaths(t *testing.T) { Config: config.Root{ Workspace: config.Workspace{ RootPath: "/Some/Root/Path", - ArtifactPath: "/Users/foo@bar.com/artifacts", - FilePath: "/Users/foo@bar.com/files", - StatePath: "/Users/foo@bar.com/state", - ResourcePath: "/Users/foo@bar.com/resources", + ArtifactPath: "/Users/foo@bar.test/artifacts", + FilePath: "/Users/foo@bar.test/files", + StatePath: "/Users/foo@bar.test/state", + ResourcePath: "/Users/foo@bar.test/resources", }, Permissions: []resources.Permission{ {Level: CAN_MANAGE, UserName: "TestUser"}, @@ -122,16 +122,16 @@ func TestApplyWorkspaceRootPermissionsForAllPaths(t *testing.T) { workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Some/Root/Path").Return(&workspace.ObjectInfo{ ObjectId: 1, }, nil) - workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.com/artifacts").Return(&workspace.ObjectInfo{ + workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.test/artifacts").Return(&workspace.ObjectInfo{ ObjectId: 2, }, nil) - workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.com/files").Return(&workspace.ObjectInfo{ + workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.test/files").Return(&workspace.ObjectInfo{ ObjectId: 3, }, nil) - workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.com/state").Return(&workspace.ObjectInfo{ + workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.test/state").Return(&workspace.ObjectInfo{ ObjectId: 4, }, nil) - workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.com/resources").Return(&workspace.ObjectInfo{ + workspaceApi.EXPECT().GetStatusByPath(mock.Anything, "/Users/foo@bar.test/resources").Return(&workspace.ObjectInfo{ ObjectId: 5, }, nil) diff --git a/bundle/phases/approval.go b/bundle/phases/approval.go new file mode 100644 index 00000000000..fdbcd8ecea9 --- /dev/null +++ b/bundle/phases/approval.go @@ -0,0 +1,41 @@ +package phases + +import ( + "context" + + "github.com/databricks/cli/bundle/deployplan" + "github.com/databricks/cli/libs/cmdio" +) + +// approvalGroup describes one resource type that needs explicit user consent +// before a destructive action is applied. +type approvalGroup struct { + group string // matches config.GetResourceTypeFromKey, e.g. "schemas" + message string // banner shown above the action list + skipChildren bool // skip actions where IsChildResource() is true +} + +// logApprovalGroups filters actions per group and prints non-empty groups. +// If trailingNewline is true, an empty line is printed after each non-empty group. +// Returns the total number of matched actions across all groups. +func logApprovalGroups(ctx context.Context, actions []deployplan.Action, groups []approvalGroup, trailingNewline bool, types ...deployplan.ActionType) int { + total := 0 + for _, g := range groups { + matched := filterGroup(actions, g.group, types...) + if len(matched) == 0 { + continue + } + total += len(matched) + cmdio.LogString(ctx, g.message) + for _, a := range matched { + if g.skipChildren && a.IsChildResource() { + continue + } + cmdio.Log(ctx, a) + } + if trailingNewline { + cmdio.LogString(ctx, "") + } + } + return total +} diff --git a/bundle/phases/bind.go b/bundle/phases/bind.go index 0435d19a6d2..f0041e91838 100644 --- a/bundle/phases/bind.go +++ b/bundle/phases/bind.go @@ -5,6 +5,8 @@ import ( "encoding/json" "errors" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/engine" @@ -15,7 +17,6 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" - "github.com/databricks/cli/libs/utils" ) func Bind(ctx context.Context, b *bundle.Bundle, opts *terraform.BindOptions, engine engine.EngineType) { @@ -40,7 +41,7 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *terraform.BindOptions, en resourceKey := fmt.Sprintf("resources.%s.%s", groupName, opts.ResourceKey) _, statePath := b.StateFilenameDirect(ctx) - result, err := b.DeploymentBundle.Bind(ctx, b.WorkspaceClient(), &b.Config, statePath, resourceKey, opts.ResourceId) + result, err := b.DeploymentBundle.Bind(ctx, b.WorkspaceClient(ctx), &b.Config, statePath, resourceKey, opts.ResourceId) if err != nil { logdiag.LogError(ctx, err) return @@ -55,7 +56,7 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *terraform.BindOptions, en if result.Plan != nil { if entry, ok := result.Plan.Plan[resourceKey]; ok && entry != nil && len(entry.Changes) > 0 { cmdio.LogString(ctx, "\nChanges detected:") - for _, field := range utils.SortedKeys(entry.Changes) { + for _, field := range slices.Sorted(maps.Keys(entry.Changes)) { change := entry.Changes[field] if change.Action != deployplan.Skip { cmdio.LogString(ctx, fmt.Sprintf(" ~ %s: %v -> %v", field, jsonDump(ctx, change.Remote, field), jsonDump(ctx, change.New, field))) diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 110ab757312..b4d70ede5ad 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -26,6 +26,17 @@ import ( "github.com/databricks/cli/libs/sync" ) +var deployApprovalGroups = []approvalGroup{ + {group: "schemas", message: deleteOrRecreateSchemaMessage, skipChildren: true}, + {group: "pipelines", message: deleteOrRecreatePipelineMessage}, + {group: "volumes", message: deleteOrRecreateVolumeMessage}, + {group: "dashboards", message: deleteOrRecreateDashboardMessage}, + {group: "database_instances", message: deleteOrRecreateDatabaseInstanceMessage}, + {group: "synced_database_tables", message: deleteOrRecreateSyncedDatabaseTableMessage}, + {group: "postgres_projects", message: deleteOrRecreatePostgresProjectMessage}, + {group: "postgres_branches", message: deleteOrRecreatePostgresBranchMessage}, +} + func approvalForDeploy(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan) (bool, error) { actions := plan.GetActions() @@ -34,52 +45,12 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle, plan *deployplan.P return false, err } - types := []deployplan.ActionType{deployplan.Recreate, deployplan.Delete} - schemaActions := filterGroup(actions, "schemas", types...) - dltActions := filterGroup(actions, "pipelines", types...) - volumeActions := filterGroup(actions, "volumes", types...) - dashboardActions := filterGroup(actions, "dashboards", types...) - - // We don't need to display any prompts in this case. - if len(schemaActions) == 0 && len(dltActions) == 0 && len(volumeActions) == 0 && len(dashboardActions) == 0 { + total := logApprovalGroups(ctx, actions, deployApprovalGroups, false, deployplan.Recreate, deployplan.Delete) + if total == 0 { + // No destructive actions in any tracked group: skip the prompt. return true, nil } - // One or more UC schema resources will be deleted or recreated. - if len(schemaActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreateSchemaMessage) - for _, action := range schemaActions { - if action.IsChildResource() { - continue - } - cmdio.Log(ctx, action) - } - } - - // One or more DLT pipelines is being recreated. - if len(dltActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreatePipelineMessage) - for _, action := range dltActions { - cmdio.Log(ctx, action) - } - } - - // One or more volumes is being recreated. - if len(volumeActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreateVolumeMessage) - for _, action := range volumeActions { - cmdio.Log(ctx, action) - } - } - - // One or more dashboards is being recreated. - if len(dashboardActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreateDashboardMessage) - for _, action := range dashboardActions { - cmdio.Log(ctx, action) - } - } - if b.AutoApprove { return true, nil } @@ -89,12 +60,7 @@ func approvalForDeploy(ctx context.Context, b *bundle.Bundle, plan *deployplan.P } cmdio.LogString(ctx, "") - approved, err := cmdio.AskYesOrNo(ctx, "Would you like to proceed?") - if err != nil { - return false, err - } - - return approved, nil + return cmdio.AskYesOrNo(ctx, "Would you like to proceed?") } func deployCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, targetEngine engine.EngineType) { @@ -103,7 +69,7 @@ func deployCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, ta cmdio.LogString(ctx, "Deploying resources...") if targetEngine.IsDirect() { - b.DeploymentBundle.Apply(ctx, b.WorkspaceClient(), plan, direct.MigrateMode(false)) + b.DeploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(false)) // Finalize state: write to disk even if deploy failed, so partial progress is saved. // Skip for empty plans to avoid creating a state file when nothing was deployed. if len(plan.Plan) > 0 { @@ -185,7 +151,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand if plan != nil { // Initialize DeploymentBundle for applying the loaded plan - err := b.DeploymentBundle.InitForApply(ctx, b.WorkspaceClient(), plan) + err := b.DeploymentBundle.InitForApply(ctx, b.WorkspaceClient(ctx), plan) if err != nil { logdiag.LogError(ctx, err) return @@ -219,7 +185,7 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand func RunPlan(ctx context.Context, b *bundle.Bundle, engine engine.EngineType) *deployplan.Plan { if engine.IsDirect() { - plan, err := b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(), &b.Config) + plan, err := b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), &b.Config) if err != nil { logdiag.LogError(ctx, err) return nil diff --git a/bundle/phases/destroy.go b/bundle/phases/destroy.go index 12720f1dc58..91640ac6cad 100644 --- a/bundle/phases/destroy.go +++ b/bundle/phases/destroy.go @@ -20,8 +20,8 @@ import ( ) func assertRootPathExists(ctx context.Context, b *bundle.Bundle) (bool, error) { - w := b.WorkspaceClient() - _, err := w.Workspace.GetStatusByPath(ctx, b.Config.Workspace.RootPath) + w := b.WorkspaceClient(ctx) + _, err := w.Workspace.GetStatusByPath(ctx, b.Config.Workspace.RootPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. var aerr *apierr.APIError if errors.As(err, &aerr) && aerr.StatusCode == http.StatusNotFound { @@ -32,6 +32,16 @@ func assertRootPathExists(ctx context.Context, b *bundle.Bundle) (bool, error) { return true, err } +var destroyApprovalGroups = []approvalGroup{ + {group: "schemas", message: deleteSchemaMessage}, + {group: "pipelines", message: deletePipelineMessage}, + {group: "volumes", message: deleteVolumeMessage}, + {group: "database_instances", message: deleteDatabaseInstanceMessage}, + {group: "synced_database_tables", message: deleteSyncedDatabaseTableMessage}, + {group: "postgres_projects", message: deletePostgresProjectMessage}, + {group: "postgres_branches", message: deletePostgresBranchMessage}, +} + func approvalForDestroy(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan) (bool, error) { deleteActions := plan.GetActions() @@ -51,33 +61,7 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle, plan *deployplan. cmdio.LogString(ctx, "") } - schemaActions := filterGroup(deleteActions, "schemas", deployplan.Delete) - dltActions := filterGroup(deleteActions, "pipelines", deployplan.Delete) - volumeActions := filterGroup(deleteActions, "volumes", deployplan.Delete) - - if len(schemaActions) > 0 { - cmdio.LogString(ctx, deleteSchemaMessage) - for _, a := range schemaActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(dltActions) > 0 { - cmdio.LogString(ctx, deletePipelineMessage) - for _, a := range dltActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(volumeActions) > 0 { - cmdio.LogString(ctx, deleteVolumeMessage) - for _, a := range volumeActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } + logApprovalGroups(ctx, deleteActions, destroyApprovalGroups, true, deployplan.Delete) cmdio.LogString(ctx, "All files and directories at the following location will be deleted: "+b.Config.Workspace.RootPath) cmdio.LogString(ctx, "") @@ -86,17 +70,12 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle, plan *deployplan. return true, nil } - approved, err := cmdio.AskYesOrNo(ctx, "Would you like to proceed?") - if err != nil { - return false, err - } - - return approved, nil + return cmdio.AskYesOrNo(ctx, "Would you like to proceed?") } func destroyCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, engine engine.EngineType) { if engine.IsDirect() { - b.DeploymentBundle.Apply(ctx, b.WorkspaceClient(), plan, direct.MigrateMode(false)) + b.DeploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(false)) // Skip Finalize for empty plans to avoid creating a state file when nothing was destroyed. if len(plan.Plan) > 0 { if err := b.DeploymentBundle.StateDB.Finalize(); err != nil { @@ -163,7 +142,7 @@ func Destroy(ctx context.Context, b *bundle.Bundle, engine engine.EngineType) { var plan *deployplan.Plan if engine.IsDirect() { - plan, err = b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(), nil) + plan, err = b.DeploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), nil) if err != nil { logdiag.LogError(ctx, err) return diff --git a/bundle/phases/messages.go b/bundle/phases/messages.go index 625373dd8b1..347df8ece43 100644 --- a/bundle/phases/messages.go +++ b/bundle/phases/messages.go @@ -20,6 +20,22 @@ is removed from the catalog, but the underlying files are not deleted:` deleteOrRecreateDashboardMessage = ` This action will result in the deletion or recreation of the following dashboards. This will result in changed IDs and permanent URLs of the dashboards that will be recreated:` + + deleteOrRecreateDatabaseInstanceMessage = ` +This action will result in the deletion or recreation of the following Lakebase database instances. +All data stored in them will be permanently lost:` + + deleteOrRecreateSyncedDatabaseTableMessage = ` +This action will result in the deletion or recreation of the following synced database tables. +The synced data in the destination database will be lost (the source table is preserved):` + + deleteOrRecreatePostgresProjectMessage = ` +This action will result in the deletion or recreation of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost:` + + deleteOrRecreatePostgresBranchMessage = ` +This action will result in the deletion or recreation of the following Lakebase branches. +All data stored in them will be permanently lost:` ) // Messages for bundle destroy. @@ -33,4 +49,16 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them:` For managed volumes, the files stored in the volume are also deleted from your cloud tenant within 30 days. For external volumes, the metadata about the volume is removed from the catalog, but the underlying files are not deleted:` + + deleteDatabaseInstanceMessage = `This action will result in the deletion of the following Lakebase database instances. +All data stored in them will be permanently lost:` + + deleteSyncedDatabaseTableMessage = `This action will result in the deletion of the following synced database tables. +The synced data in the destination database will be lost (the source table is preserved):` + + deletePostgresProjectMessage = `This action will result in the deletion of the following Lakebase projects along with +all their branches, databases, and endpoints. All data stored in them will be permanently lost:` + + deletePostgresBranchMessage = `This action will result in the deletion of the following Lakebase branches. +All data stored in them will be permanently lost:` ) diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index 5478ddb2a16..b7df901f867 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -1,13 +1,14 @@ package phases import ( + "cmp" "context" "slices" - "sort" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/libraries" + "github.com/databricks/cli/bundle/metrics" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/telemetry" @@ -18,8 +19,8 @@ func getExecutionTimes(b *bundle.Bundle) []protos.IntMapEntry { executionTimes := b.Metrics.ExecutionTimes // Sort the execution times in descending order. - sort.Slice(executionTimes, func(i, j int) bool { - return executionTimes[i].Value > executionTimes[j].Value + slices.SortFunc(executionTimes, func(a, b protos.IntMapEntry) int { + return cmp.Compare(b.Value, a.Value) }) // Keep only the top 250 execution times. This keeps the telemetry event @@ -113,6 +114,13 @@ func LogDeployTelemetry(ctx context.Context, b *bundle.Bundle, errMsg string) { slices.Sort(clusterIds) slices.Sort(dashboardIds) + for _, app := range b.Config.Resources.Apps { + if app != nil && app.Lifecycle != nil && app.Lifecycle.Started != nil { + b.Metrics.SetBoolValue(metrics.AppLifecycleStarted, *app.Lifecycle.Started) + break + } + } + // If the bundle UUID is not set, we use a default 0 value. bundleUuid := "00000000-0000-0000-0000-000000000000" if b.Config.Bundle.Uuid != "" { diff --git a/bundle/render/render_text_output.go b/bundle/render/render_text_output.go index 58d77170d6e..b1f0c6442d1 100644 --- a/bundle/render/render_text_output.go +++ b/bundle/render/render_text_output.go @@ -1,34 +1,20 @@ package render import ( + "cmp" "context" "fmt" "io" - "sort" + "slices" "strings" "text/template" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/databricks-sdk-go/service/iam" - "github.com/fatih/color" ) -var renderFuncMap = template.FuncMap{ - "red": color.RedString, - "green": color.GreenString, - "blue": color.BlueString, - "yellow": color.YellowString, - "magenta": color.MagentaString, - "cyan": color.CyanString, - "bold": func(format string, a ...any) string { - return color.New(color.Bold).Sprintf(format, a...) - }, - "italic": func(format string, a ...any) string { - return color.New(color.Italic).Sprintf(format, a...) - }, -} - const summaryHeaderTemplate = `{{- if .Name -}} Name: {{ .Name | bold }} {{- if .Target }} @@ -81,13 +67,13 @@ func buildTrailer(ctx context.Context) string { info := logdiag.Copy(ctx) var parts []string if info.Errors > 0 { - parts = append(parts, color.RedString(pluralize(info.Errors, "error", "errors"))) + parts = append(parts, cmdio.Red(ctx, pluralize(info.Errors, "error", "errors"))) } if info.Warnings > 0 { - parts = append(parts, color.YellowString(pluralize(info.Warnings, "warning", "warnings"))) + parts = append(parts, cmdio.Yellow(ctx, pluralize(info.Warnings, "warning", "warnings"))) } if info.Recommendations > 0 { - parts = append(parts, color.BlueString(pluralize(info.Recommendations, "recommendation", "recommendations"))) + parts = append(parts, cmdio.Blue(ctx, pluralize(info.Recommendations, "recommendation", "recommendations"))) } switch { case len(parts) >= 3: @@ -100,7 +86,7 @@ func buildTrailer(ctx context.Context) string { return fmt.Sprintf("Found %s\n", parts[0]) default: // No diagnostics to print. - return color.GreenString("Validation OK!\n") + return cmdio.Green(ctx, "Validation OK!\n") } } @@ -117,7 +103,7 @@ func renderSummaryHeaderTemplate(ctx context.Context, out io.Writer, b *bundle.B } } - t := template.Must(template.New("summary").Funcs(renderFuncMap).Parse(summaryHeaderTemplate)) + t := template.Must(template.New("summary").Funcs(cmdio.RenderFuncMap(ctx)).Parse(summaryHeaderTemplate)) err := t.Execute(out, map[string]any{ "Name": b.Config.Bundle.Name, "Target": b.Config.Bundle.Target, @@ -178,7 +164,7 @@ func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error { } } - if err := renderResourcesTemplate(out, resourceGroups); err != nil { + if err := renderResourcesTemplate(ctx, out, resourceGroups); err != nil { return fmt.Errorf("failed to render resources template: %w", err) } @@ -186,18 +172,18 @@ func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error { } // Helper function to sort and render resource groups using the template -func renderResourcesTemplate(out io.Writer, resourceGroups []ResourceGroup) error { +func renderResourcesTemplate(ctx context.Context, out io.Writer, resourceGroups []ResourceGroup) error { // Sort everything to ensure consistent output - sort.Slice(resourceGroups, func(i, j int) bool { - return resourceGroups[i].GroupName < resourceGroups[j].GroupName + slices.SortFunc(resourceGroups, func(a, b ResourceGroup) int { + return cmp.Compare(a.GroupName, b.GroupName) }) for _, group := range resourceGroups { - sort.Slice(group.Resources, func(i, j int) bool { - return group.Resources[i].Key < group.Resources[j].Key + slices.SortFunc(group.Resources, func(a, b ResourceInfo) int { + return cmp.Compare(a.Key, b.Key) }) } - t := template.Must(template.New("resources").Funcs(renderFuncMap).Parse(resourcesTemplate)) + t := template.Must(template.New("resources").Funcs(cmdio.RenderFuncMap(ctx)).Parse(resourcesTemplate)) return t.Execute(out, resourceGroups) } diff --git a/bundle/render/render_text_output_test.go b/bundle/render/render_text_output_test.go index 3d424445396..4a6b777c1c2 100644 --- a/bundle/render/render_text_output_test.go +++ b/bundle/render/render_text_output_test.go @@ -18,7 +18,6 @@ import ( "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" "github.com/databricks/databricks-sdk-go/service/serving" - "github.com/fatih/color" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,20 +25,13 @@ import ( func TestRenderSummaryHeaderTemplate_nilBundle(t *testing.T) { writer := &bytes.Buffer{} - err := renderSummaryHeaderTemplate(t.Context(), writer, nil) + err := renderSummaryHeaderTemplate(cmdio.MockDiscard(t.Context()), writer, nil) require.NoError(t, err) assert.Equal(t, "", writer.String()) } func TestRenderDiagnosticsSummary(t *testing.T) { - // Disable colors for consistent test output - oldNoColor := color.NoColor - color.NoColor = true - defer func() { - color.NoColor = oldNoColor - }() - testCases := []struct { name string bundle *bundle.Bundle @@ -114,7 +106,7 @@ func TestRenderDiagnosticsSummary(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - ctx := logdiag.InitContext(t.Context()) + ctx := logdiag.InitContext(cmdio.MockDiscard(t.Context())) logdiag.SetCollect(ctx, true) // Collect diagnostics instead of outputting to stderr // Simulate diagnostic counts by logging fake diagnostics @@ -144,13 +136,6 @@ type renderDiagnosticsTestCase struct { } func TestRenderDiagnostics(t *testing.T) { - // Disable colors for consistent test output - oldNoColor := color.NoColor - color.NoColor = true - defer func() { - color.NoColor = oldNoColor - }() - testCases := []renderDiagnosticsTestCase{ { name: "empty diagnostics", @@ -286,14 +271,7 @@ func TestRenderDiagnostics(t *testing.T) { } func TestRenderSummaryTemplate_nilBundle(t *testing.T) { - // Disable colors for consistent test output - oldNoColor := color.NoColor - color.NoColor = true - defer func() { - color.NoColor = oldNoColor - }() - - ctx := logdiag.InitContext(t.Context()) + ctx := logdiag.InitContext(cmdio.MockDiscard(t.Context())) writer := &bytes.Buffer{} err := renderSummaryHeaderTemplate(ctx, writer, nil) @@ -306,14 +284,7 @@ func TestRenderSummaryTemplate_nilBundle(t *testing.T) { } func TestRenderSummary(t *testing.T) { - ctx := t.Context() - - // Disable colors for consistent test output - oldNoColor := color.NoColor - color.NoColor = true - defer func() { - color.NoColor = oldNoColor - }() + ctx := cmdio.MockDiscard(t.Context()) // Create a mock bundle with various resources b := &bundle.Bundle{ diff --git a/bundle/run/app.go b/bundle/run/app.go index c3a6497f1d3..8c0f135cc5d 100644 --- a/bundle/run/app.go +++ b/bundle/run/app.go @@ -11,6 +11,9 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/bundle/run/output" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/dyn/dynvar" "github.com/databricks/databricks-sdk-go/service/apps" "github.com/spf13/cobra" ) @@ -50,7 +53,7 @@ func (a *appRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, e } logProgress(ctx, "Getting the status of the app "+app.Name) - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) // Check the status of the app first. createdApp, err := w.Apps.Get(ctx, apps.GetAppRequest{Name: app.Name}) @@ -102,7 +105,7 @@ func isAppComputeStarting(app *apps.App) bool { func (a *appRunner) start(ctx context.Context) error { app := a.app b := a.bundle - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) logProgress(ctx, "Starting the app "+app.Name) wait, err := w.Apps.Start(ctx, apps.StartAppRequest{Name: app.Name}) @@ -134,11 +137,60 @@ func (a *appRunner) start(ctx context.Context) error { } func (a *appRunner) deploy(ctx context.Context) error { - w := a.bundle.WorkspaceClient() - deployment := appdeploy.BuildDeployment(a.app.SourceCodePath, a.app.Config, a.app.GitSource) + w := a.bundle.WorkspaceClient(ctx) + config, err := a.resolvedConfig() + if err != nil { + return err + } + deployment := appdeploy.BuildDeployment(a.app.SourceCodePath, config, a.app.GitSource) return appdeploy.Deploy(ctx, w, a.app.Name, deployment) } +// resolvedConfig returns the app config with any ${resources.*} variable references +// resolved against the current bundle state. This is needed because the app runtime +// configuration (env vars, command) can reference other bundle resources whose +// properties are known only after the initialization phase. +func (a *appRunner) resolvedConfig() (*resources.AppConfig, error) { + if a.app.Config == nil { + return nil, nil + } + + root := a.bundle.Config.Value() + + // Normalize the full config so that all typed fields are present, even those + // not explicitly set. This allows looking up resource properties by path. + normalized, _ := convert.Normalize(a.bundle.Config, root, convert.IncludeMissingFields) + + // Get the app's config section as a dyn.Value to resolve references in it. + // The key is of the form "apps.", so the full path is "resources.apps..config". + configPath := dyn.MustPathFromString("resources." + a.Key() + ".config") + configV, err := dyn.GetByPath(root, configPath) + if err != nil || !configV.IsValid() { + return a.app.Config, nil //nolint:nilerr // missing config path means use default config + } + + resourcesPrefix := dyn.MustPathFromString("resources") + + // Resolve ${resources.*} references in the app config against the full bundle config. + // Other variable types (bundle.*, workspace.*, variables.*) are already resolved + // during the initialization phase and are left in place if encountered here. + resolved, err := dynvar.Resolve(configV, func(path dyn.Path) (dyn.Value, error) { + if !path.HasPrefix(resourcesPrefix) { + return dyn.InvalidValue, dynvar.ErrSkipResolution + } + return dyn.GetByPath(normalized, path) + }) + if err != nil { + return nil, err + } + + var config resources.AppConfig + if err := convert.ToTyped(&config, resolved); err != nil { + return nil, err + } + return &config, nil +} + func (a *appRunner) Cancel(ctx context.Context) error { // We should cancel the app by stopping it. app := a.app @@ -147,7 +199,7 @@ func (a *appRunner) Cancel(ctx context.Context) error { return errors.New("app is not defined") } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) logProgress(ctx, "Stopping app "+app.Name) wait, err := w.Apps.Stop(ctx, apps.StopAppRequest{Name: app.Name}) diff --git a/bundle/run/app_test.go b/bundle/run/app_test.go index 1c05fa63ebd..ca68d095ea6 100644 --- a/bundle/run/app_test.go +++ b/bundle/run/app_test.go @@ -51,7 +51,7 @@ func setupBundle(t *testing.T) (context.Context, *bundle.Bundle, *mocks.MockWork SyncRoot: vfs.MustNew(root), Config: config.Root{ Workspace: config.Workspace{ - RootPath: "/Workspace/Users/foo@bar.com/", + RootPath: "/Workspace/Users/foo@bar.test/", }, Resources: config.Resources{ Apps: map[string]*resources.App{ @@ -107,7 +107,7 @@ func setupTestApp(t *testing.T, initialAppState apps.ApplicationState, initialCo AppName: "my_app", AppDeployment: apps.AppDeployment{ Mode: apps.AppDeploymentModeSnapshot, - SourceCodePath: "/Workspace/Users/foo@bar.com/files/my_app", + SourceCodePath: "/Workspace/Users/foo@bar.test/files/my_app", }, }).Return(wait, nil) @@ -210,7 +210,7 @@ func TestAppDeployWithDeploymentInProgress(t *testing.T) { AppName: "my_app", AppDeployment: apps.AppDeployment{ Mode: apps.AppDeploymentModeSnapshot, - SourceCodePath: "/Workspace/Users/foo@bar.com/files/my_app", + SourceCodePath: "/Workspace/Users/foo@bar.test/files/my_app", }, }).Return(nil, errors.New("deployment in progress")).Once() @@ -229,12 +229,12 @@ func TestAppDeployWithDeploymentInProgress(t *testing.T) { appApi.EXPECT().WaitGetDeploymentAppSucceeded(mock.Anything, "my_app", "active_deployment_id", mock.Anything, mock.Anything).Return(nil, nil) - // Second one should succeeed + // Second one should succeed appApi.EXPECT().Deploy(mock.Anything, apps.CreateAppDeploymentRequest{ AppName: "my_app", AppDeployment: apps.AppDeployment{ Mode: apps.AppDeploymentModeSnapshot, - SourceCodePath: "/Workspace/Users/foo@bar.com/files/my_app", + SourceCodePath: "/Workspace/Users/foo@bar.test/files/my_app", }, }).Return(wait, nil).Once() diff --git a/bundle/run/job.go b/bundle/run/job.go index c3af7255164..04b357682fe 100644 --- a/bundle/run/job.go +++ b/bundle/run/job.go @@ -15,7 +15,6 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/databricks/databricks-sdk-go/service/jobs" - "github.com/fatih/color" "github.com/spf13/cobra" "golang.org/x/sync/errgroup" ) @@ -49,10 +48,7 @@ func isSuccess(task jobs.RunTask) bool { } func (r *jobRunner) logFailedTasks(ctx context.Context, runId int64) { - w := r.bundle.WorkspaceClient() - red := color.New(color.FgRed).SprintFunc() - green := color.New(color.FgGreen).SprintFunc() - yellow := color.New(color.FgYellow).SprintFunc() + w := r.bundle.WorkspaceClient(ctx) run, err := w.Jobs.GetRun(ctx, jobs.GetRunRequest{ RunId: runId, }) @@ -65,21 +61,21 @@ func (r *jobRunner) logFailedTasks(ctx context.Context, runId int64) { } for _, task := range run.Tasks { if isSuccess(task) { - log.Infof(ctx, "task %s completed successfully", green(task.TaskKey)) + log.Infof(ctx, "task %s completed successfully", cmdio.Green(ctx, task.TaskKey)) } else if isFailed(task) { taskInfo, err := w.Jobs.GetRunOutput(ctx, jobs.GetRunOutputRequest{ RunId: task.RunId, }) if err != nil { - log.Errorf(ctx, "task %s failed. Unable to fetch error trace: %s", red(task.TaskKey), err) + log.Errorf(ctx, "task %s failed. Unable to fetch error trace: %s", cmdio.Red(ctx, task.TaskKey), err) continue } cmdio.Log(ctx, progress.NewTaskErrorEvent(task.TaskKey, taskInfo.Error, taskInfo.ErrorTrace)) log.Errorf(ctx, "Task %s failed!\nError:\n%s\nTrace:\n%s", - red(task.TaskKey), taskInfo.Error, taskInfo.ErrorTrace) + cmdio.Red(ctx, task.TaskKey), taskInfo.Error, taskInfo.ErrorTrace) } else { log.Infof(ctx, "task %s is in state %s", - yellow(task.TaskKey), task.State.LifeCycleState) + cmdio.Yellow(ctx, task.TaskKey), task.State.LifeCycleState) } } } @@ -146,7 +142,7 @@ func (r *jobRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, e // Include resource key in logger. ctx = log.NewContext(ctx, log.GetLogger(ctx).With("resource", r.Key())) - w := r.bundle.WorkspaceClient() + w := r.bundle.WorkspaceClient(ctx) monitor := &jobRunMonitor{ ctx: ctx, @@ -191,7 +187,7 @@ func (r *jobRunner) Run(ctx context.Context, opts *Options) (output.RunOutput, e // The task completed successfully. case jobs.RunResultStateSuccess: log.Infof(ctx, "Run has completed successfully!") - return output.GetJobOutput(ctx, r.bundle.WorkspaceClient(), waiter.RunId) + return output.GetJobOutput(ctx, r.bundle.WorkspaceClient(ctx), waiter.RunId) // The run was stopped after reaching the timeout. case jobs.RunResultStateTimedout: @@ -245,7 +241,7 @@ func (r *jobRunner) convertPythonParams(opts *Options) error { } func (r *jobRunner) Cancel(ctx context.Context) error { - w := r.bundle.WorkspaceClient() + w := r.bundle.WorkspaceClient(ctx) jobID, err := strconv.ParseInt(r.job.ID, 10, 64) if err != nil { return fmt.Errorf("job ID is not an integer: %s", r.job.ID) diff --git a/bundle/run/job_args.go b/bundle/run/job_args.go index 40434f8396e..dea9e71e560 100644 --- a/bundle/run/job_args.go +++ b/bundle/run/job_args.go @@ -1,9 +1,11 @@ package run import ( + "maps" + "slices" + "github.com/databricks/cli/bundle/config/resources" "github.com/spf13/cobra" - "golang.org/x/exp/maps" ) type jobParameterArgs struct { @@ -20,9 +22,7 @@ func (a jobParameterArgs) ParseArgs(args []string, opts *Options) error { if opts.Job.jobParams == nil { opts.Job.jobParams = kv } else { - for k, v := range kv { - opts.Job.jobParams[k] = v - } + maps.Copy(opts.Job.jobParams, kv) } return nil } @@ -49,9 +49,7 @@ func (a jobTaskNotebookParamArgs) ParseArgs(args []string, opts *Options) error if opts.Job.notebookParams == nil { opts.Job.notebookParams = kv } else { - for k, v := range kv { - opts.Job.notebookParams[k] = v - } + maps.Copy(opts.Job.notebookParams, kv) } return nil } @@ -63,7 +61,7 @@ func (a jobTaskNotebookParamArgs) CompleteArgs(args []string, toComplete string) maps.Copy(parameters, nt.BaseParameters) } } - return genericCompleteKeyValueArgs(args, toComplete, maps.Keys(parameters)) + return genericCompleteKeyValueArgs(args, toComplete, slices.Collect(maps.Keys(parameters))) } type jobTaskJarParamArgs struct { @@ -163,7 +161,7 @@ func (r *jobRunner) posArgsHandler() argsHandler { } // Cannot handle positional arguments if we have more than one task type. - keys := maps.Keys(seen) + keys := slices.Collect(maps.Keys(seen)) if len(keys) != 1 { return nopArgsHandler{} } diff --git a/bundle/run/output/job.go b/bundle/run/output/job.go index 2ac974cd577..0de381e4c4b 100644 --- a/bundle/run/output/job.go +++ b/bundle/run/output/job.go @@ -1,9 +1,10 @@ package output import ( + "cmp" "context" "fmt" - "sort" + "slices" "strings" "github.com/databricks/databricks-sdk-go" @@ -34,8 +35,8 @@ func (out *JobOutput) String() (string, error) { } result := strings.Builder{} result.WriteString("Output:\n") - sort.Slice(out.TaskOutputs, func(i, j int) bool { - return out.TaskOutputs[i].EndTime < out.TaskOutputs[j].EndTime + slices.SortFunc(out.TaskOutputs, func(a, b TaskOutput) int { + return cmp.Compare(a.EndTime, b.EndTime) }) for _, v := range out.TaskOutputs { if v.Output == nil { @@ -43,10 +44,10 @@ func (out *JobOutput) String() (string, error) { } taskString, err := v.Output.String() if err != nil { - return "", nil + return "", nil //nolint:nilerr // skip tasks with unparseable output } result.WriteString("=======\n") - result.WriteString(fmt.Sprintf("Task %s:\n", v.TaskKey)) + fmt.Fprintf(&result, "Task %s:\n", v.TaskKey) result.WriteString(taskString + "\n") } return result.String(), nil diff --git a/bundle/run/output/job_test.go b/bundle/run/output/job_test.go index 80c52c3e1f7..9ecb7fc43e4 100644 --- a/bundle/run/output/job_test.go +++ b/bundle/run/output/job_test.go @@ -110,6 +110,23 @@ func TestNotebookOutputToRunOutput(t *testing.T) { assert.Equal(t, expected, actual) } +func TestNotebookOutputWithEmptyResultFallsBackToLogs(t *testing.T) { + jobOutput := &jobs.RunOutput{ + NotebookOutput: &jobs.NotebookOutput{ + Result: "", + }, + Logs: "hello :)", + LogsTruncated: true, + } + actual := toRunOutput(jobOutput) + + expected := &LogsOutput{ + Logs: "hello :)", + LogsTruncated: true, + } + assert.Equal(t, expected, actual) +} + func TestDbtOutputToRunOutput(t *testing.T) { jobOutput := &jobs.RunOutput{ DbtOutput: &jobs.DbtOutput{ diff --git a/bundle/run/output/task.go b/bundle/run/output/task.go index 53b989e885f..d30370bb015 100644 --- a/bundle/run/output/task.go +++ b/bundle/run/output/task.go @@ -67,6 +67,13 @@ func (out *LogsOutput) String() (string, error) { func toRunOutput(output *jobs.RunOutput) RunOutput { switch { case output.NotebookOutput != nil: + if output.NotebookOutput.Result == "" && !output.NotebookOutput.Truncated && output.Logs != "" { + result := LogsOutput{ + Logs: output.Logs, + LogsTruncated: output.LogsTruncated, + } + return &result + } result := NotebookOutput(*output.NotebookOutput) return &result case output.DbtOutput != nil: diff --git a/bundle/run/pipeline.go b/bundle/run/pipeline.go index 916df54828a..3e801ee2c3b 100644 --- a/bundle/run/pipeline.go +++ b/bundle/run/pipeline.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "slices" + "strings" "time" "github.com/databricks/cli/bundle" @@ -27,23 +29,24 @@ func filterEventsByUpdateId(events []pipelines.PipelineEvent, updateId string) [ } func (r *pipelineRunner) logEvent(ctx context.Context, event pipelines.PipelineEvent) { - logString := "" + var sb strings.Builder if event.Message != "" { - logString += fmt.Sprintf(" %s\n", event.Message) + fmt.Fprintf(&sb, " %s\n", event.Message) } if event.Error != nil && len(event.Error.Exceptions) > 0 { - logString += "trace for most recent exception: \n" - for i := range len(event.Error.Exceptions) { - logString += event.Error.Exceptions[i].Message + "\n" + sb.WriteString("trace for most recent exception: \n") + for _, exc := range event.Error.Exceptions { + sb.WriteString(exc.Message) + sb.WriteByte('\n') } } - if logString != "" { - log.Errorf(ctx, "[%s] %s", event.EventType, logString) + if sb.Len() > 0 { + log.Errorf(ctx, "[%s] %s", event.EventType, sb.String()) } } func (r *pipelineRunner) logErrorEvent(ctx context.Context, pipelineId, updateId string) error { - w := r.bundle.WorkspaceClient() + w := r.bundle.WorkspaceClient(ctx) // Note: For a 100 percent correct and complete solution we should use the // w.Pipelines.ListPipelineEventsAll method to find all relevant events. However the @@ -63,10 +66,9 @@ func (r *pipelineRunner) logErrorEvent(ctx context.Context, pipelineId, updateId return err } updateEvents := filterEventsByUpdateId(events, updateId) - // The events API returns most recent events first. We iterate in a reverse order - // to print the events chronologically - for i := len(updateEvents) - 1; i >= 0; i-- { - r.logEvent(ctx, updateEvents[i]) + // The events API returns most recent events first. + for _, event := range slices.Backward(updateEvents) { + r.logEvent(ctx, event) } return nil } @@ -90,7 +92,7 @@ func (r *pipelineRunner) Run(ctx context.Context, opts *Options) (output.RunOutp // Include resource key in logger. ctx = log.NewContext(ctx, log.GetLogger(ctx).With("resource", r.Key())) - w := r.bundle.WorkspaceClient() + w := r.bundle.WorkspaceClient(ctx) req, err := opts.Pipeline.toPayload(r.pipeline, pipelineID) if err != nil { @@ -165,7 +167,7 @@ func (r *pipelineRunner) Run(ctx context.Context, opts *Options) (output.RunOutp } func (r *pipelineRunner) Cancel(ctx context.Context) error { - w := r.bundle.WorkspaceClient() + w := r.bundle.WorkspaceClient(ctx) wait, err := w.Pipelines.Stop(ctx, pipelines.StopRequest{ PipelineId: r.pipeline.ID, }) diff --git a/bundle/run/pipeline_test.go b/bundle/run/pipeline_test.go index 8febf62e4dc..56457218468 100644 --- a/bundle/run/pipeline_test.go +++ b/bundle/run/pipeline_test.go @@ -69,7 +69,7 @@ func TestPipelineRunnerRestart(t *testing.T) { m := mocks.NewMockWorkspaceClient(t) m.WorkspaceClient.Config = &sdk_config.Config{ - Host: "https://test.com", + Host: "https://test.test", } b.SetWorkpaceClient(m.WorkspaceClient) diff --git a/bundle/run/progress/job_events.go b/bundle/run/progress/job_events.go index 80e4938a715..6872e0fe0ea 100644 --- a/bundle/run/progress/job_events.go +++ b/bundle/run/progress/job_events.go @@ -21,7 +21,7 @@ func NewTaskErrorEvent(taskKey, errorMessage, errorTrace string) *TaskErrorEvent func (event *TaskErrorEvent) String() string { result := strings.Builder{} - result.WriteString(fmt.Sprintf("Task %s FAILED:\n", event.TaskKey)) + fmt.Fprintf(&result, "Task %s FAILED:\n", event.TaskKey) result.WriteString(event.Error + "\n") result.WriteString(event.ErrorTrace + "\n") return result.String() diff --git a/bundle/run/progress/pipeline.go b/bundle/run/progress/pipeline.go index a98f074f78f..cce17965da8 100644 --- a/bundle/run/progress/pipeline.go +++ b/bundle/run/progress/pipeline.go @@ -3,21 +3,22 @@ package progress import ( "context" "fmt" + "slices" "strings" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/pipelines" ) -// The dlt backend computes events for pipeline runs which are accessable through +// The dlt backend computes events for pipeline runs which are accessible through // the 2.0/pipelines/{pipeline_id}/events API // // There are 4 levels for these events: ("ERROR", "WARN", "INFO", "METRICS") // // Here's short introduction to a few important events we display on the console: // -// 1. `update_progress`: A state transition occured for the entire pipeline update -// 2. `flow_progress`: A state transition occured for a single flow in the pipeine +// 1. `update_progress`: A state transition occurred for the entire pipeline update +// 2. `flow_progress`: A state transition occurred for a single flow in the pipeine type ProgressEvent pipelines.PipelineEvent func (event *ProgressEvent) String() string { @@ -28,7 +29,7 @@ func (event *ProgressEvent) String() string { result.WriteString(fmt.Sprintf("%-15s", event.EventType) + " ") result.WriteString(event.Level.String() + " ") - result.WriteString(fmt.Sprintf(`"%s"`, event.Message)) + fmt.Fprintf(&result, `"%s"`, event.Message) // construct error string if level=`Error` if event.Level == pipelines.EventLevelError && event.Error != nil { @@ -83,9 +84,8 @@ func (l *UpdateTracker) Events(ctx context.Context) ([]ProgressEvent, error) { } var result []ProgressEvent - // we iterate in reverse to return events in chronological order - for i := len(events) - 1; i >= 0; i-- { - event := events[i] + // Return events in chronological order. + for _, event := range slices.Backward(events) { // filter to only include update_progress and flow_progress events if event.EventType == "flow_progress" || event.EventType == "update_progress" { result = append(result, ProgressEvent(event)) diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 993adec7935..ee105a6f821 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -301,6 +301,12 @@ "lifecycle": { "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" }, + "managed_encryption_settings": { + "description": "Control CMK encryption for managed catalog data", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.EncryptionSettings", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "name": { "$ref": "#/$defs/string" }, @@ -1322,7 +1328,7 @@ } }, "additionalProperties": false, - "markdownDescription": "The pipeline resource allows you to create Delta Live Tables [pipelines](https://docs.databricks.com/api/workspace/pipelines/create). For information about pipelines, see [link](https://docs.databricks.com/dlt/index.html). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [link](https://docs.databricks.com/dev-tools/bundles/pipelines-tutorial.html)." + "markdownDescription": "This resource allows you to create [pipelines](https://docs.databricks.com/api/workspace/pipelines/create). For information about pipelines, see [link](https://docs.databricks.com/dlt/index.html). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [link](https://docs.databricks.com/dev-tools/bundles/pipelines-tutorial.html)." }, { "type": "string", @@ -1470,6 +1476,9 @@ "custom_tags": { "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/postgres.ProjectCustomTag" }, + "default_branch": { + "$ref": "#/$defs/string" + }, "default_endpoint_settings": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.ProjectDefaultEndpointSettings" }, @@ -1696,7 +1705,7 @@ "catalog_name", "name" ], - "markdownDescription": "The schema resource type allows you to define Unity Catalog [schemas](https://docs.databricks.com/api/workspace/schemas/create) for tables and other assets in your workflows and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations:\n\n- The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema.\n- Only fields supported by the corresponding [Schemas object create API](https://docs.databricks.com/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](https://docs.databricks.com/api/workspace/schemas/update)." + "markdownDescription": "The schema resource type allows you to define Unity Catalog [schemas](https://docs.databricks.com/api/workspace/schemas/create) for tables and other assets in your jobs and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations:\n\n- The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema.\n- Only fields supported by the corresponding [Schemas object create API](https://docs.databricks.com/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](https://docs.databricks.com/api/workspace/schemas/update)." }, { "type": "string", @@ -1925,6 +1934,47 @@ } ] }, + "resources.VectorSearchEndpoint": { + "oneOf": [ + { + "type": "object", + "properties": { + "budget_policy_id": { + "$ref": "#/$defs/string" + }, + "endpoint_type": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" + }, + "min_qps": { + "$ref": "#/$defs/int64" + }, + "name": { + "$ref": "#/$defs/string" + }, + "permissions": { + "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.Permission" + }, + "usage_policy_id": { + "$ref": "#/$defs/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + } + }, + "additionalProperties": false, + "required": [ + "endpoint_type", + "name" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.Volume": { "oneOf": [ { @@ -2510,6 +2560,9 @@ "synced_database_tables": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable" }, + "vector_search_endpoints": { + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint" + }, "volumes": { "description": "The volume definitions for the bundle, where each key is the name of the volume.", "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.Volume", @@ -2654,7 +2707,7 @@ "$ref": "#/$defs/string" }, "artifact_path": { - "description": "The artifact path to use within the workspace for both deployments and workflow runs", + "description": "The artifact path to use within the workspace for both deployments and job runs", "$ref": "#/$defs/string" }, "auth_type": { @@ -2690,11 +2743,11 @@ "$ref": "#/$defs/string" }, "experimental_is_unified_host": { - "description": "Experimental feature flag to indicate if the host is a unified host", + "description": "Deprecated: no-op. Unified hosts are now detected automatically from /.well-known/databricks-config. Retained for schema compatibility with existing databricks.yml files.", "$ref": "#/$defs/bool" }, "file_path": { - "description": "The file path to use within the workspace for both deployments and workflow runs", + "description": "The file path to use within the workspace for both deployments and job runs", "$ref": "#/$defs/string" }, "google_service_account": { @@ -2905,9 +2958,7 @@ "$ref": "#/$defs/string" }, "postgres": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgres", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgres" }, "secret": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourceSecret" @@ -2937,6 +2988,14 @@ "oneOf": [ { "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/string" + }, + "permission": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourceAppAppPermission" + } + }, "additionalProperties": false }, { @@ -2945,6 +3004,20 @@ } ] }, + "apps.AppResourceAppAppPermission": { + "oneOf": [ + { + "type": "string", + "enum": [ + "CAN_USE" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "apps.AppResourceDatabase": { "oneOf": [ { @@ -3119,19 +3192,13 @@ "type": "object", "properties": { "branch": { - "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/string" }, "database": { - "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/string" }, "permission": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgresPostgresPermission", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgresPostgresPermission" } }, "additionalProperties": false @@ -3573,6 +3640,32 @@ } ] }, + "catalog.AzureEncryptionSettings": { + "oneOf": [ + { + "type": "object", + "properties": { + "azure_cmk_access_connector_id": { + "$ref": "#/$defs/string" + }, + "azure_cmk_managed_identity_id": { + "$ref": "#/$defs/string" + }, + "azure_tenant_id": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "azure_tenant_id" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "catalog.AzureQueueStorage": { "oneOf": [ { @@ -3614,6 +3707,33 @@ } ] }, + "catalog.EncryptionSettings": { + "oneOf": [ + { + "type": "object", + "description": "Encryption Settings are used to carry metadata for securable encryption at rest.\nCurrently used for catalogs, we can use the information supplied here to interact with a CMK.", + "properties": { + "azure_encryption_settings": { + "description": "optional Azure settings - only required if an Azure CMK is used.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.AzureEncryptionSettings" + }, + "azure_key_vault_key_id": { + "description": "the AKV URL in Azure, null otherwise.", + "$ref": "#/$defs/string" + }, + "customer_managed_key_id": { + "description": "the CMK uuid in AWS and GCP, null otherwise.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "catalog.FileEventQueue": { "oneOf": [ { @@ -4592,7 +4712,7 @@ "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines.\nIn this minimal environment spec, only pip and java dependencies are supported.", "properties": { "base_environment": { - "description": "The `base_environment` key refers to an `env.yaml` file that specifies an environment version and a collection of dependencies required for the environment setup.\nThis `env.yaml` file may itself include a `base_environment` reference pointing to another `env_1.yaml` file. However, when used as a base environment, `env_1.yaml` (or further nested references) will not be processed or included in the final environment, meaning that the resolution of `base_environment` references is not recursive.", + "description": "The base environment this environment is built on top of. A base environment defines the environment version and a\nlist of dependencies for serverless compute. The value can be a file path to a custom `env.yaml` file\n(e.g., `/Workspace/path/to/env.yaml`). Support for a Databricks-provided base environment ID\n(e.g., `workspace-base-environments/databricks_ai_v4`) and workspace base environment ID\n(e.g., `workspace-base-environments/dbe_b849b66e-b31a-4cb5-b161-1f2b10877fb7`) is in Beta.\nEither `environment_version` or `base_environment` can be provided. For more information, see", "$ref": "#/$defs/string" }, "client": { @@ -7282,7 +7402,7 @@ "type": "object", "properties": { "alert_task": { - "description": "New alert v2 task", + "description": "The task evaluates a Databricks alert and sends notifications to subscribers\nwhen the `alert_task` field is present.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.AlertTask" }, "clean_rooms_notebook_task": { @@ -7777,6 +7897,43 @@ } ] }, + "pipelines.ConnectorOptions": { + "oneOf": [ + { + "type": "object", + "description": "Wrapper message for source-specific options to support multiple connector types", + "properties": { + "gdrive_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "google_ads_options": { + "description": "Google Ads specific options for ingestion (object-level).\nWhen set, these values override the corresponding fields in GoogleAdsConfig\n(source_configurations).", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "sharepoint_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "tiktok_ads_options": { + "description": "TikTok Ads specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.ConnectorType": { "oneOf": [ { @@ -7907,6 +8064,125 @@ } ] }, + "pipelines.FileFilter": { + "oneOf": [ + { + "type": "object", + "properties": { + "modified_after": { + "description": "Include files with modification times occurring after the specified time.\nTimestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00)\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters", + "$ref": "#/$defs/string" + }, + "modified_before": { + "description": "Include files with modification times occurring before the specified time.\nTimestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00)\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters", + "$ref": "#/$defs/string" + }, + "path_filter": { + "description": "Include files with file names matching the pattern\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#path-glob-filter", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.FileIngestionOptions": { + "oneOf": [ + { + "type": "object", + "properties": { + "corrupt_record_column": { + "$ref": "#/$defs/string" + }, + "file_filters": { + "description": "Generic options", + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter" + }, + "format": { + "description": "required for TableSpec", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFileFormat" + }, + "format_options": { + "description": "Format-specific options\nBased on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/options#file-format-options", + "$ref": "#/$defs/map/string" + }, + "ignore_corrupt_files": { + "$ref": "#/$defs/bool" + }, + "infer_column_types": { + "$ref": "#/$defs/bool" + }, + "reader_case_sensitive": { + "description": "Column name case sensitivity\nhttps://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#change-case-sensitive-behavior", + "$ref": "#/$defs/bool" + }, + "rescued_data_column": { + "$ref": "#/$defs/string" + }, + "schema_evolution_mode": { + "description": "Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode" + }, + "schema_hints": { + "description": "Override inferred schema of specific columns\nBased on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#override-schema-inference-with-schema-hints", + "$ref": "#/$defs/string" + }, + "single_variant_column": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.FileIngestionOptionsFileFormat": { + "oneOf": [ + { + "type": "string", + "enum": [ + "BINARYFILE", + "JSON", + "CSV", + "XML", + "EXCEL", + "PARQUET", + "AVRO", + "ORC" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.FileIngestionOptionsSchemaEvolutionMode": { + "oneOf": [ + { + "type": "string", + "description": "Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work", + "enum": [ + "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", + "ADD_NEW_COLUMNS", + "RESCUE", + "FAIL_ON_NEW_COLUMNS", + "NONE" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.FileLibrary": { "oneOf": [ { @@ -7947,6 +8223,76 @@ } ] }, + "pipelines.GoogleAdsOptions": { + "oneOf": [ + { + "type": "object", + "description": "Google Ads specific options for ingestion (object-level).\nWhen set, these values override the corresponding fields in GoogleAdsConfig\n(source_configurations).", + "properties": { + "lookback_window_days": { + "description": "(Optional) Number of days to look back for report tables to capture late-arriving data.\nIf not specified, defaults to 30 days.", + "$ref": "#/$defs/int" + }, + "manager_account_id": { + "description": "(Optional at this level) Manager Account ID (also called MCC Account ID) used to list\nand access customer accounts under this manager account.\nOverrides GoogleAdsConfig.manager_account_id from source_configurations when set.", + "$ref": "#/$defs/string" + }, + "sync_start_date": { + "description": "(Optional) Start date for the initial sync of report tables in YYYY-MM-DD format.\nThis determines the earliest date from which to sync historical data.\nIf not specified, defaults to 2 years of historical data.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "manager_account_id" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.GoogleDriveOptions": { + "oneOf": [ + { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoogleDriveEntityType" + }, + "file_ingestion_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + }, + "url": { + "description": "Google Drive URL.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.GoogleDriveOptionsGoogleDriveEntityType": { + "oneOf": [ + { + "type": "string", + "enum": [ + "FILE", + "FILE_METADATA", + "PERMISSION" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.IngestionConfig": { "oneOf": [ { @@ -8185,6 +8531,7 @@ "TERADATA", "SHAREPOINT", "DYNAMICS365", + "GOOGLE_DRIVE", "FOREIGN_CATALOG" ] }, @@ -8700,6 +9047,12 @@ { "type": "object", "properties": { + "connector_options": { + "description": "(Optional) Source Specific Connector Options", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "destination_catalog": { "description": "Required. Destination catalog to store tables.", "$ref": "#/$defs/string" @@ -8734,6 +9087,49 @@ } ] }, + "pipelines.SharepointOptions": { + "oneOf": [ + { + "type": "object", + "properties": { + "entity_type": { + "description": "(Optional) The type of SharePoint entity to ingest.\nIf not specified, defaults to FILE.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptionsSharepointEntityType" + }, + "file_ingestion_options": { + "description": "(Optional) File ingestion options for processing files.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + }, + "url": { + "description": "Required. The SharePoint URL.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.SharepointOptionsSharepointEntityType": { + "oneOf": [ + { + "type": "string", + "enum": [ + "FILE", + "FILE_METADATA", + "PERMISSION", + "LIST" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.SourceCatalogConfig": { "oneOf": [ { @@ -8780,6 +9176,12 @@ { "type": "object", "properties": { + "connector_options": { + "description": "(Optional) Source Specific Connector Options", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "destination_catalog": { "description": "Required. Destination catalog to store table.", "$ref": "#/$defs/string" @@ -8902,6 +9304,87 @@ } ] }, + "pipelines.TikTokAdsOptions": { + "oneOf": [ + { + "type": "object", + "description": "TikTok Ads specific options for ingestion", + "properties": { + "data_level": { + "description": "(Optional) Data level for the report.\nIf not specified, defaults to AUCTION_CAMPAIGN.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokDataLevel" + }, + "dimensions": { + "description": "(Optional) Dimensions to include in the report.\nExamples: \"campaign_id\", \"adgroup_id\", \"ad_id\", \"stat_time_day\", \"stat_time_hour\"\nIf not specified, defaults to campaign_id.", + "$ref": "#/$defs/slice/string" + }, + "lookback_window_days": { + "description": "(Optional) Number of days to look back for report tables during incremental sync\nto capture late-arriving conversions and attribution data.\nIf not specified, defaults to 7 days.", + "$ref": "#/$defs/int" + }, + "metrics": { + "description": "(Optional) Metrics to include in the report.\nExamples: \"spend\", \"impressions\", \"clicks\", \"conversion\", \"cpc\"\nIf not specified, defaults to basic metrics (spend, impressions, clicks, etc.)", + "$ref": "#/$defs/slice/string" + }, + "query_lifetime": { + "description": "(Optional) Whether to request lifetime metrics (all-time aggregated data).\nWhen true, the report returns all-time data.\nIf not specified, defaults to false.", + "$ref": "#/$defs/bool" + }, + "report_type": { + "description": "(Optional) Report type for the TikTok Ads API.\nIf not specified, defaults to BASIC.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokReportType" + }, + "sync_start_date": { + "description": "(Optional) Start date for the initial sync of report tables in YYYY-MM-DD format.\nThis determines the earliest date from which to sync historical data.\nIf not specified, defaults to 1 year of historical data for daily reports\nand 30 days for hourly reports.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.TikTokAdsOptionsTikTokDataLevel": { + "oneOf": [ + { + "type": "string", + "description": "Data level for TikTok Ads report aggregation.", + "enum": [ + "AUCTION_ADVERTISER", + "AUCTION_CAMPAIGN", + "AUCTION_ADGROUP", + "AUCTION_AD" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.TikTokAdsOptionsTikTokReportType": { + "oneOf": [ + { + "type": "string", + "description": "Report type for TikTok Ads API.", + "enum": [ + "BASIC", + "AUDIENCE", + "PLAYABLE_AD", + "DSA", + "BUSINESS_CENTER", + "GMV_MAX" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "postgres.EndpointGroupSpec": { "oneOf": [ { @@ -10566,6 +11049,22 @@ } ] }, + "vectorsearch.EndpointType": { + "oneOf": [ + { + "type": "string", + "description": "Type of endpoint.", + "enum": [ + "STORAGE_OPTIMIZED", + "STANDARD" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "workspace.AzureKeyVaultSecretScopeMetadata": { "oneOf": [ { @@ -10982,6 +11481,20 @@ } ] }, + "resources.VectorSearchEndpoint": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint" + } + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.Volume": { "oneOf": [ { @@ -11629,6 +12142,20 @@ } ] }, + "pipelines.FileFilter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter" + } + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.IngestionConfig": { "oneOf": [ { @@ -11907,9 +12434,9 @@ "markdownDescription": "A Map that defines the resources for the bundle, where each key is the name of the resource, and the value is a Map that defines the resource. For more information about Declarative Automation Bundles supported resources, and resource definition reference, see [link](https://docs.databricks.com/dev-tools/bundles/resources.html).\n\n```yaml\nresources:\n \u003cresource-type\u003e:\n \u003cresource-name\u003e:\n \u003cresource-field-name\u003e: \u003cresource-field-value\u003e\n```" }, "run_as": { - "description": "The identity to use when running Declarative Automation Bundles workflows.", + "description": "The identity to use when running Declarative Automation Bundles resources.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.JobRunAs", - "markdownDescription": "The identity to use when running Declarative Automation Bundles workflows. See [link](https://docs.databricks.com/dev-tools/bundles/run-as.html)." + "markdownDescription": "The identity to use when running Declarative Automation Bundles resources. See [link](https://docs.databricks.com/dev-tools/bundles/run-as.html)." }, "scripts": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config.Script" diff --git a/bundle/schema/jsonschema_for_docs.json b/bundle/schema/jsonschema_for_docs.json index bcb68662967..3c13ff8c134 100644 --- a/bundle/schema/jsonschema_for_docs.json +++ b/bundle/schema/jsonschema_for_docs.json @@ -119,7 +119,7 @@ }, "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", + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.LifecycleWithStarted", "x-since-version": "v0.268.0" }, "name": { @@ -244,6 +244,12 @@ "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle", "x-since-version": "v0.287.0" }, + "managed_encryption_settings": { + "description": "Control CMK encryption for managed catalog data", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.EncryptionSettings", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "name": { "$ref": "#/$defs/string", "x-since-version": "v0.287.0" @@ -890,6 +896,22 @@ }, "additionalProperties": false }, + "resources.LifecycleWithStarted": { + "type": "object", + "properties": { + "prevent_destroy": { + "description": "Lifecycle setting to prevent the resource from being destroyed.", + "$ref": "#/$defs/bool", + "x-since-version": "v0.297.0" + }, + "started": { + "description": "Lifecycle setting to deploy the resource in started mode. Only supported for apps, clusters, and sql_warehouses in direct deployment mode.", + "$ref": "#/$defs/bool", + "x-since-version": "v0.297.0" + } + }, + "additionalProperties": false + }, "resources.MlflowExperiment": { "type": "object", "properties": { @@ -1295,7 +1317,7 @@ } }, "additionalProperties": false, - "markdownDescription": "The pipeline resource allows you to create Delta Live Tables [pipelines](https://docs.databricks.com/api/workspace/pipelines/create). For information about pipelines, see [link](https://docs.databricks.com/dlt/index.html). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [link](https://docs.databricks.com/dev-tools/bundles/pipelines-tutorial.html)." + "markdownDescription": "This resource allows you to create [pipelines](https://docs.databricks.com/api/workspace/pipelines/create). For information about pipelines, see [link](https://docs.databricks.com/dlt/index.html). For a tutorial that uses the Declarative Automation Bundles template to create a pipeline, see [link](https://docs.databricks.com/dev-tools/bundles/pipelines-tutorial.html)." }, "resources.PipelinePermission": { "type": "object", @@ -1438,6 +1460,9 @@ "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/postgres.ProjectCustomTag", "x-since-version": "v0.290.0" }, + "default_branch": { + "$ref": "#/$defs/string" + }, "default_endpoint_settings": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.ProjectDefaultEndpointSettings", "x-since-version": "v0.287.0" @@ -1687,7 +1712,7 @@ "catalog_name", "name" ], - "markdownDescription": "The schema resource type allows you to define Unity Catalog [schemas](https://docs.databricks.com/api/workspace/schemas/create) for tables and other assets in your workflows and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations:\n\n- The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema.\n- Only fields supported by the corresponding [Schemas object create API](https://docs.databricks.com/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](https://docs.databricks.com/api/workspace/schemas/update)." + "markdownDescription": "The schema resource type allows you to define Unity Catalog [schemas](https://docs.databricks.com/api/workspace/schemas/create) for tables and other assets in your jobs and pipelines created as part of a bundle. A schema, different from other resource types, has the following limitations:\n\n- The owner of a schema resource is always the deployment user, and cannot be changed. If `run_as` is specified in the bundle, it will be ignored by operations on the schema.\n- Only fields supported by the corresponding [Schemas object create API](https://docs.databricks.com/api/workspace/schemas/create) are available for the schema resource. For example, `enable_predictive_optimization` is not supported as it is only available on the [update API](https://docs.databricks.com/api/workspace/schemas/update)." }, "resources.SecretScope": { "type": "object", @@ -1895,6 +1920,39 @@ "name" ] }, + "resources.VectorSearchEndpoint": { + "type": "object", + "properties": { + "budget_policy_id": { + "$ref": "#/$defs/string" + }, + "endpoint_type": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" + }, + "min_qps": { + "$ref": "#/$defs/int64" + }, + "name": { + "$ref": "#/$defs/string" + }, + "permissions": { + "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.Permission" + }, + "usage_policy_id": { + "$ref": "#/$defs/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + } + }, + "additionalProperties": false, + "required": [ + "endpoint_type", + "name" + ] + }, "resources.Volume": { "type": "object", "properties": { @@ -2466,6 +2524,9 @@ "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable", "x-since-version": "v0.266.0" }, + "vector_search_endpoints": { + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint" + }, "volumes": { "description": "The volume definitions for the bundle, where each key is the name of the volume.", "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.Volume", @@ -2592,8 +2653,13 @@ "config.Workspace": { "type": "object", "properties": { + "account_id": { + "description": "The Databricks account ID.", + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" + }, "artifact_path": { - "description": "The artifact path to use within the workspace for both deployments and workflow runs", + "description": "The artifact path to use within the workspace for both deployments and job runs", "$ref": "#/$defs/string", "x-since-version": "v0.229.0" }, @@ -2643,7 +2709,7 @@ "x-since-version": "v0.285.0" }, "file_path": { - "description": "The file path to use within the workspace for both deployments and workflow runs", + "description": "The file path to use within the workspace for both deployments and job runs", "$ref": "#/$defs/string", "x-since-version": "v0.229.0" }, @@ -2813,8 +2879,6 @@ }, "postgres": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgres", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.294.0" }, "secret": { @@ -2841,8 +2905,22 @@ }, "apps.AppResourceApp": { "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/string" + }, + "permission": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourceAppAppPermission" + } + }, "additionalProperties": false }, + "apps.AppResourceAppAppPermission": { + "type": "string", + "enum": [ + "CAN_USE" + ] + }, "apps.AppResourceDatabase": { "type": "object", "properties": { @@ -2962,20 +3040,14 @@ "properties": { "branch": { "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.294.0" }, "database": { "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.294.0" }, "permission": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourcePostgresPostgresPermission", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.294.0" } }, @@ -3268,6 +3340,24 @@ }, "additionalProperties": false }, + "catalog.AzureEncryptionSettings": { + "type": "object", + "properties": { + "azure_cmk_access_connector_id": { + "$ref": "#/$defs/string" + }, + "azure_cmk_managed_identity_id": { + "$ref": "#/$defs/string" + }, + "azure_tenant_id": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "azure_tenant_id" + ] + }, "catalog.AzureQueueStorage": { "type": "object", "properties": { @@ -3297,6 +3387,25 @@ }, "additionalProperties": false }, + "catalog.EncryptionSettings": { + "type": "object", + "description": "Encryption Settings are used to carry metadata for securable encryption at rest.\nCurrently used for catalogs, we can use the information supplied here to interact with a CMK.", + "properties": { + "azure_encryption_settings": { + "description": "optional Azure settings - only required if an Azure CMK is used.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.AzureEncryptionSettings" + }, + "azure_key_vault_key_id": { + "description": "the AKV URL in Azure, null otherwise.", + "$ref": "#/$defs/string" + }, + "customer_managed_key_id": { + "description": "the CMK uuid in AWS and GCP, null otherwise.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, "catalog.FileEventQueue": { "type": "object", "properties": { @@ -4100,7 +4209,7 @@ "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and DLT's environment for classic and serverless pipelines.\nIn this minimal environment spec, only pip and java dependencies are supported.", "properties": { "base_environment": { - "description": "The `base_environment` key refers to an `env.yaml` file that specifies an environment version and a collection of dependencies required for the environment setup.\nThis `env.yaml` file may itself include a `base_environment` reference pointing to another `env_1.yaml` file. However, when used as a base environment, `env_1.yaml` (or further nested references) will not be processed or included in the final environment, meaning that the resolution of `base_environment` references is not recursive.", + "description": "The base environment this environment is built on top of. A base environment defines the environment version and a\nlist of dependencies for serverless compute. The value can be a file path to a custom `env.yaml` file\n(e.g., `/Workspace/path/to/env.yaml`). Support for a Databricks-provided base environment ID\n(e.g., `workspace-base-environments/databricks_ai_v4`) and workspace base environment ID\n(e.g., `workspace-base-environments/dbe_b849b66e-b31a-4cb5-b161-1f2b10877fb7`) is in Beta.\nEither `environment_version` or `base_environment` can be provided. For more information, see", "$ref": "#/$defs/string", "x-since-version": "v0.289.0" }, @@ -4753,19 +4862,23 @@ "properties": { "alert_id": { "description": "The alert_id is the canonical identifier of the alert.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" }, "subscribers": { "description": "The subscribers receive alert evaluation result notifications after the alert task is completed.\nThe number of subscriptions is limited to 100.", - "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/jobs.AlertTaskSubscriber" + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/jobs.AlertTaskSubscriber", + "x-since-version": "v0.296.0" }, "warehouse_id": { "description": "The warehouse_id identifies the warehouse settings used by the alert task.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" }, "workspace_path": { "description": "The workspace_path is the path to the alert file in the workspace. The path:\n* must start with \"/Workspace\"\n* must be a normalized path.\nUser has to select only one of alert_id or workspace_path to identify the alert.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" } }, "additionalProperties": false @@ -4775,11 +4888,13 @@ "description": "Represents a subscriber that will receive alert notifications.\nA subscriber can be either a user (via email) or a notification destination (via destination_id).", "properties": { "destination_id": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" }, "user_name": { "description": "A valid workspace email address.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" } }, "additionalProperties": false @@ -6158,8 +6273,9 @@ "type": "object", "properties": { "alert_task": { - "description": "New alert v2 task", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.AlertTask" + "description": "The task evaluates a Databricks alert and sends notifications to subscribers\nwhen the `alert_task` field is present.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.AlertTask", + "x-since-version": "v0.296.0" }, "clean_rooms_notebook_task": { "description": "The task runs a [clean rooms](https://docs.databricks.com/clean-rooms/index.html) notebook\nwhen the `clean_rooms_notebook_task` field is present.", @@ -6609,6 +6725,35 @@ }, "additionalProperties": false }, + "pipelines.ConnectorOptions": { + "type": "object", + "description": "Wrapper message for source-specific options to support multiple connector types", + "properties": { + "gdrive_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "google_ads_options": { + "description": "Google Ads specific options for ingestion (object-level).\nWhen set, these values override the corresponding fields in GoogleAdsConfig\n(source_configurations).", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "sharepoint_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "tiktok_ads_options": { + "description": "TikTok Ads specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + } + }, + "additionalProperties": false + }, "pipelines.ConnectorType": { "type": "string", "description": "For certain database sources LakeFlow Connect offers both query based and cdc\ningestion, ConnectorType can bse used to convey the type of ingestion.\nIf connection_name is provided for database sources, we default to Query Based ingestion", @@ -6637,15 +6782,18 @@ "properties": { "catalog_name": { "description": "(Required, Immutable) The name of the catalog for the connector's staging storage location.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" }, "schema_name": { "description": "(Required, Immutable) The name of the schema for the connector's staging storage location.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" }, "volume_name": { "description": "(Optional) The Unity Catalog-compatible name for the storage location.\nThis is the volume to use for the data that is extracted by the connector.\nSpark Declarative Pipelines system will automatically create the volume under the catalog and schema.\nFor Combined Cdc Managed Ingestion pipelines default name for the volume would be :\n__databricks_ingestion_gateway_staging_data-$pipelineId", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.296.0" } }, "additionalProperties": false, @@ -6696,6 +6844,93 @@ }, "additionalProperties": false }, + "pipelines.FileFilter": { + "type": "object", + "properties": { + "modified_after": { + "description": "Include files with modification times occurring after the specified time.\nTimestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00)\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters", + "$ref": "#/$defs/string" + }, + "modified_before": { + "description": "Include files with modification times occurring before the specified time.\nTimestamp format: YYYY-MM-DDTHH:mm:ss (e.g. 2020-06-01T13:00:00)\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#modification-time-path-filters", + "$ref": "#/$defs/string" + }, + "path_filter": { + "description": "Include files with file names matching the pattern\nBased on https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html#path-glob-filter", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.FileIngestionOptions": { + "type": "object", + "properties": { + "corrupt_record_column": { + "$ref": "#/$defs/string" + }, + "file_filters": { + "description": "Generic options", + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter" + }, + "format": { + "description": "required for TableSpec", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFileFormat" + }, + "format_options": { + "description": "Format-specific options\nBased on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/options#file-format-options", + "$ref": "#/$defs/map/string" + }, + "ignore_corrupt_files": { + "$ref": "#/$defs/bool" + }, + "infer_column_types": { + "$ref": "#/$defs/bool" + }, + "reader_case_sensitive": { + "description": "Column name case sensitivity\nhttps://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#change-case-sensitive-behavior", + "$ref": "#/$defs/bool" + }, + "rescued_data_column": { + "$ref": "#/$defs/string" + }, + "schema_evolution_mode": { + "description": "Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode" + }, + "schema_hints": { + "description": "Override inferred schema of specific columns\nBased on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#override-schema-inference-with-schema-hints", + "$ref": "#/$defs/string" + }, + "single_variant_column": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.FileIngestionOptionsFileFormat": { + "type": "string", + "enum": [ + "BINARYFILE", + "JSON", + "CSV", + "XML", + "EXCEL", + "PARQUET", + "AVRO", + "ORC" + ] + }, + "pipelines.FileIngestionOptionsSchemaEvolutionMode": { + "type": "string", + "description": "Based on https://docs.databricks.com/aws/en/ingestion/cloud-object-storage/auto-loader/schema#how-does-auto-loader-schema-evolution-work", + "enum": [ + "ADD_NEW_COLUMNS_WITH_TYPE_WIDENING", + "ADD_NEW_COLUMNS", + "RESCUE", + "FAIL_ON_NEW_COLUMNS", + "NONE" + ] + }, "pipelines.FileLibrary": { "type": "object", "properties": { @@ -6723,6 +6958,52 @@ }, "additionalProperties": false }, + "pipelines.GoogleAdsOptions": { + "type": "object", + "description": "Google Ads specific options for ingestion (object-level).\nWhen set, these values override the corresponding fields in GoogleAdsConfig\n(source_configurations).", + "properties": { + "lookback_window_days": { + "description": "(Optional) Number of days to look back for report tables to capture late-arriving data.\nIf not specified, defaults to 30 days.", + "$ref": "#/$defs/int" + }, + "manager_account_id": { + "description": "(Optional at this level) Manager Account ID (also called MCC Account ID) used to list\nand access customer accounts under this manager account.\nOverrides GoogleAdsConfig.manager_account_id from source_configurations when set.", + "$ref": "#/$defs/string" + }, + "sync_start_date": { + "description": "(Optional) Start date for the initial sync of report tables in YYYY-MM-DD format.\nThis determines the earliest date from which to sync historical data.\nIf not specified, defaults to 2 years of historical data.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "manager_account_id" + ] + }, + "pipelines.GoogleDriveOptions": { + "type": "object", + "properties": { + "entity_type": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoogleDriveEntityType" + }, + "file_ingestion_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + }, + "url": { + "description": "Google Drive URL.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.GoogleDriveOptionsGoogleDriveEntityType": { + "type": "string", + "enum": [ + "FILE", + "FILE_METADATA", + "PERMISSION" + ] + }, "pipelines.IngestionConfig": { "type": "object", "properties": { @@ -6801,13 +7082,15 @@ "description": "(Optional) Connector Type for sources. Ex: CDC, Query Based.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorType", "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "doNotSuggest": true, + "x-since-version": "v0.296.0" }, "data_staging_options": { "description": "(Optional) Location of staged data storage. This is required for migration from Cdc Managed Ingestion Pipeline\nwith Gateway pipeline to Combined Cdc Managed Ingestion Pipeline.\nIf not specified, the volume for staged data will be created in catalog and schema/target specified in the\ntop level pipeline definition.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.DataStagingOptions", "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "doNotSuggest": true, + "x-since-version": "v0.296.0" }, "full_refresh_window": { "description": "(Optional) A window that specifies a set of time ranges for snapshot queries in CDC.", @@ -6936,6 +7219,7 @@ "TERADATA", "SHAREPOINT", "DYNAMICS365", + "GOOGLE_DRIVE", "FOREIGN_CATALOG" ] }, @@ -7353,6 +7637,12 @@ "pipelines.SchemaSpec": { "type": "object", "properties": { + "connector_options": { + "description": "(Optional) Source Specific Connector Options", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "destination_catalog": { "description": "Required. Destination catalog to store tables.", "$ref": "#/$defs/string", @@ -7386,6 +7676,33 @@ "source_schema" ] }, + "pipelines.SharepointOptions": { + "type": "object", + "properties": { + "entity_type": { + "description": "(Optional) The type of SharePoint entity to ingest.\nIf not specified, defaults to FILE.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptionsSharepointEntityType" + }, + "file_ingestion_options": { + "description": "(Optional) File ingestion options for processing files.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + }, + "url": { + "description": "Required. The SharePoint URL.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.SharepointOptionsSharepointEntityType": { + "type": "string", + "enum": [ + "FILE", + "FILE_METADATA", + "PERMISSION", + "LIST" + ] + }, "pipelines.SourceCatalogConfig": { "type": "object", "description": "SourceCatalogConfig contains catalog-level custom configuration parameters for each source", @@ -7417,6 +7734,12 @@ "pipelines.TableSpec": { "type": "object", "properties": { + "connector_options": { + "description": "(Optional) Source Specific Connector Options", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "destination_catalog": { "description": "Required. Destination catalog to store table.", "$ref": "#/$defs/string", @@ -7534,6 +7857,63 @@ "APPEND_ONLY" ] }, + "pipelines.TikTokAdsOptions": { + "type": "object", + "description": "TikTok Ads specific options for ingestion", + "properties": { + "data_level": { + "description": "(Optional) Data level for the report.\nIf not specified, defaults to AUCTION_CAMPAIGN.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokDataLevel" + }, + "dimensions": { + "description": "(Optional) Dimensions to include in the report.\nExamples: \"campaign_id\", \"adgroup_id\", \"ad_id\", \"stat_time_day\", \"stat_time_hour\"\nIf not specified, defaults to campaign_id.", + "$ref": "#/$defs/slice/string" + }, + "lookback_window_days": { + "description": "(Optional) Number of days to look back for report tables during incremental sync\nto capture late-arriving conversions and attribution data.\nIf not specified, defaults to 7 days.", + "$ref": "#/$defs/int" + }, + "metrics": { + "description": "(Optional) Metrics to include in the report.\nExamples: \"spend\", \"impressions\", \"clicks\", \"conversion\", \"cpc\"\nIf not specified, defaults to basic metrics (spend, impressions, clicks, etc.)", + "$ref": "#/$defs/slice/string" + }, + "query_lifetime": { + "description": "(Optional) Whether to request lifetime metrics (all-time aggregated data).\nWhen true, the report returns all-time data.\nIf not specified, defaults to false.", + "$ref": "#/$defs/bool" + }, + "report_type": { + "description": "(Optional) Report type for the TikTok Ads API.\nIf not specified, defaults to BASIC.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokReportType" + }, + "sync_start_date": { + "description": "(Optional) Start date for the initial sync of report tables in YYYY-MM-DD format.\nThis determines the earliest date from which to sync historical data.\nIf not specified, defaults to 1 year of historical data for daily reports\nand 30 days for hourly reports.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.TikTokAdsOptionsTikTokDataLevel": { + "type": "string", + "description": "Data level for TikTok Ads report aggregation.", + "enum": [ + "AUCTION_ADVERTISER", + "AUCTION_CAMPAIGN", + "AUCTION_ADGROUP", + "AUCTION_AD" + ] + }, + "pipelines.TikTokAdsOptionsTikTokReportType": { + "type": "string", + "description": "Report type for TikTok Ads API.", + "enum": [ + "BASIC", + "AUDIENCE", + "PLAYABLE_AD", + "DSA", + "BUSINESS_CENTER", + "GMV_MAX" + ] + }, "postgres.EndpointGroupSpec": { "type": "object", "properties": { @@ -8850,6 +9230,14 @@ "CAN_VIEW" ] }, + "vectorsearch.EndpointType": { + "type": "string", + "description": "Type of endpoint.", + "enum": [ + "STORAGE_OPTIMIZED", + "STANDARD" + ] + }, "workspace.AzureKeyVaultSecretScopeMetadata": { "type": "object", "description": "The metadata of the Azure KeyVault for a secret scope of type `AZURE_KEYVAULT`", @@ -9028,6 +9416,12 @@ "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.SyncedDatabaseTable" } }, + "resources.VectorSearchEndpoint": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint" + } + }, "resources.Volume": { "type": "object", "additionalProperties": { @@ -9315,6 +9709,12 @@ "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.DayOfWeek" } }, + "pipelines.FileFilter": { + "type": "array", + "items": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter" + } + }, "pipelines.IngestionConfig": { "type": "array", "items": { @@ -9474,9 +9874,9 @@ "x-since-version": "v0.229.0" }, "run_as": { - "description": "The identity to use when running Declarative Automation Bundles workflows.", + "description": "The identity to use when running Declarative Automation Bundles resources.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.JobRunAs", - "markdownDescription": "The identity to use when running Declarative Automation Bundles workflows. See [link](https://docs.databricks.com/dev-tools/bundles/run-as.html).", + "markdownDescription": "The identity to use when running Declarative Automation Bundles resources. See [link](https://docs.databricks.com/dev-tools/bundles/run-as.html).", "x-since-version": "v0.229.0" }, "scripts": { diff --git a/bundle/statemgmt/check_running_resources.go b/bundle/statemgmt/check_running_resources.go index 4bf9285d7af..2ad5adf6da8 100644 --- a/bundle/statemgmt/check_running_resources.go +++ b/bundle/statemgmt/check_running_resources.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle/deploy/terraform" "github.com/databricks/cli/libs/diag" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" "golang.org/x/sync/errgroup" @@ -50,7 +51,7 @@ func (l *checkRunningResources) Apply(ctx context.Context, b *bundle.Bundle) dia } } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) err = checkAnyResourceRunning(ctx, w, state) if err != nil { return diag.FromErr(err) @@ -76,7 +77,6 @@ func checkAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, if resourceType == "jobs" { errs.Go(func() error { isRunning, err := IsJobRunning(errCtx, w, id) - // If there's an error retrieving the job, we assume it's not running if err != nil { return err } @@ -90,9 +90,8 @@ func checkAnyResourceRunning(ctx context.Context, w *databricks.WorkspaceClient, if resourceType == "pipelines" { errs.Go(func() error { isRunning, err := IsPipelineRunning(errCtx, w, id) - // If there's an error retrieving the pipeline, we assume it's not running if err != nil { - return nil + return err } if isRunning { return &ErrResourceIsRunning{resourceType: "pipeline", resourceId: id} @@ -112,6 +111,9 @@ func IsJobRunning(ctx context.Context, w *databricks.WorkspaceClient, jobId stri } runs, err := w.Jobs.ListRunsAll(ctx, jobs.ListRunsRequest{JobId: int64(id), ActiveOnly: true}) + if apierr.IsMissing(err) { + return false, nil + } if err != nil { return false, err } @@ -121,6 +123,9 @@ func IsJobRunning(ctx context.Context, w *databricks.WorkspaceClient, jobId stri func IsPipelineRunning(ctx context.Context, w *databricks.WorkspaceClient, pipelineId string) (bool, error) { resp, err := w.Pipelines.Get(ctx, pipelines.GetPipelineRequest{PipelineId: pipelineId}) + if apierr.IsMissing(err) { + return false, nil + } if err != nil { return false, err } diff --git a/bundle/statemgmt/check_running_resources_test.go b/bundle/statemgmt/check_running_resources_test.go index 233dd12181b..763af295d60 100644 --- a/bundle/statemgmt/check_running_resources_test.go +++ b/bundle/statemgmt/check_running_resources_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/pipelines" @@ -82,6 +83,37 @@ func TestIsAnyResourceRunningWithAPIFailure(t *testing.T) { PipelineId: "123", }).Return(nil, errors.New("API failure")).Once() + err := checkAnyResourceRunning(t.Context(), m.WorkspaceClient, resources) + require.ErrorContains(t, err, "API failure") +} + +func TestIsAnyResourceRunningWithDeletedJob(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + resources := ExportedResourcesMap{ + "resources.jobs.job1": {ID: "123"}, + } + + jobsApi := m.GetMockJobsAPI() + jobsApi.EXPECT().ListRunsAll(mock.Anything, jobs.ListRunsRequest{ + JobId: 123, + ActiveOnly: true, + }).Return(nil, &apierr.APIError{StatusCode: 404}).Once() + + err := checkAnyResourceRunning(t.Context(), m.WorkspaceClient, resources) + require.NoError(t, err) +} + +func TestIsAnyResourceRunningWithDeletedPipeline(t *testing.T) { + m := mocks.NewMockWorkspaceClient(t) + resources := ExportedResourcesMap{ + "resources.pipelines.pipeline1": {ID: "123"}, + } + + pipelineApi := m.GetMockPipelinesAPI() + pipelineApi.EXPECT().Get(mock.Anything, pipelines.GetPipelineRequest{ + PipelineId: "123", + }).Return(nil, &apierr.APIError{StatusCode: 404}).Once() + err := checkAnyResourceRunning(t.Context(), m.WorkspaceClient, resources) require.NoError(t, err) } diff --git a/bundle/statemgmt/state_load_test.go b/bundle/statemgmt/state_load_test.go index d86cca7789f..34c4fa4f5aa 100644 --- a/bundle/statemgmt/state_load_test.go +++ b/bundle/statemgmt/state_load_test.go @@ -16,6 +16,7 @@ import ( "github.com/databricks/databricks-sdk-go/service/postgres" "github.com/databricks/databricks-sdk-go/service/serving" "github.com/databricks/databricks-sdk-go/service/sql" + "github.com/databricks/databricks-sdk-go/service/vectorsearch" "github.com/stretchr/testify/assert" ) @@ -25,29 +26,30 @@ func TestStateToBundleEmptyLocalResources(t *testing.T) { } state := ExportedResourcesMap{ - "resources.jobs.test_job": {ID: "1"}, - "resources.pipelines.test_pipeline": {ID: "1"}, - "resources.models.test_mlflow_model": {ID: "1"}, - "resources.experiments.test_mlflow_experiment": {ID: "1"}, - "resources.model_serving_endpoints.test_model_serving": {ID: "1"}, - "resources.registered_models.test_registered_model": {ID: "1"}, - "resources.quality_monitors.test_monitor": {ID: "1"}, - "resources.catalogs.test_catalog": {ID: "1"}, - "resources.schemas.test_schema": {ID: "1"}, - "resources.external_locations.test_external_location": {ID: "1"}, - "resources.volumes.test_volume": {ID: "1"}, - "resources.clusters.test_cluster": {ID: "1"}, - "resources.dashboards.test_dashboard": {ID: "1"}, - "resources.apps.test_app": {ID: "app1"}, - "resources.secret_scopes.test_secret_scope": {ID: "secret_scope1"}, - "resources.sql_warehouses.test_sql_warehouse": {ID: "1"}, - "resources.database_instances.test_database_instance": {ID: "1"}, - "resources.database_catalogs.test_database_catalog": {ID: "1"}, - "resources.synced_database_tables.test_synced_database_table": {ID: "1"}, - "resources.alerts.test_alert": {ID: "1"}, - "resources.postgres_projects.test_postgres_project": {ID: "projects/test-project"}, - "resources.postgres_branches.test_postgres_branch": {ID: "projects/test-project/branches/main"}, - "resources.postgres_endpoints.test_postgres_endpoint": {ID: "projects/test-project/branches/main/endpoints/primary"}, + "resources.jobs.test_job": {ID: "1"}, + "resources.pipelines.test_pipeline": {ID: "1"}, + "resources.models.test_mlflow_model": {ID: "1"}, + "resources.experiments.test_mlflow_experiment": {ID: "1"}, + "resources.model_serving_endpoints.test_model_serving": {ID: "1"}, + "resources.registered_models.test_registered_model": {ID: "1"}, + "resources.quality_monitors.test_monitor": {ID: "1"}, + "resources.catalogs.test_catalog": {ID: "1"}, + "resources.schemas.test_schema": {ID: "1"}, + "resources.external_locations.test_external_location": {ID: "1"}, + "resources.volumes.test_volume": {ID: "1"}, + "resources.clusters.test_cluster": {ID: "1"}, + "resources.dashboards.test_dashboard": {ID: "1"}, + "resources.apps.test_app": {ID: "app1"}, + "resources.secret_scopes.test_secret_scope": {ID: "secret_scope1"}, + "resources.sql_warehouses.test_sql_warehouse": {ID: "1"}, + "resources.database_instances.test_database_instance": {ID: "1"}, + "resources.database_catalogs.test_database_catalog": {ID: "1"}, + "resources.synced_database_tables.test_synced_database_table": {ID: "1"}, + "resources.alerts.test_alert": {ID: "1"}, + "resources.postgres_projects.test_postgres_project": {ID: "projects/test-project"}, + "resources.postgres_branches.test_postgres_branch": {ID: "projects/test-project/branches/main"}, + "resources.postgres_endpoints.test_postgres_endpoint": {ID: "projects/test-project/branches/main/endpoints/primary"}, + "resources.vector_search_endpoints.test_vector_search_endpoint": {ID: "vs-endpoint-1"}, } err := StateToBundle(t.Context(), state, &config) assert.NoError(t, err) @@ -116,6 +118,9 @@ func TestStateToBundleEmptyLocalResources(t *testing.T) { assert.Equal(t, "projects/test-project/branches/main/endpoints/primary", config.Resources.PostgresEndpoints["test_postgres_endpoint"].ID) assert.Equal(t, resources.ModifiedStatusDeleted, config.Resources.PostgresEndpoints["test_postgres_endpoint"].ModifiedStatus) + assert.Equal(t, "vs-endpoint-1", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ID) + assert.Equal(t, resources.ModifiedStatusDeleted, config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ModifiedStatus) + AssertFullResourceCoverage(t, &config) } @@ -287,6 +292,13 @@ func TestStateToBundleEmptyRemoteResources(t *testing.T) { }, }, }, + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "test_vector_search_endpoint": { + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "test_vector_search_endpoint", + }, + }, + }, }, } @@ -362,6 +374,9 @@ func TestStateToBundleEmptyRemoteResources(t *testing.T) { assert.Equal(t, "", config.Resources.PostgresEndpoints["test_postgres_endpoint"].ID) assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.PostgresEndpoints["test_postgres_endpoint"].ModifiedStatus) + assert.Equal(t, "", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ID) + assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ModifiedStatus) + AssertFullResourceCoverage(t, &config) } @@ -646,49 +661,63 @@ func TestStateToBundleModifiedResources(t *testing.T) { }, }, }, + VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ + "test_vector_search_endpoint": { + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "test_vector_search_endpoint", + }, + }, + "test_vector_search_endpoint_new": { + CreateEndpoint: vectorsearch.CreateEndpoint{ + Name: "test_vector_search_endpoint_new", + }, + }, + }, }, } state := ExportedResourcesMap{ - "resources.jobs.test_job": {ID: "1"}, - "resources.jobs.test_job_old": {ID: "2"}, - "resources.pipelines.test_pipeline": {ID: "1"}, - "resources.pipelines.test_pipeline_old": {ID: "2"}, - "resources.models.test_mlflow_model": {ID: "1"}, - "resources.models.test_mlflow_model_old": {ID: "2"}, - "resources.experiments.test_mlflow_experiment": {ID: "1"}, - "resources.experiments.test_mlflow_experiment_old": {ID: "2"}, - "resources.model_serving_endpoints.test_model_serving": {ID: "1"}, - "resources.model_serving_endpoints.test_model_serving_old": {ID: "2"}, - "resources.registered_models.test_registered_model": {ID: "1"}, - "resources.registered_models.test_registered_model_old": {ID: "2"}, - "resources.quality_monitors.test_monitor": {ID: "test_monitor"}, - "resources.quality_monitors.test_monitor_old": {ID: "test_monitor_old"}, - "resources.catalogs.test_catalog": {ID: "1"}, - "resources.catalogs.test_catalog_old": {ID: "2"}, - "resources.schemas.test_schema": {ID: "1"}, - "resources.schemas.test_schema_old": {ID: "2"}, - "resources.volumes.test_volume": {ID: "1"}, - "resources.volumes.test_volume_old": {ID: "2"}, - "resources.clusters.test_cluster": {ID: "1"}, - "resources.clusters.test_cluster_old": {ID: "2"}, - "resources.dashboards.test_dashboard": {ID: "1"}, - "resources.dashboards.test_dashboard_old": {ID: "2"}, - "resources.apps.test_app": {ID: "test_app"}, - "resources.apps.test_app_old": {ID: "test_app_old"}, - "resources.secret_scopes.test_secret_scope": {ID: "test_secret_scope"}, - "resources.secret_scopes.test_secret_scope_old": {ID: "test_secret_scope_old"}, - "resources.sql_warehouses.test_sql_warehouse": {ID: "1"}, - "resources.sql_warehouses.test_sql_warehouse_old": {ID: "2"}, - "resources.database_instances.test_database_instance": {ID: "1"}, - "resources.database_instances.test_database_instance_old": {ID: "2"}, - "resources.alerts.test_alert": {ID: "1"}, - "resources.alerts.test_alert_old": {ID: "2"}, - "resources.postgres_projects.test_postgres_project": {ID: "projects/test-project"}, - "resources.postgres_projects.test_postgres_project_old": {ID: "projects/test-project-old"}, - "resources.postgres_branches.test_postgres_branch": {ID: "projects/test-project/branches/main"}, - "resources.postgres_branches.test_postgres_branch_old": {ID: "projects/test-project/branches/old"}, - "resources.postgres_endpoints.test_postgres_endpoint": {ID: "projects/test-project/branches/main/endpoints/primary"}, - "resources.postgres_endpoints.test_postgres_endpoint_old": {ID: "projects/test-project/branches/main/endpoints/old"}, + "resources.jobs.test_job": {ID: "1"}, + "resources.jobs.test_job_old": {ID: "2"}, + "resources.pipelines.test_pipeline": {ID: "1"}, + "resources.pipelines.test_pipeline_old": {ID: "2"}, + "resources.models.test_mlflow_model": {ID: "1"}, + "resources.models.test_mlflow_model_old": {ID: "2"}, + "resources.experiments.test_mlflow_experiment": {ID: "1"}, + "resources.experiments.test_mlflow_experiment_old": {ID: "2"}, + "resources.model_serving_endpoints.test_model_serving": {ID: "1"}, + "resources.model_serving_endpoints.test_model_serving_old": {ID: "2"}, + "resources.registered_models.test_registered_model": {ID: "1"}, + "resources.registered_models.test_registered_model_old": {ID: "2"}, + "resources.quality_monitors.test_monitor": {ID: "test_monitor"}, + "resources.quality_monitors.test_monitor_old": {ID: "test_monitor_old"}, + "resources.catalogs.test_catalog": {ID: "1"}, + "resources.catalogs.test_catalog_old": {ID: "2"}, + "resources.schemas.test_schema": {ID: "1"}, + "resources.schemas.test_schema_old": {ID: "2"}, + "resources.volumes.test_volume": {ID: "1"}, + "resources.volumes.test_volume_old": {ID: "2"}, + "resources.clusters.test_cluster": {ID: "1"}, + "resources.clusters.test_cluster_old": {ID: "2"}, + "resources.dashboards.test_dashboard": {ID: "1"}, + "resources.dashboards.test_dashboard_old": {ID: "2"}, + "resources.apps.test_app": {ID: "test_app"}, + "resources.apps.test_app_old": {ID: "test_app_old"}, + "resources.secret_scopes.test_secret_scope": {ID: "test_secret_scope"}, + "resources.secret_scopes.test_secret_scope_old": {ID: "test_secret_scope_old"}, + "resources.sql_warehouses.test_sql_warehouse": {ID: "1"}, + "resources.sql_warehouses.test_sql_warehouse_old": {ID: "2"}, + "resources.database_instances.test_database_instance": {ID: "1"}, + "resources.database_instances.test_database_instance_old": {ID: "2"}, + "resources.alerts.test_alert": {ID: "1"}, + "resources.alerts.test_alert_old": {ID: "2"}, + "resources.postgres_projects.test_postgres_project": {ID: "projects/test-project"}, + "resources.postgres_projects.test_postgres_project_old": {ID: "projects/test-project-old"}, + "resources.postgres_branches.test_postgres_branch": {ID: "projects/test-project/branches/main"}, + "resources.postgres_branches.test_postgres_branch_old": {ID: "projects/test-project/branches/old"}, + "resources.postgres_endpoints.test_postgres_endpoint": {ID: "projects/test-project/branches/main/endpoints/primary"}, + "resources.postgres_endpoints.test_postgres_endpoint_old": {ID: "projects/test-project/branches/main/endpoints/old"}, + "resources.vector_search_endpoints.test_vector_search_endpoint": {ID: "vs-endpoint-1"}, + "resources.vector_search_endpoints.test_vector_search_endpoint_old": {ID: "vs-endpoint-old"}, } err := StateToBundle(t.Context(), state, &config) assert.NoError(t, err) @@ -835,6 +864,13 @@ func TestStateToBundleModifiedResources(t *testing.T) { assert.Equal(t, "", config.Resources.PostgresEndpoints["test_postgres_endpoint_new"].ID) assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.PostgresEndpoints["test_postgres_endpoint_new"].ModifiedStatus) + assert.Equal(t, "vs-endpoint-1", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ID) + assert.Equal(t, "", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint"].ModifiedStatus) + assert.Equal(t, "vs-endpoint-old", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint_old"].ID) + assert.Equal(t, resources.ModifiedStatusDeleted, config.Resources.VectorSearchEndpoints["test_vector_search_endpoint_old"].ModifiedStatus) + assert.Equal(t, "", config.Resources.VectorSearchEndpoints["test_vector_search_endpoint_new"].ID) + assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.VectorSearchEndpoints["test_vector_search_endpoint_new"].ModifiedStatus) + AssertFullResourceCoverage(t, &config) } diff --git a/bundle/statemgmt/state_pull.go b/bundle/statemgmt/state_pull.go index 9a5f493a9f3..7e62bb84967 100644 --- a/bundle/statemgmt/state_pull.go +++ b/bundle/statemgmt/state_pull.go @@ -59,7 +59,7 @@ func (s *StateDesc) HasRemoteTerraformState() bool { func localRead(ctx context.Context, fullPath string, engine engine.EngineType) *StateDesc { content, err := os.ReadFile(fullPath) if err != nil { - if !os.IsNotExist(err) { + if !errors.Is(err, fs.ErrNotExist) { logdiag.LogError(ctx, fmt.Errorf("reading %s: %w", filepath.ToSlash(fullPath), err)) } return nil @@ -220,7 +220,7 @@ func readStates(ctx context.Context, b *bundle.Bundle, alwaysPull AlwaysPull) [] terraformLocalState := localRead(ctx, localPathTerraform, engine.EngineTerraform) if (directLocalState == nil && terraformLocalState == nil) || alwaysPull { - f, err := deploy.StateFiler(b) + f, err := deploy.StateFiler(ctx, b) if err != nil { logdiag.LogError(ctx, err) return nil diff --git a/bundle/statemgmt/state_push.go b/bundle/statemgmt/state_push.go index b2da9f893c0..f098e8a07cc 100644 --- a/bundle/statemgmt/state_push.go +++ b/bundle/statemgmt/state_push.go @@ -17,7 +17,7 @@ import ( // PushResourcesState uploads the local state file to the remote location. func PushResourcesState(ctx context.Context, b *bundle.Bundle, engine engine.EngineType) { - f, err := deploy.StateFiler(b) + f, err := deploy.StateFiler(ctx, b) if err != nil { logdiag.LogError(ctx, err) return @@ -53,7 +53,7 @@ func PushResourcesState(ctx context.Context, b *bundle.Bundle, engine engine.Eng } func BackupRemoteTerraformState(ctx context.Context, b *bundle.Bundle) { - f, err := deploy.StateFiler(b) + f, err := deploy.StateFiler(ctx, b) if err != nil { logdiag.LogError(ctx, err) return diff --git a/bundle/statemgmt/upload_state_for_yaml_sync.go b/bundle/statemgmt/upload_state_for_yaml_sync.go index aefe5aa2759..74def3174f8 100644 --- a/bundle/statemgmt/upload_state_for_yaml_sync.go +++ b/bundle/statemgmt/upload_state_for_yaml_sync.go @@ -76,7 +76,7 @@ func (m *uploadStateForYamlSync) Apply(ctx context.Context, b *bundle.Bundle) di } func uploadState(ctx context.Context, b *bundle.Bundle) error { - f, err := deploy.StateFiler(b) + f, err := deploy.StateFiler(ctx, b) if err != nil { return fmt.Errorf("failed to get state filer: %w", err) } @@ -173,7 +173,7 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun return false, fmt.Errorf("failed to create uninterpolated config: %w", err) } - plan, err := deploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(), &uninterpolatedConfig) + plan, err := deploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), &uninterpolatedConfig) if err != nil { return false, err } @@ -197,7 +197,7 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun } } - deploymentBundle.Apply(ctx, b.WorkspaceClient(), plan, direct.MigrateMode(true)) + deploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(true)) if err := deploymentBundle.StateDB.Finalize(); err != nil { return false, err } diff --git a/bundle/tests/include_test.go b/bundle/tests/include_test.go index c5ad2d58a10..50bf177fdfc 100644 --- a/bundle/tests/include_test.go +++ b/bundle/tests/include_test.go @@ -1,13 +1,14 @@ package config_tests import ( + "maps" "path/filepath" + "slices" "testing" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/phases" "github.com/databricks/cli/libs/logdiag" - "github.com/databricks/cli/libs/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -26,7 +27,7 @@ func TestIncludeInvalid(t *testing.T) { func TestIncludeWithGlob(t *testing.T) { b := load(t, "./include_with_glob") - keys := utils.SortedKeys(b.Config.Resources.Jobs) + keys := slices.Sorted(maps.Keys(b.Config.Resources.Jobs)) assert.Equal(t, []string{"my_job"}, keys) job := b.Config.Resources.Jobs["my_job"] @@ -46,7 +47,7 @@ func TestIncludeForMultipleMatches(t *testing.T) { b := load(t, "./include_multiple") // Test that both jobs were loaded. - keys := utils.SortedKeys(b.Config.Resources.Jobs) + keys := slices.Sorted(maps.Keys(b.Config.Resources.Jobs)) assert.Equal(t, []string{"my_first_job", "my_second_job"}, keys) first := b.Config.Resources.Jobs["my_first_job"] diff --git a/bundle/trampoline/python_dbr_warning.go b/bundle/trampoline/python_dbr_warning.go index d871193f6ec..4cc7a67dc8c 100644 --- a/bundle/trampoline/python_dbr_warning.go +++ b/bundle/trampoline/python_dbr_warning.go @@ -101,7 +101,7 @@ func hasIncompatibleWheelTasks(ctx context.Context, b *bundle.Bundle) diag.Diagn } version = cluster.SparkVersion } else { - version, err = getSparkVersionForCluster(ctx, b.WorkspaceClient(), task.ExistingClusterId) + version, err = getSparkVersionForCluster(ctx, b.WorkspaceClient(ctx), task.ExistingClusterId) // If there's error getting spark version for cluster, do not mark it as incompatible if err != nil { log.Warnf(ctx, "unable to get spark version for cluster %s, err: %s", task.ExistingClusterId, err.Error()) diff --git a/bundle/trampoline/python_wheel.go b/bundle/trampoline/python_wheel.go index af949991ed8..722a0b35e6c 100644 --- a/bundle/trampoline/python_wheel.go +++ b/bundle/trampoline/python_wheel.go @@ -160,7 +160,7 @@ func (t *pythonTrampoline) GetTemplateData(task *jobs.Task) (map[string]any, err func (t *pythonTrampoline) generateParameters(task *jobs.PythonWheelTask) (string, error) { if task.Parameters != nil && task.NamedParameters != nil { - return "", errors.New("not allowed to pass both paramaters and named_parameters") + return "", errors.New("not allowed to pass both parameters and named_parameters") } params := append([]string{task.PackageName}, task.Parameters...) for k, v := range task.NamedParameters { diff --git a/bundle/trampoline/python_wheel_test.go b/bundle/trampoline/python_wheel_test.go index 8b9669087fc..1ae42a063e0 100644 --- a/bundle/trampoline/python_wheel_test.go +++ b/bundle/trampoline/python_wheel_test.go @@ -66,7 +66,7 @@ func TestGenerateBoth(t *testing.T) { task := &jobs.PythonWheelTask{NamedParameters: map[string]string{"a": "1"}, Parameters: []string{"b"}} _, err := trampoline.generateParameters(task) require.Error(t, err) - require.ErrorContains(t, err, "not allowed to pass both paramaters and named_parameters") + require.ErrorContains(t, err, "not allowed to pass both parameters and named_parameters") } func TestTransformFiltersWheelTasksOnly(t *testing.T) { diff --git a/cmd/account/access-control/access-control.go b/cmd/account/access-control/access-control.go index 9dc0566f1ee..2dab1d83a99 100755 --- a/cmd/account/access-control/access-control.go +++ b/cmd/account/access-control/access-control.go @@ -92,6 +92,7 @@ func newGetAssignableRolesForResource() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -174,6 +175,7 @@ func newGetRuleSet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -240,6 +242,7 @@ func newUpdateRuleSet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/billable-usage/billable-usage.go b/cmd/account/billable-usage/billable-usage.go index 624c23c7384..d89b87e4440 100755 --- a/cmd/account/billable-usage/billable-usage.go +++ b/cmd/account/billable-usage/billable-usage.go @@ -95,6 +95,7 @@ func newDownload() *cobra.Command { if err != nil { return err } + defer response.Contents.Close() return cmdio.Render(ctx, response.Contents) } diff --git a/cmd/account/budget-policy/budget-policy.go b/cmd/account/budget-policy/budget-policy.go index 37adb68b13b..419168fd9ee 100755 --- a/cmd/account/budget-policy/budget-policy.go +++ b/cmd/account/budget-policy/budget-policy.go @@ -3,6 +3,8 @@ package budget_policy import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -94,6 +96,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -206,6 +209,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -234,12 +238,22 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq billing.ListBudgetPoliciesRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int // TODO: complex arg: filter_by cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, `The maximum number of budget policies to return.`) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `A page token, received from a previous ListServerlessPolicies call.`) // TODO: complex arg: sort_spec + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + cmd.Use = "list" cmd.Short = `List policies.` cmd.Long = `List policies. @@ -260,6 +274,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.BudgetPolicy.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -338,6 +359,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/budgets/budgets.go b/cmd/account/budgets/budgets.go index 9fb34e4664d..b6dfed84376 100755 --- a/cmd/account/budgets/budgets.go +++ b/cmd/account/budgets/budgets.go @@ -94,6 +94,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -208,6 +209,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -236,8 +238,17 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq billing.ListBudgetConfigurationsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `A page token received from a previous get all budget configurations call.`) + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" cmd.Short = `Get all budgets.` @@ -258,6 +269,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.Budgets.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -332,6 +350,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/credentials/credentials.go b/cmd/account/credentials/credentials.go index 2c117acf06b..a940bf547d7 100755 --- a/cmd/account/credentials/credentials.go +++ b/cmd/account/credentials/credentials.go @@ -106,6 +106,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -164,6 +165,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -221,6 +223,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -264,6 +267,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/csp-enablement-account/csp-enablement-account.go b/cmd/account/csp-enablement-account/csp-enablement-account.go index 657f12c4eb8..f4d5388981f 100755 --- a/cmd/account/csp-enablement-account/csp-enablement-account.go +++ b/cmd/account/csp-enablement-account/csp-enablement-account.go @@ -81,6 +81,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -146,6 +147,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/custom-app-integration/custom-app-integration.go b/cmd/account/custom-app-integration/custom-app-integration.go index 3a6d9d151d9..7e1f79bcef8 100755 --- a/cmd/account/custom-app-integration/custom-app-integration.go +++ b/cmd/account/custom-app-integration/custom-app-integration.go @@ -3,6 +3,8 @@ package custom_app_integration import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -103,6 +105,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -213,6 +216,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -241,10 +245,20 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListCustomAppIntegrationsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().BoolVar(&listReq.IncludeCreatorUsername, "include-creator-username", listReq.IncludeCreatorUsername, ``) cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" cmd.Short = `Get custom oauth app integrations.` @@ -266,6 +280,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.CustomAppIntegration.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/disable-legacy-features/disable-legacy-features.go b/cmd/account/disable-legacy-features/disable-legacy-features.go index cd34fa7af09..32d554869d2 100755 --- a/cmd/account/disable-legacy-features/disable-legacy-features.go +++ b/cmd/account/disable-legacy-features/disable-legacy-features.go @@ -80,6 +80,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -133,6 +134,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -197,6 +199,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/enable-ip-access-lists/enable-ip-access-lists.go b/cmd/account/enable-ip-access-lists/enable-ip-access-lists.go index 8c9da2ba77d..f1329c9732c 100755 --- a/cmd/account/enable-ip-access-lists/enable-ip-access-lists.go +++ b/cmd/account/enable-ip-access-lists/enable-ip-access-lists.go @@ -80,6 +80,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -133,6 +134,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -197,6 +199,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/encryption-keys/encryption-keys.go b/cmd/account/encryption-keys/encryption-keys.go index a60cf1fe4d8..dd0ee03b2c8 100755 --- a/cmd/account/encryption-keys/encryption-keys.go +++ b/cmd/account/encryption-keys/encryption-keys.go @@ -123,6 +123,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -180,6 +181,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -250,6 +252,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -292,6 +295,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/endpoints/endpoints.go b/cmd/account/endpoints/endpoints.go index 9e1d3e7bac1..3c8a78bbd1d 100755 --- a/cmd/account/endpoints/endpoints.go +++ b/cmd/account/endpoints/endpoints.go @@ -125,6 +125,7 @@ func newCreateEndpoint() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -233,6 +234,7 @@ func newGetEndpoint() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -261,9 +263,19 @@ func newListEndpoints() *cobra.Command { cmd := &cobra.Command{} var listEndpointsReq networking.ListEndpointsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listEndpointsLimit int cmd.Flags().IntVar(&listEndpointsReq.PageSize, "page-size", listEndpointsReq.PageSize, ``) - cmd.Flags().StringVar(&listEndpointsReq.PageToken, "page-token", listEndpointsReq.PageToken, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listEndpointsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listEndpointsReq.PageToken, "page-token", listEndpointsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-endpoints PARENT" cmd.Short = `List network endpoints.` @@ -290,6 +302,13 @@ func newListEndpoints() *cobra.Command { listEndpointsReq.Parent = args[0] response := a.Endpoints.ListEndpoints(ctx, listEndpointsReq) + if listEndpointsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listEndpointsLimit) + } + if listEndpointsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listEndpointsLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/esm-enablement-account/esm-enablement-account.go b/cmd/account/esm-enablement-account/esm-enablement-account.go index 3cbc8d5afaf..d7468cf8f68 100755 --- a/cmd/account/esm-enablement-account/esm-enablement-account.go +++ b/cmd/account/esm-enablement-account/esm-enablement-account.go @@ -79,6 +79,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -144,6 +145,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/federation-policy/federation-policy.go b/cmd/account/federation-policy/federation-policy.go index e8aba32696b..64610ef39df 100755 --- a/cmd/account/federation-policy/federation-policy.go +++ b/cmd/account/federation-policy/federation-policy.go @@ -3,6 +3,8 @@ package federation_policy import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -142,6 +144,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -250,6 +253,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -278,9 +282,19 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListAccountFederationPoliciesRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" cmd.Short = `List account federation policies.` @@ -299,6 +313,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.FederationPolicy.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -373,6 +394,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/groups-v2/groups-v2.go b/cmd/account/groups-v2/groups-v2.go index 669b21f84a9..678a9f5bb84 100755 --- a/cmd/account/groups-v2/groups-v2.go +++ b/cmd/account/groups-v2/groups-v2.go @@ -3,6 +3,8 @@ package groups_v2 import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -106,6 +108,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -218,6 +221,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -246,14 +250,25 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq iam.ListAccountGroupsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().StringVar(&listReq.Attributes, "attributes", listReq.Attributes, `Comma-separated list of attributes to return in response.`) - cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Desired number of results per page.`) cmd.Flags().StringVar(&listReq.ExcludedAttributes, "excluded-attributes", listReq.ExcludedAttributes, `Comma-separated list of attributes to exclude in response.`) cmd.Flags().StringVar(&listReq.Filter, "filter", listReq.Filter, `Query by which the results have to be filtered.`) cmd.Flags().StringVar(&listReq.SortBy, "sort-by", listReq.SortBy, `Attribute to sort the results.`) cmd.Flags().Var(&listReq.SortOrder, "sort-order", `The order to sort the results. Supported values: [ascending, descending]`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Flags().Int64Var(&listReq.StartIndex, "start-index", listReq.StartIndex, `Specifies the index of the first result.`) + cmd.Flags().Lookup("start-index").Hidden = true + cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Number of results per API page.`) + cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" cmd.Short = `List group details.` @@ -278,6 +293,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.GroupsV2.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/iam-v2/iam-v2.go b/cmd/account/iam-v2/iam-v2.go index d3617145c0b..028c48b47b9 100755 --- a/cmd/account/iam-v2/iam-v2.go +++ b/cmd/account/iam-v2/iam-v2.go @@ -100,6 +100,7 @@ func newGetWorkspaceAccessDetail() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -150,7 +151,7 @@ func newResolveGroup() *cobra.Command { if cmd.Flags().Changed("json") { err := root.ExactArgs(0)(cmd, args) if err != nil { - return fmt.Errorf("when --json flag is specified, no positional arguments are required. Provide 'external_id' in your JSON input") + return fmt.Errorf("when --json flag is specified, no positional arguments are allowed. Provide 'external_id' in your JSON input") } return nil } @@ -183,6 +184,7 @@ func newResolveGroup() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -232,7 +234,7 @@ func newResolveServicePrincipal() *cobra.Command { if cmd.Flags().Changed("json") { err := root.ExactArgs(0)(cmd, args) if err != nil { - return fmt.Errorf("when --json flag is specified, no positional arguments are required. Provide 'external_id' in your JSON input") + return fmt.Errorf("when --json flag is specified, no positional arguments are allowed. Provide 'external_id' in your JSON input") } return nil } @@ -265,6 +267,7 @@ func newResolveServicePrincipal() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -314,7 +317,7 @@ func newResolveUser() *cobra.Command { if cmd.Flags().Changed("json") { err := root.ExactArgs(0)(cmd, args) if err != nil { - return fmt.Errorf("when --json flag is specified, no positional arguments are required. Provide 'external_id' in your JSON input") + return fmt.Errorf("when --json flag is specified, no positional arguments are allowed. Provide 'external_id' in your JSON input") } return nil } @@ -347,6 +350,7 @@ func newResolveUser() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/ip-access-lists/ip-access-lists.go b/cmd/account/ip-access-lists/ip-access-lists.go index 32bf120db51..187cce71305 100755 --- a/cmd/account/ip-access-lists/ip-access-lists.go +++ b/cmd/account/ip-access-lists/ip-access-lists.go @@ -112,7 +112,7 @@ func newCreate() *cobra.Command { if cmd.Flags().Changed("json") { err := root.ExactArgs(0)(cmd, args) if err != nil { - return fmt.Errorf("when --json flag is specified, no positional arguments are required. Provide 'label', 'list_type' in your JSON input") + return fmt.Errorf("when --json flag is specified, no positional arguments are allowed. Provide 'label', 'list_type' in your JSON input") } return nil } @@ -152,6 +152,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -288,6 +289,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -313,6 +315,15 @@ var listOverrides []func( func newList() *cobra.Command { cmd := &cobra.Command{} + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Use = "list" cmd.Short = `Get access lists.` @@ -327,6 +338,13 @@ func newList() *cobra.Command { ctx := cmd.Context() a := cmdctx.AccountClient(ctx) response := a.IpAccessLists.List(ctx) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/llm-proxy-partner-powered-account/llm-proxy-partner-powered-account.go b/cmd/account/llm-proxy-partner-powered-account/llm-proxy-partner-powered-account.go index e80753e5991..fa094ed844b 100755 --- a/cmd/account/llm-proxy-partner-powered-account/llm-proxy-partner-powered-account.go +++ b/cmd/account/llm-proxy-partner-powered-account/llm-proxy-partner-powered-account.go @@ -78,6 +78,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -142,6 +143,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/llm-proxy-partner-powered-enforce/llm-proxy-partner-powered-enforce.go b/cmd/account/llm-proxy-partner-powered-enforce/llm-proxy-partner-powered-enforce.go index e5972a1aa2f..94428f62769 100755 --- a/cmd/account/llm-proxy-partner-powered-enforce/llm-proxy-partner-powered-enforce.go +++ b/cmd/account/llm-proxy-partner-powered-enforce/llm-proxy-partner-powered-enforce.go @@ -79,6 +79,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -144,6 +145,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/log-delivery/log-delivery.go b/cmd/account/log-delivery/log-delivery.go index e94e4194353..348df018025 100755 --- a/cmd/account/log-delivery/log-delivery.go +++ b/cmd/account/log-delivery/log-delivery.go @@ -178,6 +178,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -247,6 +248,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -275,12 +277,22 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq billing.ListLogDeliveryRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().StringVar(&listReq.CredentialsId, "credentials-id", listReq.CredentialsId, `The Credentials id to filter the search results with.`) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `A page token received from a previous get all budget configurations call.`) cmd.Flags().Var(&listReq.Status, "status", `The log delivery status to filter the search results with. Supported values: [DISABLED, ENABLED]`) cmd.Flags().StringVar(&listReq.StorageConfigurationId, "storage-configuration-id", listReq.StorageConfigurationId, `The Storage Configuration id to filter the search results with.`) + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + cmd.Use = "list" cmd.Short = `Get all log delivery configurations.` cmd.Long = `Get all log delivery configurations. @@ -301,6 +313,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.LogDelivery.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/metastore-assignments/metastore-assignments.go b/cmd/account/metastore-assignments/metastore-assignments.go index b1e574596f0..124d059cea1 100755 --- a/cmd/account/metastore-assignments/metastore-assignments.go +++ b/cmd/account/metastore-assignments/metastore-assignments.go @@ -105,6 +105,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -168,6 +169,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -230,6 +232,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -258,6 +261,15 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq catalog.ListAccountMetastoreAssignmentsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Use = "list METASTORE_ID" cmd.Short = `Get all workspaces assigned to a metastore.` @@ -284,6 +296,13 @@ func newList() *cobra.Command { listReq.MetastoreId = args[0] response := a.MetastoreAssignments.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -364,6 +383,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/metastores/metastores.go b/cmd/account/metastores/metastores.go index f2bfebacb18..8d58352bc3b 100755 --- a/cmd/account/metastores/metastores.go +++ b/cmd/account/metastores/metastores.go @@ -3,6 +3,8 @@ package metastores import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -94,6 +96,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -152,6 +155,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -208,6 +212,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -233,6 +238,15 @@ var listOverrides []func( func newList() *cobra.Command { cmd := &cobra.Command{} + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Use = "list" cmd.Short = `Get all metastores associated with an account.` @@ -247,6 +261,13 @@ func newList() *cobra.Command { ctx := cmd.Context() a := cmdctx.AccountClient(ctx) response := a.Metastores.List(ctx) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -320,6 +341,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/network-connectivity/network-connectivity.go b/cmd/account/network-connectivity/network-connectivity.go index 23c4e85dded..3f8c32ed001 100755 --- a/cmd/account/network-connectivity/network-connectivity.go +++ b/cmd/account/network-connectivity/network-connectivity.go @@ -103,7 +103,7 @@ func newCreateNetworkConnectivityConfiguration() *cobra.Command { if cmd.Flags().Changed("json") { err := root.ExactArgs(0)(cmd, args) if err != nil { - return fmt.Errorf("when --json flag is specified, no positional arguments are required. Provide 'name', 'region' in your JSON input") + return fmt.Errorf("when --json flag is specified, no positional arguments are allowed. Provide 'name', 'region' in your JSON input") } return nil } @@ -139,6 +139,7 @@ func newCreateNetworkConnectivityConfiguration() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -175,6 +176,7 @@ func newCreatePrivateEndpointRule() *cobra.Command { // TODO: array: domain_names cmd.Flags().StringVar(&createPrivateEndpointRuleReq.PrivateEndpointRule.EndpointService, "endpoint-service", createPrivateEndpointRuleReq.PrivateEndpointRule.EndpointService, `The full target AWS endpoint service name that connects to the destination resources of the private endpoint.`) cmd.Flags().StringVar(&createPrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, "error-message", createPrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, ``) + // TODO: complex arg: gcp_endpoint cmd.Flags().StringVar(&createPrivateEndpointRuleReq.PrivateEndpointRule.GroupId, "group-id", createPrivateEndpointRuleReq.PrivateEndpointRule.GroupId, `Not used by customer-managed private endpoint services.`) cmd.Flags().StringVar(&createPrivateEndpointRuleReq.PrivateEndpointRule.ResourceId, "resource-id", createPrivateEndpointRuleReq.PrivateEndpointRule.ResourceId, `The Azure resource ID of the target resource.`) // TODO: array: resource_names @@ -227,6 +229,7 @@ func newCreatePrivateEndpointRule() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -346,6 +349,7 @@ func newDeletePrivateEndpointRule() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -402,6 +406,7 @@ func newGetNetworkConnectivityConfiguration() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -460,6 +465,7 @@ func newGetPrivateEndpointRule() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -488,8 +494,17 @@ func newListNetworkConnectivityConfigurations() *cobra.Command { cmd := &cobra.Command{} var listNetworkConnectivityConfigurationsReq settings.ListNetworkConnectivityConfigurationsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listNetworkConnectivityConfigurationsLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listNetworkConnectivityConfigurationsLimit, "limit", 0, `Maximum number of results to return.`) - cmd.Flags().StringVar(&listNetworkConnectivityConfigurationsReq.PageToken, "page-token", listNetworkConnectivityConfigurationsReq.PageToken, `Pagination token to go to next page based on previous query.`) + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listNetworkConnectivityConfigurationsReq.PageToken, "page-token", listNetworkConnectivityConfigurationsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-network-connectivity-configurations" cmd.Short = `List network connectivity configurations.` @@ -510,6 +525,13 @@ func newListNetworkConnectivityConfigurations() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.NetworkConnectivity.ListNetworkConnectivityConfigurations(ctx, listNetworkConnectivityConfigurationsReq) + if listNetworkConnectivityConfigurationsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listNetworkConnectivityConfigurationsLimit) + } + if listNetworkConnectivityConfigurationsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listNetworkConnectivityConfigurationsLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -538,8 +560,17 @@ func newListPrivateEndpointRules() *cobra.Command { cmd := &cobra.Command{} var listPrivateEndpointRulesReq settings.ListPrivateEndpointRulesRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listPrivateEndpointRulesLimit int - cmd.Flags().StringVar(&listPrivateEndpointRulesReq.PageToken, "page-token", listPrivateEndpointRulesReq.PageToken, `Pagination token to go to next page based on previous query.`) + // Limit flag for total result capping. + cmd.Flags().IntVar(&listPrivateEndpointRulesLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listPrivateEndpointRulesReq.PageToken, "page-token", listPrivateEndpointRulesReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-private-endpoint-rules NETWORK_CONNECTIVITY_CONFIG_ID" cmd.Short = `List private endpoint rules.` @@ -565,6 +596,13 @@ func newListPrivateEndpointRules() *cobra.Command { listPrivateEndpointRulesReq.NetworkConnectivityConfigId = args[0] response := a.NetworkConnectivity.ListPrivateEndpointRules(ctx, listPrivateEndpointRulesReq) + if listPrivateEndpointRulesLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listPrivateEndpointRulesLimit) + } + if listPrivateEndpointRulesLimit > 0 { + ctx = cmdio.WithLimit(ctx, listPrivateEndpointRulesLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -601,6 +639,7 @@ func newUpdatePrivateEndpointRule() *cobra.Command { // TODO: array: domain_names cmd.Flags().BoolVar(&updatePrivateEndpointRuleReq.PrivateEndpointRule.Enabled, "enabled", updatePrivateEndpointRuleReq.PrivateEndpointRule.Enabled, `Only used by private endpoints towards an AWS S3 service.`) cmd.Flags().StringVar(&updatePrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, "error-message", updatePrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, ``) + // TODO: complex arg: gcp_endpoint // TODO: array: resource_names cmd.Use = "update-private-endpoint-rule NETWORK_CONNECTIVITY_CONFIG_ID PRIVATE_ENDPOINT_RULE_ID UPDATE_MASK" @@ -653,6 +692,7 @@ func newUpdatePrivateEndpointRule() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/network-policies/network-policies.go b/cmd/account/network-policies/network-policies.go index 4165773e646..229012a888a 100755 --- a/cmd/account/network-policies/network-policies.go +++ b/cmd/account/network-policies/network-policies.go @@ -3,6 +3,8 @@ package network_policies import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -63,8 +65,9 @@ func newCreateNetworkPolicyRpc() *cobra.Command { cmd.Flags().Var(&createNetworkPolicyRpcJson, "json", `either inline JSON string or @path/to/file.json with request body`) - cmd.Flags().StringVar(&createNetworkPolicyRpcReq.NetworkPolicy.AccountId, "account-id", createNetworkPolicyRpcReq.NetworkPolicy.AccountId, `The associated account ID for this Network Policy object.`) // TODO: complex arg: egress + // TODO: complex arg: ingress + // TODO: complex arg: ingress_dry_run cmd.Flags().StringVar(&createNetworkPolicyRpcReq.NetworkPolicy.NetworkPolicyId, "network-policy-id", createNetworkPolicyRpcReq.NetworkPolicy.NetworkPolicyId, `The unique identifier for the network policy.`) cmd.Use = "create-network-policy-rpc" @@ -103,6 +106,7 @@ func newCreateNetworkPolicyRpc() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -215,6 +219,7 @@ func newGetNetworkPolicyRpc() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -243,8 +248,17 @@ func newListNetworkPoliciesRpc() *cobra.Command { cmd := &cobra.Command{} var listNetworkPoliciesRpcReq settings.ListNetworkPoliciesRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listNetworkPoliciesRpcLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listNetworkPoliciesRpcLimit, "limit", 0, `Maximum number of results to return.`) - cmd.Flags().StringVar(&listNetworkPoliciesRpcReq.PageToken, "page-token", listNetworkPoliciesRpcReq.PageToken, `Pagination token to go to next page based on previous query.`) + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listNetworkPoliciesRpcReq.PageToken, "page-token", listNetworkPoliciesRpcReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-network-policies-rpc" cmd.Short = `List network policies.` @@ -265,6 +279,13 @@ func newListNetworkPoliciesRpc() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.NetworkPolicies.ListNetworkPoliciesRpc(ctx, listNetworkPoliciesRpcReq) + if listNetworkPoliciesRpcLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listNetworkPoliciesRpcLimit) + } + if listNetworkPoliciesRpcLimit > 0 { + ctx = cmdio.WithLimit(ctx, listNetworkPoliciesRpcLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -298,8 +319,9 @@ func newUpdateNetworkPolicyRpc() *cobra.Command { cmd.Flags().Var(&updateNetworkPolicyRpcJson, "json", `either inline JSON string or @path/to/file.json with request body`) - cmd.Flags().StringVar(&updateNetworkPolicyRpcReq.NetworkPolicy.AccountId, "account-id", updateNetworkPolicyRpcReq.NetworkPolicy.AccountId, `The associated account ID for this Network Policy object.`) // TODO: complex arg: egress + // TODO: complex arg: ingress + // TODO: complex arg: ingress_dry_run cmd.Flags().StringVar(&updateNetworkPolicyRpcReq.NetworkPolicy.NetworkPolicyId, "network-policy-id", updateNetworkPolicyRpcReq.NetworkPolicy.NetworkPolicyId, `The unique identifier for the network policy.`) cmd.Use = "update-network-policy-rpc NETWORK_POLICY_ID" @@ -342,6 +364,7 @@ func newUpdateNetworkPolicyRpc() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/networks/networks.go b/cmd/account/networks/networks.go index 54b80cb8ce0..4845a1aa3b2 100755 --- a/cmd/account/networks/networks.go +++ b/cmd/account/networks/networks.go @@ -100,6 +100,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -161,6 +162,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -218,6 +220,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -260,6 +263,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/o-auth-published-apps/o-auth-published-apps.go b/cmd/account/o-auth-published-apps/o-auth-published-apps.go index 254ef5324c1..2bade901da7 100755 --- a/cmd/account/o-auth-published-apps/o-auth-published-apps.go +++ b/cmd/account/o-auth-published-apps/o-auth-published-apps.go @@ -3,6 +3,8 @@ package o_auth_published_apps import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -50,9 +52,19 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListOAuthPublishedAppsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, `The max number of OAuth published apps to return in one page.`) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `A token that can be used to get the next page of results.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" cmd.Short = `Get all the published OAuth apps.` @@ -73,6 +85,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.OAuthPublishedApps.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/personal-compute/personal-compute.go b/cmd/account/personal-compute/personal-compute.go index e2e5b2f9c5e..1888fc237fe 100755 --- a/cmd/account/personal-compute/personal-compute.go +++ b/cmd/account/personal-compute/personal-compute.go @@ -87,6 +87,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -140,6 +141,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -204,6 +206,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/private-access/private-access.go b/cmd/account/private-access/private-access.go index d3c8f9fde32..2933414a449 100755 --- a/cmd/account/private-access/private-access.go +++ b/cmd/account/private-access/private-access.go @@ -100,6 +100,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -154,6 +155,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -207,6 +209,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -249,6 +252,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -339,6 +343,7 @@ func newReplace() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/published-app-integration/published-app-integration.go b/cmd/account/published-app-integration/published-app-integration.go index 58e3fc923dc..693767985a2 100755 --- a/cmd/account/published-app-integration/published-app-integration.go +++ b/cmd/account/published-app-integration/published-app-integration.go @@ -3,6 +3,8 @@ package published_app_integration import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -99,6 +101,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -206,6 +209,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -234,9 +238,19 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListPublishedAppIntegrationsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" cmd.Short = `Get published oauth app integrations.` @@ -258,6 +272,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.PublishedAppIntegration.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/service-principal-federation-policy/service-principal-federation-policy.go b/cmd/account/service-principal-federation-policy/service-principal-federation-policy.go index 13edacfb9ac..233c5e2e730 100755 --- a/cmd/account/service-principal-federation-policy/service-principal-federation-policy.go +++ b/cmd/account/service-principal-federation-policy/service-principal-federation-policy.go @@ -158,6 +158,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -282,6 +283,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -310,9 +312,19 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListServicePrincipalFederationPoliciesRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list SERVICE_PRINCIPAL_ID" cmd.Short = `List service principal federation policies.` @@ -341,6 +353,13 @@ func newList() *cobra.Command { } response := a.ServicePrincipalFederationPolicy.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -423,6 +442,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/service-principal-secrets/service-principal-secrets.go b/cmd/account/service-principal-secrets/service-principal-secrets.go index 022e8090fc4..cf17cb035f2 100755 --- a/cmd/account/service-principal-secrets/service-principal-secrets.go +++ b/cmd/account/service-principal-secrets/service-principal-secrets.go @@ -3,6 +3,8 @@ package service_principal_secrets import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -107,6 +109,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -193,9 +196,19 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq oauth2.ListServicePrincipalSecretsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) - cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `An opaque page token which was the next_page_token in the response of the previous request to list the secrets for this service principal.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listReq.PageToken, "page-token", listReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list SERVICE_PRINCIPAL_ID" cmd.Short = `List service principal secrets.` @@ -223,6 +236,13 @@ func newList() *cobra.Command { listReq.ServicePrincipalId = args[0] response := a.ServicePrincipalSecrets.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/service-principals-v2/service-principals-v2.go b/cmd/account/service-principals-v2/service-principals-v2.go index dfdd40c36cb..4604dfa3256 100755 --- a/cmd/account/service-principals-v2/service-principals-v2.go +++ b/cmd/account/service-principals-v2/service-principals-v2.go @@ -3,6 +3,8 @@ package service_principals_v2 import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -104,6 +106,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -217,6 +220,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -245,14 +249,25 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq iam.ListAccountServicePrincipalsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().StringVar(&listReq.Attributes, "attributes", listReq.Attributes, `Comma-separated list of attributes to return in response.`) - cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Desired number of results per page.`) cmd.Flags().StringVar(&listReq.ExcludedAttributes, "excluded-attributes", listReq.ExcludedAttributes, `Comma-separated list of attributes to exclude in response.`) cmd.Flags().StringVar(&listReq.Filter, "filter", listReq.Filter, `Query by which the results have to be filtered.`) cmd.Flags().StringVar(&listReq.SortBy, "sort-by", listReq.SortBy, `Attribute to sort the results.`) cmd.Flags().Var(&listReq.SortOrder, "sort-order", `The order to sort the results. Supported values: [ascending, descending]`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Flags().Int64Var(&listReq.StartIndex, "start-index", listReq.StartIndex, `Specifies the index of the first result.`) + cmd.Flags().Lookup("start-index").Hidden = true + cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Number of results per API page.`) + cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" cmd.Short = `List service principals.` @@ -273,6 +288,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.ServicePrincipalsV2.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/settings-v2/settings-v2.go b/cmd/account/settings-v2/settings-v2.go index 11883f11f67..f2d464d1804 100755 --- a/cmd/account/settings-v2/settings-v2.go +++ b/cmd/account/settings-v2/settings-v2.go @@ -3,6 +3,8 @@ package settings_v2 import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -80,6 +82,7 @@ func newGetPublicAccountSetting() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -122,9 +125,6 @@ func newGetPublicAccountUserPreference() *cobra.Command { USER_ID: User ID of the user whose setting is being retrieved. NAME: User Setting name.` - // This command is being previewed; hide from help output. - cmd.Hidden = true - cmd.Annotations = make(map[string]string) cmd.Args = func(cmd *cobra.Command, args []string) error { @@ -144,6 +144,7 @@ func newGetPublicAccountUserPreference() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -172,9 +173,19 @@ func newListAccountSettingsMetadata() *cobra.Command { cmd := &cobra.Command{} var listAccountSettingsMetadataReq settingsv2.ListAccountSettingsMetadataRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listAccountSettingsMetadataLimit int cmd.Flags().IntVar(&listAccountSettingsMetadataReq.PageSize, "page-size", listAccountSettingsMetadataReq.PageSize, `The maximum number of settings to return.`) - cmd.Flags().StringVar(&listAccountSettingsMetadataReq.PageToken, "page-token", listAccountSettingsMetadataReq.PageToken, `A page token, received from a previous ListAccountSettingsMetadataRequest call.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listAccountSettingsMetadataLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listAccountSettingsMetadataReq.PageToken, "page-token", listAccountSettingsMetadataReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-account-settings-metadata" cmd.Short = `List valid setting keys and their metadata.` @@ -197,6 +208,13 @@ func newListAccountSettingsMetadata() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.SettingsV2.ListAccountSettingsMetadata(ctx, listAccountSettingsMetadataReq) + if listAccountSettingsMetadataLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listAccountSettingsMetadataLimit) + } + if listAccountSettingsMetadataLimit > 0 { + ctx = cmdio.WithLimit(ctx, listAccountSettingsMetadataLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -225,9 +243,19 @@ func newListAccountUserPreferencesMetadata() *cobra.Command { cmd := &cobra.Command{} var listAccountUserPreferencesMetadataReq settingsv2.ListAccountUserPreferencesMetadataRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listAccountUserPreferencesMetadataLimit int cmd.Flags().IntVar(&listAccountUserPreferencesMetadataReq.PageSize, "page-size", listAccountUserPreferencesMetadataReq.PageSize, `The maximum number of settings to return.`) - cmd.Flags().StringVar(&listAccountUserPreferencesMetadataReq.PageToken, "page-token", listAccountUserPreferencesMetadataReq.PageToken, `A page token, received from a previous ListAccountUserPreferencesMetadataRequest call.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listAccountUserPreferencesMetadataLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listAccountUserPreferencesMetadataReq.PageToken, "page-token", listAccountUserPreferencesMetadataReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-account-user-preferences-metadata USER_ID" cmd.Short = `List user preferences and their metadata.` @@ -242,9 +270,6 @@ func newListAccountUserPreferencesMetadata() *cobra.Command { Arguments: USER_ID: User ID of the user whose settings metadata is being retrieved.` - // This command is being previewed; hide from help output. - cmd.Hidden = true - cmd.Annotations = make(map[string]string) cmd.Args = func(cmd *cobra.Command, args []string) error { @@ -260,6 +285,13 @@ func newListAccountUserPreferencesMetadata() *cobra.Command { listAccountUserPreferencesMetadataReq.UserId = args[0] response := a.SettingsV2.ListAccountUserPreferencesMetadata(ctx, listAccountUserPreferencesMetadataReq) + if listAccountUserPreferencesMetadataLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listAccountUserPreferencesMetadataLimit) + } + if listAccountUserPreferencesMetadataLimit > 0 { + ctx = cmdio.WithLimit(ctx, listAccountUserPreferencesMetadataLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -353,6 +385,7 @@ func newPatchPublicAccountSetting() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -408,9 +441,6 @@ func newPatchPublicAccountUserPreference() *cobra.Command { USER_ID: User ID of the user whose setting is being updated. NAME: ` - // This command is being previewed; hide from help output. - cmd.Hidden = true - cmd.Annotations = make(map[string]string) cmd.Args = func(cmd *cobra.Command, args []string) error { @@ -442,6 +472,7 @@ func newPatchPublicAccountUserPreference() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/storage-credentials/storage-credentials.go b/cmd/account/storage-credentials/storage-credentials.go index c3fce95ea29..2eaee847d60 100755 --- a/cmd/account/storage-credentials/storage-credentials.go +++ b/cmd/account/storage-credentials/storage-credentials.go @@ -3,6 +3,8 @@ package storage_credentials import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -103,6 +105,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -164,6 +167,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -224,6 +228,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -252,6 +257,15 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq catalog.ListAccountStorageCredentialsRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Use = "list METASTORE_ID" cmd.Short = `Get all storage credentials assigned to a metastore.` @@ -278,6 +292,13 @@ func newList() *cobra.Command { listReq.MetastoreId = args[0] response := a.StorageCredentials.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -356,6 +377,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/storage/storage.go b/cmd/account/storage/storage.go index fd9cfbcb8a2..c852a05084f 100755 --- a/cmd/account/storage/storage.go +++ b/cmd/account/storage/storage.go @@ -96,6 +96,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -150,6 +151,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -203,6 +205,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -245,6 +248,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/usage-dashboards/usage-dashboards.go b/cmd/account/usage-dashboards/usage-dashboards.go index 1efa6f236a6..7e08d033845 100755 --- a/cmd/account/usage-dashboards/usage-dashboards.go +++ b/cmd/account/usage-dashboards/usage-dashboards.go @@ -95,6 +95,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -149,6 +150,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/users-v2/users-v2.go b/cmd/account/users-v2/users-v2.go index b7ddd391b4e..3a401ce16cd 100755 --- a/cmd/account/users-v2/users-v2.go +++ b/cmd/account/users-v2/users-v2.go @@ -3,6 +3,8 @@ package users_v2 import ( + "fmt" + "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" @@ -112,6 +114,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -233,6 +236,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -261,14 +265,25 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq iam.ListAccountUsersRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int cmd.Flags().StringVar(&listReq.Attributes, "attributes", listReq.Attributes, `Comma-separated list of attributes to return in response.`) - cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Desired number of results per page.`) cmd.Flags().StringVar(&listReq.ExcludedAttributes, "excluded-attributes", listReq.ExcludedAttributes, `Comma-separated list of attributes to exclude in response.`) cmd.Flags().StringVar(&listReq.Filter, "filter", listReq.Filter, `Query by which the results have to be filtered.`) cmd.Flags().StringVar(&listReq.SortBy, "sort-by", listReq.SortBy, `Attribute to sort the results.`) cmd.Flags().Var(&listReq.SortOrder, "sort-order", `The order to sort the results. Supported values: [ascending, descending]`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Flags().Int64Var(&listReq.StartIndex, "start-index", listReq.StartIndex, `Specifies the index of the first result.`) + cmd.Flags().Lookup("start-index").Hidden = true + cmd.Flags().Int64Var(&listReq.Count, "count", listReq.Count, `Number of results per API page.`) + cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" cmd.Short = `List users.` @@ -289,6 +304,13 @@ func newList() *cobra.Command { a := cmdctx.AccountClient(ctx) response := a.UsersV2.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } diff --git a/cmd/account/vpc-endpoints/vpc-endpoints.go b/cmd/account/vpc-endpoints/vpc-endpoints.go index a6402b8b281..ee2bf1aa6a6 100755 --- a/cmd/account/vpc-endpoints/vpc-endpoints.go +++ b/cmd/account/vpc-endpoints/vpc-endpoints.go @@ -107,6 +107,7 @@ func newCreate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -161,6 +162,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -221,6 +223,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -263,6 +266,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/workspace-assignment/workspace-assignment.go b/cmd/account/workspace-assignment/workspace-assignment.go index 16c163cd6bf..43146462329 100755 --- a/cmd/account/workspace-assignment/workspace-assignment.go +++ b/cmd/account/workspace-assignment/workspace-assignment.go @@ -151,6 +151,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -179,6 +180,15 @@ func newList() *cobra.Command { cmd := &cobra.Command{} var listReq iam.ListWorkspaceAssignmentRequest + // Registered for all paginated methods. Validated at call time in the + // method-call template. Paginated list methods never have Wait or LRO + // branches, so the method-call path is always reached. + var listLimit int + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). cmd.Use = "list WORKSPACE_ID" cmd.Short = `Get permission assignments.` @@ -208,6 +218,13 @@ func newList() *cobra.Command { } response := a.WorkspaceAssignment.List(ctx, listReq) + if listLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listLimit) + } + if listLimit > 0 { + ctx = cmdio.WithLimit(ctx, listLimit) + } + return cmdio.RenderIterator(ctx, response) } @@ -291,6 +308,7 @@ func newUpdate() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/workspace-network-configuration/workspace-network-configuration.go b/cmd/account/workspace-network-configuration/workspace-network-configuration.go index fa49a5f0893..4dd194c9462 100755 --- a/cmd/account/workspace-network-configuration/workspace-network-configuration.go +++ b/cmd/account/workspace-network-configuration/workspace-network-configuration.go @@ -90,6 +90,7 @@ func newGetWorkspaceNetworkOptionRpc() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -170,6 +171,7 @@ func newUpdateWorkspaceNetworkOptionRpc() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/account/workspaces/workspaces.go b/cmd/account/workspaces/workspaces.go index 429407d431c..4ee0ab08f52 100755 --- a/cmd/account/workspaces/workspaces.go +++ b/cmd/account/workspaces/workspaces.go @@ -236,6 +236,7 @@ func newDelete() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -300,6 +301,7 @@ func newGet() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } @@ -342,6 +344,7 @@ func newList() *cobra.Command { if err != nil { return err } + return cmdio.Render(ctx, response) } diff --git a/cmd/api/api.go b/cmd/api/api.go index 057c8f22468..823a6a4b663 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -1,11 +1,15 @@ package api import ( + "errors" "fmt" "net/http" + "net/url" + "regexp" "strings" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" "github.com/databricks/databricks-sdk-go/client" @@ -13,6 +17,26 @@ import ( "github.com/spf13/cobra" ) +const ( + // orgIDHeader is the workspace routing identifier sent on workspace-scope + // requests against unified hosts. Generated SDK service methods set this + // per-call when cfg.WorkspaceID is populated; we mirror the same idiom. + orgIDHeader = "X-Databricks-Org-Id" + + // orgIDQueryParam is the SPOG (single-page-of-glass) URL convention used + // by the Databricks UI: "?o=" identifies the workspace a URL + // targets. When present on the path, we treat it as a per-call override + // for the workspace routing identifier so that pasted SPOG URLs route + // correctly without requiring --workspace-id. + orgIDQueryParam = "o" +) + +// accountSegmentRe matches a non-empty segment immediately after "accounts/", +// anchored at the start of the path or after a "/". Account-ID shape is +// deliberately opaque; the workspace-proxy list carves out SDK proxies that +// also live under /accounts/. +var accountSegmentRe = regexp.MustCompile(`(^|/)accounts/[^/]+`) + func New() *cobra.Command { cmd := &cobra.Command{ Use: "api", @@ -32,7 +56,11 @@ func New() *cobra.Command { } func makeCommand(method string) *cobra.Command { - var payload flags.JsonFlag + var ( + payload flags.JsonFlag + forceAccount bool + workspaceIDFlag string + ) command := &cobra.Command{ Use: strings.ToLower(method) + " PATH", @@ -60,8 +88,23 @@ func makeCommand(method string) *cobra.Command { return err } - var response any + orgID, err := resolveOrgID( + forceAccount, + workspaceIDFlag, + cmd.Flags().Changed("workspace-id"), + normalizeWorkspaceID(cfg.WorkspaceID), + path, + ) + if err != nil { + return err + } + headers := map[string]string{"Content-Type": "application/json"} + if orgID != "" { + headers[orgIDHeader] = orgID + } + + var response any err = api.Do(cmd.Context(), method, path, headers, nil, request, &response) if err != nil { return err @@ -71,5 +114,87 @@ func makeCommand(method string) *cobra.Command { } command.Flags().Var(&payload, "json", `either inline JSON string or @path/to/file.json with request body`) + command.Flags().BoolVar(&forceAccount, "account", false, + "Treat this call as account-scoped (skip the workspace routing identifier). Mutually exclusive with --workspace-id.") + command.Flags().StringVar(&workspaceIDFlag, "workspace-id", "", + "Override the workspace routing identifier on this call. Mutually exclusive with --account.") return command } + +// normalizeWorkspaceID strips the CLI-only WorkspaceIDNone sentinel so the +// SDK's idiomatic "if cfg.WorkspaceID != \"\"" check produces the right call +// shape. The CLI persists "none" in .databrickscfg to mark profiles where the +// user explicitly skipped workspace selection; the SDK does not know about +// this sentinel and would otherwise send the literal "none" as a routing +// identifier. +func normalizeWorkspaceID(workspaceID string) string { + if workspaceID == auth.WorkspaceIDNone { + return "" + } + return workspaceID +} + +// hasAccountSegment reports whether path is an account-scope API. The match +// runs on URL.Path, so query strings and fragments containing "/accounts/" +// can't trigger a false positive. Returns false for paths that match a known +// workspace-routed proxy from the proxy path tables. +func hasAccountSegment(rawPath string) (bool, error) { + u, err := url.Parse(rawPath) + if err != nil { + return false, fmt.Errorf("parse path: %w", err) + } + p := u.Path + if isWorkspaceProxyPath(p) { + return false, nil + } + return accountSegmentRe.MatchString(p), nil +} + +// extractOrgIDFromQuery returns the value of the "o" query parameter on path +// (the SPOG URL convention), or "" if absent or empty. +func extractOrgIDFromQuery(rawPath string) (string, error) { + u, err := url.Parse(rawPath) + if err != nil { + return "", fmt.Errorf("parse path: %w", err) + } + return u.Query().Get(orgIDQueryParam), nil +} + +// resolveOrgID picks the value (if any) for the workspace routing identifier +// based on flags, the resolved profile, and the path shape. Returns "" when +// no header should be sent. +func resolveOrgID( + forceAccount bool, + workspaceIDFlag string, + workspaceIDFlagSet bool, + cfgWorkspaceID string, + path string, +) (string, error) { + if forceAccount && workspaceIDFlagSet { + return "", errors.New("--account and --workspace-id are mutually exclusive") + } + if forceAccount { + return "", nil + } + if workspaceIDFlagSet { + if workspaceIDFlag == "" { + return "", errors.New("--workspace-id requires a value; use --account to scope this call to the account API") + } + return workspaceIDFlag, nil + } + orgIDFromQuery, err := extractOrgIDFromQuery(path) + if err != nil { + return "", err + } + if orgIDFromQuery != "" { + return orgIDFromQuery, nil + } + isAccount, err := hasAccountSegment(path) + if err != nil { + return "", err + } + if isAccount { + return "", nil + } + return cfgWorkspaceID, nil +} diff --git a/cmd/api/api_test.go b/cmd/api/api_test.go new file mode 100644 index 00000000000..69cd28fe5fe --- /dev/null +++ b/cmd/api/api_test.go @@ -0,0 +1,226 @@ +package api + +import ( + "testing" + + "github.com/databricks/cli/libs/auth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHasAccountSegment(t *testing.T) { + cases := []struct { + name string + path string + want bool + }{ + {"account UUID", "/api/2.0/accounts/123e4567-e89b-12d3-a456-426614174000/ip-access-lists", true}, + {"AIP resource-name shape", "/api/networking/v1/accounts/123e4567-e89b-12d3-a456-426614174000/endpoints/abc", true}, + {"iamv2 account API", "/api/2.0/identity/accounts/123e4567-e89b-12d3-a456-426614174000/workspaces/123/workspaceAccessDetails/abc", true}, + {"non-UUID account ID", "/api/2.0/accounts/abc/foo", true}, + {"hyphenated short ID", "/api/2.0/accounts/abc-123/network-policies", true}, + {"substituted any-shape ID", "/api/2.0/accounts/some-account/oauth2/published-app-integrations", true}, + + {"deny-listed exact rule-sets", "/api/2.0/preview/accounts/access-control/rule-sets", false}, + {"deny-listed exact assignable-roles", "/api/2.0/preview/accounts/access-control/assignable-roles", false}, + {"exact-list miss falls to regex (rule-sets/foo)", "/api/2.0/preview/accounts/access-control/rule-sets/foo", true}, + {"exact-list miss falls to regex (assignable-roles-extra)", "/api/2.0/preview/accounts/access-control/assignable-roles-extra", true}, + {"deny-listed prefix servicePrincipals", "/api/2.0/accounts/servicePrincipals/abc-123/credentials/secrets", false}, + + {"no accounts segment", "/api/2.0/clusters/list", false}, + {"segment ends in accounts (boundary)", "/api/2.0/some-accounts/abc/foo", false}, + + {"query string preserved on match", "/api/2.0/accounts/abc-123?include=foo", true}, + {"query string with /accounts/ does not match", "/api/2.0/clusters/list?next=/accounts/foo", false}, + {"fragment with accounts/ does not match", "/api/2.0/clusters/list#accounts/foo", false}, + + {"absolute URL, account path", "https://ignored.example.com/api/2.0/accounts/abc/foo?include=x", true}, + {"absolute URL, query-string accounts/ does not match", "https://ignored.example.com/api/2.0/clusters/list?next=/accounts/foo", false}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := hasAccountSegment(c.path) + require.NoError(t, err) + assert.Equal(t, c.want, got) + }) + } +} + +func TestExtractOrgIDFromQuery(t *testing.T) { + cases := []struct { + name string + path string + want string + }{ + {"no query string", "/api/2.0/clusters/list", ""}, + {"o param present", "/api/2.2/jobs/list?o=7474644166319138", "7474644166319138"}, + {"o param empty", "/api/2.0/clusters/list?o=", ""}, + {"o among other params first", "/api/2.0/clusters/list?o=123&foo=bar", "123"}, + {"o among other params last", "/api/2.0/clusters/list?foo=bar&o=123", "123"}, + {"unrelated o-prefixed param ignored", "/api/2.0/clusters/list?other=1", ""}, + {"absolute URL", "https://example.com/api/2.0/clusters/list?o=42", "42"}, + {"first value wins on duplicate", "/api/2.0/clusters/list?o=1&o=2", "1"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := extractOrgIDFromQuery(c.path) + require.NoError(t, err) + assert.Equal(t, c.want, got) + }) + } +} + +func TestResolveOrgID(t *testing.T) { + const ( + workspacePath = "/api/2.0/clusters/list" + accountPath = "/api/2.0/accounts/abc-123/network-policies" + proxyPath = "/api/2.0/preview/accounts/access-control/rule-sets" + spogPath = "/api/2.2/jobs/list?o=7474644166319138" + spogAccountPath = "/api/2.0/accounts/abc-123/network-policies?o=7474644166319138" + spogWorkspaceID = "7474644166319138" + resolvedWSID = "900800700600" + flagWSID = "999" + ) + + cases := []struct { + name string + forceAccount bool + workspaceIDFlag string + flagSet bool + cfgWorkspaceID string + path string + want string + wantErrSubstring string + }{ + { + name: "empty WorkspaceID + workspace path -> no identifier", + cfgWorkspaceID: "", + path: workspacePath, + want: "", + }, + { + name: "WorkspaceID set + workspace path -> sends identifier", + cfgWorkspaceID: resolvedWSID, + path: workspacePath, + want: resolvedWSID, + }, + { + name: "WorkspaceID set + account path -> no identifier (auto-detect)", + cfgWorkspaceID: resolvedWSID, + path: accountPath, + want: "", + }, + { + name: "WorkspaceID set + workspace-routed proxy under accounts/", + cfgWorkspaceID: resolvedWSID, + path: proxyPath, + want: resolvedWSID, + }, + { + name: "--account on workspace path", + forceAccount: true, + cfgWorkspaceID: resolvedWSID, + path: workspacePath, + want: "", + }, + { + name: "--workspace-id overrides resolved value", + workspaceIDFlag: flagWSID, + flagSet: true, + cfgWorkspaceID: resolvedWSID, + path: workspacePath, + want: flagWSID, + }, + { + name: "--workspace-id on account path still overrides", + workspaceIDFlag: flagWSID, + flagSet: true, + cfgWorkspaceID: resolvedWSID, + path: accountPath, + want: flagWSID, + }, + { + name: "--workspace-id empty value -> error", + workspaceIDFlag: "", + flagSet: true, + cfgWorkspaceID: resolvedWSID, + path: workspacePath, + wantErrSubstring: "--workspace-id requires a value", + }, + { + name: "--account and --workspace-id both set -> error", + forceAccount: true, + workspaceIDFlag: flagWSID, + flagSet: true, + cfgWorkspaceID: resolvedWSID, + path: workspacePath, + wantErrSubstring: "mutually exclusive", + }, + { + name: "?o= sets identifier when no flag and no profile WorkspaceID", + cfgWorkspaceID: "", + path: spogPath, + want: spogWorkspaceID, + }, + { + name: "?o= overrides profile WorkspaceID", + cfgWorkspaceID: resolvedWSID, + path: spogPath, + want: spogWorkspaceID, + }, + { + name: "--workspace-id wins over ?o=", + workspaceIDFlag: flagWSID, + flagSet: true, + cfgWorkspaceID: resolvedWSID, + path: spogPath, + want: flagWSID, + }, + { + name: "--account wins over ?o=", + forceAccount: true, + cfgWorkspaceID: resolvedWSID, + path: spogPath, + want: "", + }, + { + name: "?o= on /accounts/ path still routes to that workspace", + cfgWorkspaceID: "", + path: spogAccountPath, + want: spogWorkspaceID, + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := resolveOrgID(c.forceAccount, c.workspaceIDFlag, c.flagSet, c.cfgWorkspaceID, c.path) + if c.wantErrSubstring != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), c.wantErrSubstring) + return + } + require.NoError(t, err) + assert.Equal(t, c.want, got) + }) + } +} + +// TestNormalizeWorkspaceID covers the helper that strips the CLI-only +// WorkspaceIDNone sentinel. RunE calls this directly before resolveOrgID, so +// a regression here would surface as the literal "none" being sent on the +// wire. +func TestNormalizeWorkspaceID(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {"sentinel stripped to empty", auth.WorkspaceIDNone, ""}, + {"empty passes through", "", ""}, + {"normal value passes through", "900800700600", "900800700600"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + assert.Equal(t, c.want, normalizeWorkspaceID(c.in)) + }) + } +} diff --git a/cmd/api/paths.go b/cmd/api/paths.go new file mode 100644 index 00000000000..67b301f84d7 --- /dev/null +++ b/cmd/api/paths.go @@ -0,0 +1,29 @@ +package api + +import "strings" + +// workspaceProxyPrefixes lists SDK endpoints that live under accounts/ but +// route to the workspace gateway. Keep this list in sync with workspace-routed +// proxy APIs in the pinned SDK. +var workspaceProxyPrefixes = []string{ + "/api/2.0/accounts/servicePrincipals/", +} + +// workspaceProxyExact lists literal SDK endpoints that live under accounts/ but +// route to the workspace gateway. +var workspaceProxyExact = map[string]struct{}{ + "/api/2.0/preview/accounts/access-control/assignable-roles": {}, + "/api/2.0/preview/accounts/access-control/rule-sets": {}, +} + +func isWorkspaceProxyPath(path string) bool { + if _, ok := workspaceProxyExact[path]; ok { + return true + } + for _, prefix := range workspaceProxyPrefixes { + if strings.HasPrefix(path, prefix) { + return true + } + } + return false +} diff --git a/cmd/api/paths_test.go b/cmd/api/paths_test.go new file mode 100644 index 00000000000..c14da6b3ccc --- /dev/null +++ b/cmd/api/paths_test.go @@ -0,0 +1,52 @@ +package api + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIsWorkspaceProxyPath(t *testing.T) { + cases := []struct { + name string + path string + want bool + }{ + { + name: "assignable roles proxy", + path: "/api/2.0/preview/accounts/access-control/assignable-roles", + want: true, + }, + { + name: "rule sets proxy", + path: "/api/2.0/preview/accounts/access-control/rule-sets", + want: true, + }, + { + name: "service principal secrets proxy", + path: "/api/2.0/accounts/servicePrincipals/spn-123/credentials/secrets", + want: true, + }, + { + name: "account service principal secrets path has account id segment", + path: "/api/2.0/accounts/abc-123/servicePrincipals/spn-123/credentials/secrets", + want: false, + }, + { + name: "rule sets child is not part of exact proxy entry", + path: "/api/2.0/preview/accounts/access-control/rule-sets/foo", + want: false, + }, + { + name: "workspace path", + path: "/api/2.0/clusters/list", + want: false, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + assert.Equal(t, c.want, isWorkspaceProxyPath(c.path)) + }) + } +} diff --git a/cmd/apps/deploy_bundle.go b/cmd/apps/deploy_bundle.go index 86b8c8bb1e0..7a893e23263 100644 --- a/cmd/apps/deploy_bundle.go +++ b/cmd/apps/deploy_bundle.go @@ -33,19 +33,56 @@ func hasBundleConfig() bool { return err == nil } +// bundleDeployOptions holds flags for the bundle-aware deploy path. +type bundleDeployOptions struct { + force bool + forceLock bool + failOnActiveRuns bool + autoApprove bool + verbose bool + clusterId string + readPlanPath string + skipValidation bool + skipTests bool +} + +// applyDeployFlags writes the deploy flag values onto the bundle config. +// Flags that override bundle YAML are only applied when explicitly set by the user. +func applyDeployFlags(cmd *cobra.Command, b *bundle.Bundle, opts bundleDeployOptions) { + b.Config.Bundle.Force = opts.force + b.Config.Bundle.Deployment.Lock.Force = opts.forceLock + b.AutoApprove = opts.autoApprove + + if cmd.Flag("compute-id").Changed { + b.Config.Bundle.ClusterId = opts.clusterId + } + if cmd.Flag("cluster-id").Changed { + b.Config.Bundle.ClusterId = opts.clusterId + } + if cmd.Flag("fail-on-active-runs").Changed { + b.Config.Bundle.Deployment.FailOnActiveRuns = opts.failOnActiveRuns + } +} + // BundleDeployOverrideWithWrapper creates a deploy override function that uses // the provided error wrapper for API fallback errors. func BundleDeployOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command, *apps.CreateAppDeploymentRequest) { return func(deployCmd *cobra.Command, deployReq *apps.CreateAppDeploymentRequest) { - var ( - force bool - skipValidation bool - skipTests bool - ) - - deployCmd.Flags().BoolVar(&force, "force", false, "Force-override Git branch validation") - deployCmd.Flags().BoolVar(&skipValidation, "skip-validation", false, "Skip project validation (build, typecheck, lint)") - deployCmd.Flags().BoolVar(&skipTests, "skip-tests", true, "Skip running tests during validation") + var opts bundleDeployOptions + + deployCmd.Flags().BoolVar(&opts.force, "force", false, "Force-override Git branch validation.") + deployCmd.Flags().BoolVar(&opts.forceLock, "force-lock", false, "Force acquisition of deployment lock.") + deployCmd.Flags().BoolVar(&opts.failOnActiveRuns, "fail-on-active-runs", false, "Fail if there are running jobs or pipelines in the deployment.") + deployCmd.Flags().StringVar(&opts.clusterId, "compute-id", "", "Override cluster in the deployment with the given compute ID.") + deployCmd.Flags().StringVarP(&opts.clusterId, "cluster-id", "c", "", "Override cluster in the deployment with the given cluster ID.") + deployCmd.Flags().BoolVar(&opts.autoApprove, "auto-approve", false, "Skip interactive approvals that might be required for deployment.") + deployCmd.Flags().MarkDeprecated("compute-id", "use --cluster-id instead") + deployCmd.Flags().BoolVar(&opts.verbose, "verbose", false, "Enable verbose output.") + deployCmd.Flags().StringVar(&opts.readPlanPath, "plan", "", "Path to a JSON plan file to apply instead of planning (direct engine only).") + // Verbose flag currently only affects file sync output, it's used by the vscode extension + deployCmd.Flags().MarkHidden("verbose") + deployCmd.Flags().BoolVar(&opts.skipValidation, "skip-validation", false, "Skip project validation (build, typecheck, lint)") + deployCmd.Flags().BoolVar(&opts.skipTests, "skip-tests", true, "Skip running tests during validation") makeArgsOptionalWithBundle(deployCmd, "deploy [APP_NAME]") @@ -54,7 +91,7 @@ func BundleDeployOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command if len(args) == 0 { b := root.TryConfigureBundle(cmd) if b != nil { - return runBundleDeploy(cmd, force, skipValidation, skipTests) + return runBundleDeploy(cmd, opts) } } @@ -91,12 +128,21 @@ Examples: databricks apps deploy --skip-validation # Force deploy (override git branch validation) - databricks apps deploy --force` + databricks apps deploy --force + + # Skip interactive approval prompts + databricks apps deploy --auto-approve + + # Force-acquire the deployment lock if a previous run left it stale + databricks apps deploy --force-lock + + # Override the cluster used for job resources in the bundle + databricks apps deploy --cluster-id 0123-456789-abcdef01` } } // runBundleDeploy executes the enhanced deployment flow for project directories. -func runBundleDeploy(cmd *cobra.Command, force, skipValidation, skipTests bool) error { +func runBundleDeploy(cmd *cobra.Command, opts bundleDeployOptions) error { ctx := cmd.Context() workDir, err := os.Getwd() @@ -105,13 +151,13 @@ func runBundleDeploy(cmd *cobra.Command, force, skipValidation, skipTests bool) } // Step 1: Validate project (unless skipped) - if !skipValidation { + if !opts.skipValidation { validator := validation.GetProjectValidator(workDir) if validator != nil { - opts := validation.ValidateOptions{ - SkipTests: skipTests, + vopts := validation.ValidateOptions{ + SkipTests: opts.skipTests, } - result, err := validator.Validate(ctx, workDir, opts) + result, err := validator.Validate(ctx, workDir, vopts) if err != nil { return fmt.Errorf("validation error: %w", err) } @@ -132,7 +178,7 @@ func runBundleDeploy(cmd *cobra.Command, force, skipValidation, skipTests bool) cmdio.LogString(ctx, "Deploying project...") b, err := utils.ProcessBundle(cmd, utils.ProcessOptions{ InitFunc: func(b *bundle.Bundle) { - b.Config.Bundle.Force = force + applyDeployFlags(cmd, b, opts) }, // Context is already initialized by the workspace command's PreRunE SkipInitContext: true, @@ -140,6 +186,8 @@ func runBundleDeploy(cmd *cobra.Command, force, skipValidation, skipTests bool) FastValidate: true, Build: true, Deploy: true, + Verbose: opts.verbose, + ReadPlanPath: opts.readPlanPath, }) if err != nil { return fmt.Errorf("deploy failed: %w", err) diff --git a/cmd/apps/deploy_bundle_test.go b/cmd/apps/deploy_bundle_test.go new file mode 100644 index 00000000000..5c9096afe0e --- /dev/null +++ b/cmd/apps/deploy_bundle_test.go @@ -0,0 +1,212 @@ +package apps + +import ( + "errors" + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/databricks-sdk-go/service/apps" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBundleDeployOverrideWithWrapper(t *testing.T) { + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + return err + } + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + assert.NotNil(t, overrideFunc) + + cmd := &cobra.Command{} + deployReq := &apps.CreateAppDeploymentRequest{} + + overrideFunc(cmd, deployReq) + + assert.Equal(t, "deploy [APP_NAME]", cmd.Use) +} + +func TestBundleDeployOverrideFlags(t *testing.T) { + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + return err + } + + cmd := &cobra.Command{} + deployReq := &apps.CreateAppDeploymentRequest{} + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + overrideFunc(cmd, deployReq) + + tests := []struct { + name string + defaultVal string + }{ + {"force", "false"}, + {"force-lock", "false"}, + {"fail-on-active-runs", "false"}, + {"compute-id", ""}, + {"cluster-id", ""}, + {"auto-approve", "false"}, + {"verbose", "false"}, + {"plan", ""}, + {"skip-validation", "false"}, + {"skip-tests", "true"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + flag := cmd.Flags().Lookup(tc.name) + require.NotNil(t, flag, "flag %q should be registered", tc.name) + assert.Equal(t, tc.defaultVal, flag.DefValue) + }) + } +} + +func TestBundleDeployOverrideDeprecatedAndHiddenFlags(t *testing.T) { + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + return err + } + + cmd := &cobra.Command{} + deployReq := &apps.CreateAppDeploymentRequest{} + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + overrideFunc(cmd, deployReq) + + computeID := cmd.Flags().Lookup("compute-id") + require.NotNil(t, computeID) + assert.NotEmpty(t, computeID.Deprecated, "compute-id should be deprecated") + + verbose := cmd.Flags().Lookup("verbose") + require.NotNil(t, verbose) + assert.True(t, verbose.Hidden, "verbose should be hidden") +} + +func TestBundleDeployOverrideClusterIDShorthand(t *testing.T) { + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + return err + } + + cmd := &cobra.Command{} + deployReq := &apps.CreateAppDeploymentRequest{} + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + overrideFunc(cmd, deployReq) + + flag := cmd.Flags().Lookup("cluster-id") + require.NotNil(t, flag) + assert.Equal(t, "c", flag.Shorthand) +} + +func TestBundleDeployOverrideHelpText(t *testing.T) { + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + return err + } + + cmd := &cobra.Command{} + deployReq := &apps.CreateAppDeploymentRequest{} + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + overrideFunc(cmd, deployReq) + + assert.NotEmpty(t, cmd.Long) + assert.Contains(t, cmd.Long, "app deployment") + assert.Contains(t, cmd.Long, "project directory") + assert.Contains(t, cmd.Long, "databricks.yml") + assert.Contains(t, cmd.Long, "--auto-approve") + assert.Contains(t, cmd.Long, "--force-lock") +} + +func TestApplyDeployFlags(t *testing.T) { + noopWrapper := func(cmd *cobra.Command, appName string, err error) error { return err } + + tests := []struct { + name string + args []string + opts bundleDeployOptions + assertion func(*testing.T, *bundle.Bundle) + }{ + { + name: "force, forceLock, autoApprove always apply", + opts: bundleDeployOptions{force: true, forceLock: true, autoApprove: true}, + assertion: func(t *testing.T, b *bundle.Bundle) { + assert.True(t, b.Config.Bundle.Force) + assert.True(t, b.Config.Bundle.Deployment.Lock.Force) + assert.True(t, b.AutoApprove) + }, + }, + { + name: "clusterId ignored when cluster-id flag unchanged", + opts: bundleDeployOptions{clusterId: "should-not-leak"}, + assertion: func(t *testing.T, b *bundle.Bundle) { + assert.Empty(t, b.Config.Bundle.ClusterId) + }, + }, + { + name: "clusterId applies when --cluster-id is set", + args: []string{"--cluster-id=my-cluster"}, + opts: bundleDeployOptions{clusterId: "my-cluster"}, + assertion: func(t *testing.T, b *bundle.Bundle) { + assert.Equal(t, "my-cluster", b.Config.Bundle.ClusterId) + }, + }, + { + name: "clusterId applies when --compute-id is set", + args: []string{"--compute-id=my-compute"}, + opts: bundleDeployOptions{clusterId: "my-compute"}, + assertion: func(t *testing.T, b *bundle.Bundle) { + assert.Equal(t, "my-compute", b.Config.Bundle.ClusterId) + }, + }, + { + name: "failOnActiveRuns ignored when flag unchanged", + opts: bundleDeployOptions{failOnActiveRuns: true}, + assertion: func(t *testing.T, b *bundle.Bundle) { assert.False(t, b.Config.Bundle.Deployment.FailOnActiveRuns) }, + }, + { + name: "failOnActiveRuns applies when --fail-on-active-runs is set", + args: []string{"--fail-on-active-runs"}, + opts: bundleDeployOptions{failOnActiveRuns: true}, + assertion: func(t *testing.T, b *bundle.Bundle) { + assert.True(t, b.Config.Bundle.Deployment.FailOnActiveRuns) + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + cmd := &cobra.Command{Use: "deploy"} + BundleDeployOverrideWithWrapper(noopWrapper)(cmd, &apps.CreateAppDeploymentRequest{}) + require.NoError(t, cmd.ParseFlags(tc.args)) + + b := &bundle.Bundle{} + applyDeployFlags(cmd, b, tc.opts) + + tc.assertion(t, b) + }) + } +} + +func TestBundleDeployOverrideErrorWrapping(t *testing.T) { + wrapperCalled := false + mockWrapper := func(cmd *cobra.Command, appName string, err error) error { + wrapperCalled = true + assert.Equal(t, "test-app", appName) + return err + } + + cmd := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + return errors.New("api error") + }, + } + deployReq := &apps.CreateAppDeploymentRequest{AppName: "test-app"} + + overrideFunc := BundleDeployOverrideWithWrapper(mockWrapper) + overrideFunc(cmd, deployReq) + + err := cmd.RunE(cmd, []string{"test-app"}) + assert.Error(t, err) + assert.True(t, wrapperCalled) +} diff --git a/cmd/apps/dev.go b/cmd/apps/dev.go index 3ec7dceba5e..59d000ef79c 100644 --- a/cmd/apps/dev.go +++ b/cmd/apps/dev.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io/fs" "net" "net/url" "os" @@ -112,9 +113,10 @@ func startViteDevServer(ctx context.Context, appURL string, port int) (*exec.Cmd func newDevRemoteCmd() *cobra.Command { var ( - appName string - clientPath string - port int + appName string + clientPath string + port int + autoApprove bool ) cmd := &cobra.Command{ @@ -144,7 +146,7 @@ Examples: ctx := cmd.Context() // Validate client path early (before any network calls) - if _, err := os.Stat(clientPath); os.IsNotExist(err) { + if _, err := os.Stat(clientPath); errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("client directory not found: %s", clientPath) } @@ -173,7 +175,7 @@ Examples: appName = selected } - bridge := vite.NewBridge(ctx, w, appName, port) + bridge := vite.NewBridge(ctx, w, appName, port, autoApprove) // Validate app exists and get domain before starting Vite var appDomain *url.URL @@ -233,6 +235,7 @@ Examples: cmd.Flags().StringVar(&appName, "name", "", "Name of the app to connect to (prompts if not provided)") cmd.Flags().StringVar(&clientPath, "client-path", "./client", "Path to the Vite client directory") cmd.Flags().IntVar(&port, "port", vitePort, "Port to run the Vite server on") + cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Automatically approve every viewer connection. Anyone with the shareable dev URL will be trusted for the life of the session; use only in trusted environments.") return cmd } diff --git a/cmd/apps/import.go b/cmd/apps/import.go index aae658676d6..67cf4ae9d9e 100644 --- a/cmd/apps/import.go +++ b/cmd/apps/import.go @@ -2,12 +2,14 @@ package apps import ( "bufio" + "cmp" "context" "errors" "fmt" + "io/fs" "os" "path/filepath" - "sort" + "slices" "strings" "go.yaml.in/yaml/v3" @@ -117,13 +119,16 @@ Examples: } // Sort apps: owned by current user first - sort.Slice(appList, func(i, j int) bool { - iOwned := strings.ToLower(appList[i].Creator) == currentUserEmail - jOwned := strings.ToLower(appList[j].Creator) == currentUserEmail - if iOwned != jOwned { - return iOwned + slices.SortFunc(appList, func(a, b apps.App) int { + aOwned := strings.ToLower(a.Creator) == currentUserEmail + bOwned := strings.ToLower(b.Creator) == currentUserEmail + if aOwned != bOwned { + if aOwned { + return -1 + } + return 1 } - return appList[i].Name < appList[j].Name + return cmp.Compare(a.Name, b.Name) }) // Build selection map @@ -167,7 +172,7 @@ Examples: // Check if output directory already exists if _, err := os.Stat(outputDir); err == nil { return fmt.Errorf("directory '%s' already exists. Please remove it or choose a different output directory", outputDir) - } else if !os.IsNotExist(err) { + } else if !errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("failed to check if directory exists: %w", err) } @@ -191,7 +196,7 @@ Examples: cmdio.LogString(ctx, "Cleaning up previous app folder") } - err = w.Workspace.Delete(ctx, workspace.Delete{ + err = w.Workspace.Delete(ctx, workspace.Delete{ //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. Path: oldSourceCodePath, Recursive: true, }) @@ -316,7 +321,7 @@ func runImport(ctx context.Context, w *databricks.WorkspaceClient, appName, outp } // Verify the app exists - exists, err := resource.Exists(ctx, b.WorkspaceClient(), app.Name) + exists, err := resource.Exists(ctx, b.WorkspaceClient(ctx), app.Name) if err != nil { return fmt.Errorf("failed to verify app exists: %w", err) } diff --git a/cmd/apps/init.go b/cmd/apps/init.go index 85772b6a918..2f1da99bb71 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -3,6 +3,7 @@ package apps import ( "bytes" "context" + "encoding/json" "errors" "fmt" "io/fs" @@ -37,7 +38,7 @@ const ( appkitTemplateDir = "template" appkitDefaultBranch = "main" appkitTemplateTagPfx = "template-v" - appkitDefaultVersion = "template-v0.20.3" + appkitDefaultVersion = "template-v0.24.0" defaultProfile = "DEFAULT" ) @@ -76,6 +77,7 @@ func newInitCmd() *cobra.Command { deploy bool run string setValues []string + autoApprove bool ) cmd := &cobra.Command{ @@ -160,6 +162,7 @@ Environment variables: runChanged: cmd.Flags().Changed("run"), pluginsChanged: cmd.Flags().Changed("features") || cmd.Flags().Changed("plugins"), setValues: setValues, + autoApprove: autoApprove, }) }, } @@ -178,6 +181,7 @@ Environment variables: _ = cmd.Flags().MarkHidden("plugins") cmd.Flags().BoolVar(&deploy, "deploy", false, "Deploy the app after creation") cmd.Flags().StringVar(&run, "run", "", "Run the app after creation (none, dev, dev-remote)") + cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip confirmation prompts for optional resources. Optional resources are only configured when their values are provided via --set.") return cmd } @@ -198,6 +202,7 @@ type createOptions struct { runChanged bool // true if --run flag was explicitly set pluginsChanged bool // true if --plugins flag was explicitly set setValues []string // --set plugin.resourceKey.field=value pairs + autoApprove bool } // parseSetValues parses --set key=value pairs into the resourceValues map. @@ -277,6 +282,28 @@ func pluginHasResourceField(p *manifest.Plugin, resourceKey, fieldName string) b return false } +// validateRequiredResources checks that all required resources have at least one +// value in resourceValues. Returns an error with a --set hint if any are missing. +func validateRequiredResources(resources []manifest.Resource, resourceValues map[string]string) error { + for _, r := range resources { + found := false + for k := range resourceValues { + if strings.HasPrefix(k, r.Key()+".") { + found = true + break + } + } + if !found { + fieldHint := "id" + if names := r.FieldNames(); len(names) > 0 { + fieldHint = names[0] + } + return fmt.Errorf("missing required resource %q for selected plugins (use --set %s.%s.%s=value)", r.Alias, r.PluginName, r.Key(), fieldHint) + } + } + return nil +} + // tmplBundle holds the generated bundle configuration strings. type tmplBundle struct { Variables string @@ -290,9 +317,16 @@ type dotEnvVars struct { Example string } -// pluginVar represents a selected plugin. Currently empty, but extensible -// with properties as the plugin model evolves. -type pluginVar struct{} +// pluginVar represents a selected plugin in template substitution. +// Fields here are part of the AppKit template contract — the template +// reads them via {{$p.Field}} on map values in templateVars.Plugins. +type pluginVar struct { + // Stability mirrors manifest.Plugin.Stability ("" for GA, "beta" + // for beta, future tiers preserved). The AppKit template branches + // imports on this — see databricks/appkit#264 commit d826a532, which + // routes beta plugins through the `@databricks/appkit/beta` subpath. + Stability string +} // templateVars holds the variables for template substitution. type templateVars struct { @@ -332,7 +366,7 @@ func parseDeployAndRunFlags(deploy bool, run string) (bool, prompt.RunMode, erro // promptForPluginsAndDeps prompts for plugins and their resource dependencies using the manifest. // skipDeployRunPrompt indicates whether to skip prompting for deploy/run (because flags were provided). -func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelectedPlugins []string, skipDeployRunPrompt bool) (*prompt.CreateProjectConfig, error) { +func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelectedPlugins []string, skipDeployRunPrompt, autoApprove bool) (*prompt.CreateProjectConfig, error) { config := &prompt.CreateProjectConfig{ Dependencies: make(map[string]string), Features: preSelectedPlugins, // Reuse Features field for plugin names @@ -352,7 +386,7 @@ func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelec if len(config.Features) == 0 && len(selectablePlugins) > 0 { options := make([]huh.Option[string], 0, len(selectablePlugins)) for _, p := range selectablePlugins { - label := p.DisplayName + " - " + p.Description + label := p.DisplayName + prompt.RenderStabilityTier(p.StabilityLabel()) + " - " + p.Description options = append(options, huh.NewOption(label, p.Name)) } @@ -389,19 +423,19 @@ func promptForPluginsAndDeps(ctx context.Context, m *manifest.Manifest, preSelec if err != nil { return nil, err } - for k, v := range values { - config.Dependencies[k] = v - } + maps.Copy(config.Dependencies, values) } - // Step 3: Prompt for optional plugin resource dependencies - for _, r := range optionalResources { - values, err := promptForResource(ctx, r, theme, false) - if err != nil { - return nil, err - } - for k, v := range values { - config.Dependencies[k] = v + // Step 3: Prompt for optional plugin resource dependencies. + // With --auto-approve, optional resources are skipped here; they're only + // configured when their values are supplied via --set (merged later). + if !autoApprove { + for _, r := range optionalResources { + values, err := promptForResource(ctx, r, theme, false) + if err != nil { + return nil, err + } + maps.Copy(config.Dependencies, values) } } @@ -656,6 +690,14 @@ func startBackgroundNpmInstall(ctx context.Context, srcProjectDir, destDir, proj return nil } + // Copy any file: protocol dependencies (e.g., local .tgz tarballs) so npm ci can resolve them. + pkgData, err := os.ReadFile(filepath.Join(destDir, "package.json")) + if err != nil { + log.Warnf(ctx, "Failed to read package.json for file dep copy: %v", err) + } else { + copyFileDeps(ctx, pkgData, srcProjectDir, destDir) + } + // Copy package-lock.json raw (never has template vars). lockData, err := os.ReadFile(lockFile) if err != nil { @@ -680,6 +722,41 @@ func startBackgroundNpmInstall(ctx context.Context, srcProjectDir, destDir, proj return ch } +// copyFileDeps copies local file: protocol dependencies (e.g., .tgz tarballs) +// from srcDir to destDir so that npm ci can resolve them. +func copyFileDeps(ctx context.Context, pkgJSON []byte, srcDir, destDir string) { + var pkg struct { + Dependencies map[string]string `json:"dependencies"` + DevDependencies map[string]string `json:"devDependencies"` + } + if err := json.Unmarshal(pkgJSON, &pkg); err != nil { + log.Debugf(ctx, "Failed to parse package.json for file dep copy: %v", err) + return + } + for _, deps := range []map[string]string{pkg.Dependencies, pkg.DevDependencies} { + for _, v := range deps { + if !strings.HasPrefix(v, "file:") { + continue + } + relPath := filepath.Clean(strings.TrimPrefix(v, "file:")) + src := filepath.Join(srcDir, relPath) + data, err := os.ReadFile(src) + if err != nil { + log.Debugf(ctx, "Skipping file dep %s: %v", relPath, err) + continue + } + dst := filepath.Join(destDir, relPath) + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + log.Debugf(ctx, "Failed to create dir for file dep %s: %v", relPath, err) + continue + } + if err := os.WriteFile(dst, data, 0o644); err != nil { + log.Debugf(ctx, "Failed to copy file dep %s: %v", relPath, err) + } + } + } +} + // awaitBackgroundNpmInstall waits for the background npm install to complete. // Shows an instant checkmark if already done, or a spinner for the remainder. func awaitBackgroundNpmInstall(ctx context.Context, ch <-chan error) error { @@ -791,10 +868,10 @@ func runCreate(ctx context.Context, opts createOptions) error { // Check for generic subdirectory first (default for multi-template repos) templateDir := filepath.Join(resolvedPath, "generic") - if _, err := os.Stat(templateDir); os.IsNotExist(err) { + if _, err := os.Stat(templateDir); errors.Is(err, fs.ErrNotExist) { // Fall back to the provided path directly templateDir = resolvedPath - if _, err := os.Stat(templateDir); os.IsNotExist(err) { + if _, err := os.Stat(templateDir); errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("template not found at %s (also checked %s/generic)", resolvedPath, resolvedPath) } } @@ -830,7 +907,7 @@ func runCreate(ctx context.Context, opts createOptions) error { if isInteractive && !opts.pluginsChanged && !flagsMode { // Interactive mode without --plugins flag: prompt for plugins, dependencies, description - config, err := promptForPluginsAndDeps(ctx, m, selectedPlugins, skipDeployRunPrompt) + config, err := promptForPluginsAndDeps(ctx, m, selectedPlugins, skipDeployRunPrompt, opts.autoApprove) if err != nil { return err } @@ -880,9 +957,7 @@ func runCreate(ctx context.Context, opts createOptions) error { if resourceValues == nil { resourceValues = make(map[string]string, len(setVals)) } - for k, v := range setVals { - resourceValues[k] = v - } + maps.Copy(resourceValues, setVals) } // Always include mandatory plugins regardless of user selection or flags. @@ -914,21 +989,8 @@ func runCreate(ctx context.Context, opts createOptions) error { } // Validate that all required resources are provided. - for _, r := range resources { - found := false - for k := range resourceValues { - if strings.HasPrefix(k, r.Key()+".") { - found = true - break - } - } - if !found { - fieldHint := "id" - if names := r.FieldNames(); len(names) > 0 { - fieldHint = names[0] - } - return fmt.Errorf("missing required resource %q for selected plugins (use --set %s.%s=value)", r.Alias, r.Key(), fieldHint) - } + if err := validateRequiredResources(resources, resourceValues); err != nil { + return err } } @@ -990,7 +1052,11 @@ func runCreate(ctx context.Context, opts createOptions) error { plugins := make(map[string]*pluginVar, len(selectedPlugins)) for _, name := range selectedPlugins { - plugins[name] = &pluginVar{} + pv := &pluginVar{} + if mp, ok := m.Plugins[name]; ok { + pv.Stability = mp.Stability + } + plugins[name] = pv } // Template variables with generated content @@ -1404,8 +1470,8 @@ func removeEmptyDirs(root string) error { if err != nil { return err } - for i := len(dirs) - 1; i >= 0; i-- { - _ = os.Remove(dirs[i]) + for _, dir := range slices.Backward(dirs) { + _ = os.Remove(dir) } return nil } diff --git a/cmd/apps/init_test.go b/cmd/apps/init_test.go index 66fbc4ea541..81e71eed3fb 100644 --- a/cmd/apps/init_test.go +++ b/cmd/apps/init_test.go @@ -4,8 +4,11 @@ import ( "bytes" "errors" "io" + "io/fs" "os" + "os/exec" "path/filepath" + "strings" "testing" "github.com/databricks/cli/libs/apps/manifest" @@ -249,6 +252,167 @@ func TestExecuteTemplateInvalidSyntaxReturnsOriginal(t *testing.T) { assert.Equal(t, input, string(result)) } +// TestExecuteTemplatePluginStability locks down the contract that the +// AppKit init template relies on: ranging over .plugins exposes a +// .Stability field per plugin, with GA/unset rendering as the empty +// string. See databricks/appkit#264 commit d826a532 (server.ts branches +// imports between `@databricks/appkit` and `@databricks/appkit/beta`). +func TestExecuteTemplatePluginStability(t *testing.T) { + ctx := t.Context() + vars := templateVars{ + Plugins: map[string]*pluginVar{ + "ga-plugin": {}, + "beta-plugin": {Stability: "beta"}, + }, + } + + input := `{{range $n, $p := .plugins}}{{$n}}={{$p.Stability}};{{end}}` + result, err := executeTemplate(ctx, "server.ts", []byte(input), vars) + require.NoError(t, err) + got := string(result) + + assert.Contains(t, got, "ga-plugin=;") + assert.Contains(t, got, "beta-plugin=beta;") +} + +// TestExecuteTemplateBetaImportAccumulator pins the full text/template +// pattern used by the AppKit server.ts template (databricks/appkit#264 +// commit 488797fc): a string-accumulator pre-pass over .plugins that +// reassigns an outer-scope variable inside `range` and concatenates +// names via `printf`, then emits a single guarded import line. +// +// If a future refactor of executeTemplate breaks variable reassignment, +// printf, or pointer-field access on map values, this test fails before +// users see broken init output. +func TestExecuteTemplateBetaImportAccumulator(t *testing.T) { + ctx := t.Context() + + // Mirror of the relevant slice of template/server/server.ts in AppKit. + // Kept as a literal string (not loaded from the AppKit repo) so this + // test is hermetic and survives AppKit branch movement. + input := `{{- $betaImports := "" -}} +{{- range $name, $p := .plugins -}} + {{- if eq $p.Stability "beta" -}} + {{- if eq $betaImports "" -}} + {{- $betaImports = $name -}} + {{- else -}} + {{- $betaImports = printf "%s, %s" $betaImports $name -}} + {{- end -}} + {{- end -}} +{{- end -}} +import { createApp{{range $name, $p := .plugins}}{{if ne $p.Stability "beta"}}, {{$name}}{{end}}{{end}} } from '@databricks/appkit'; +{{- if ne $betaImports "" }} +import { {{$betaImports}} } from '@databricks/appkit/beta'; +{{- end}} +` + + cases := []struct { + name string + plugins map[string]*pluginVar + wantGAImports []string // names that must appear on the GA line + wantBetaImports []string // names that must appear on the beta line, "" means no beta line + wantNoBetaLine bool + }{ + { + name: "all GA: no beta line", + plugins: map[string]*pluginVar{ + "server": {}, + "analytics": {}, + }, + wantGAImports: []string{"server", "analytics"}, + wantNoBetaLine: true, + }, + { + name: "mixed single beta", + plugins: map[string]*pluginVar{ + "server": {}, + "betaOne": {Stability: "beta"}, + }, + wantGAImports: []string{"server"}, + wantBetaImports: []string{"betaOne"}, + }, + { + name: "mixed multiple betas: combined into one import line", + plugins: map[string]*pluginVar{ + "server": {}, + "betaOne": {Stability: "beta"}, + "betaTwo": {Stability: "beta"}, + }, + wantGAImports: []string{"server"}, + wantBetaImports: []string{"betaOne", "betaTwo"}, + }, + { + name: "all beta: createApp alone on GA line", + plugins: map[string]*pluginVar{ + "betaOne": {Stability: "beta"}, + "betaTwo": {Stability: "beta"}, + }, + wantBetaImports: []string{"betaOne", "betaTwo"}, + }, + { + name: "future tier (alpha) routes to GA line for now", + plugins: map[string]*pluginVar{ + "server": {}, + "alphaOne": {Stability: "alpha"}, + }, + wantGAImports: []string{"server", "alphaOne"}, + wantNoBetaLine: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + vars := templateVars{Plugins: tc.plugins} + result, err := executeTemplate(ctx, "server.ts", []byte(input), vars) + require.NoError(t, err) + got := string(result) + + lines := strings.Split(got, "\n") + require.NotEmpty(t, lines) + + // GA line is always first: starts with `import { createApp` + // and ends with `from '@databricks/appkit';`. + gaLine := lines[0] + assert.True(t, strings.HasPrefix(gaLine, "import { createApp"), + "GA line: %q", gaLine) + assert.True(t, strings.HasSuffix(gaLine, "} from '@databricks/appkit';"), + "GA line: %q", gaLine) + for _, name := range tc.wantGAImports { + assert.Contains(t, gaLine, name, + "GA line missing %q: %q", name, gaLine) + } + for _, name := range tc.wantBetaImports { + assert.NotContains(t, gaLine, ", "+name, + "beta plugin %q leaked onto GA line: %q", name, gaLine) + } + + if tc.wantNoBetaLine { + assert.NotContains(t, got, "@databricks/appkit/beta", + "unexpected beta import emitted: %q", got) + return + } + + // Beta line: exactly one `from '@databricks/appkit/beta'` line. + betaLineCount := strings.Count(got, "from '@databricks/appkit/beta'") + assert.Equal(t, 1, betaLineCount, + "expected exactly one beta import line, got %d: %q", betaLineCount, got) + + var betaLine string + for _, l := range lines { + if strings.Contains(l, "@databricks/appkit/beta") { + betaLine = l + break + } + } + require.NotEmpty(t, betaLine, "beta line not found in: %q", got) + for _, name := range tc.wantBetaImports { + assert.Contains(t, betaLine, name, + "beta line missing %q: %q", name, betaLine) + } + }) + } +} + func TestInitCmdBranchAndVersionMutuallyExclusive(t *testing.T) { cmd := newInitCmd() cmd.PreRunE = nil // skip workspace client setup for flag validation test @@ -609,6 +773,59 @@ func TestPluginHasResourceField(t *testing.T) { assert.False(t, pluginHasResourceField(p, "nosuch", "id")) } +func TestValidateRequiredResources(t *testing.T) { + tests := []struct { + name string + resources []manifest.Resource + resourceValues map[string]string + wantErr string + }{ + { + name: "all provided", + resources: []manifest.Resource{ + {Alias: "SQL Warehouse", ResourceKey: "sql-warehouse", PluginName: "analytics"}, + }, + resourceValues: map[string]string{"sql-warehouse.id": "abc"}, + }, + { + name: "missing resource with fields includes plugin prefix in hint", + resources: []manifest.Resource{ + { + Alias: "Postgres", + ResourceKey: "postgres", + PluginName: "lakebase", + Fields: map[string]manifest.ResourceField{ + "branch": {Description: "branch"}, + "database": {Description: "database"}, + }, + }, + }, + resourceValues: map[string]string{}, + wantErr: `use --set lakebase.postgres.branch=value`, + }, + { + name: "missing resource without fields defaults to id", + resources: []manifest.Resource{ + {Alias: "SQL Warehouse", ResourceKey: "sql-warehouse", PluginName: "analytics"}, + }, + resourceValues: map[string]string{}, + wantErr: `use --set analytics.sql-warehouse.id=value`, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := validateRequiredResources(tc.resources, tc.resourceValues) + if tc.wantErr == "" { + assert.NoError(t, err) + } else { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + } + }) + } +} + func TestAppendUnique(t *testing.T) { result := appendUnique([]string{"a", "b"}, "b", "c", "a", "d") assert.Equal(t, []string{"a", "b", "c", "d"}, result) @@ -687,3 +904,171 @@ func TestRunManifestOnlyUsesTemplatePathEnvVar(t *testing.T) { out := buf.String() assert.Equal(t, content, out) } + +func TestCopyFileDeps(t *testing.T) { + ctx := t.Context() + + srcDir := t.TempDir() + destDir := t.TempDir() + + // Create a fake tarball in srcDir + tgzContent := []byte("fake-tarball-content") + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "my-pkg-1.0.0.tgz"), tgzContent, 0o644)) + + // package.json with file: dep, a registry dep, and a devDep with file: + pkgJSON := []byte(`{ + "dependencies": { + "my-pkg": "file:./my-pkg-1.0.0.tgz", + "lodash": "4.17.21" + }, + "devDependencies": { + "missing-pkg": "file:./nonexistent.tgz" + } + }`) + + copyFileDeps(ctx, pkgJSON, srcDir, destDir) + + // The file: dep should be copied + copied, err := os.ReadFile(filepath.Join(destDir, "my-pkg-1.0.0.tgz")) + require.NoError(t, err) + assert.Equal(t, tgzContent, copied) + + // The registry dep should NOT create any file + _, err = os.Stat(filepath.Join(destDir, "4.17.21")) + assert.ErrorIs(t, err, fs.ErrNotExist) + + // The missing file: dep should be skipped gracefully (no panic, no error) + _, err = os.Stat(filepath.Join(destDir, "nonexistent.tgz")) + assert.ErrorIs(t, err, fs.ErrNotExist) +} + +func TestCopyFileDepsInvalidJSON(t *testing.T) { + ctx := t.Context() + srcDir := t.TempDir() + destDir := t.TempDir() + + // Should not panic on invalid JSON + copyFileDeps(ctx, []byte("not json"), srcDir, destDir) + + // destDir should remain empty + entries, err := os.ReadDir(destDir) + require.NoError(t, err) + assert.Empty(t, entries) +} + +func TestCopyFileDepsNoDeps(t *testing.T) { + ctx := t.Context() + srcDir := t.TempDir() + destDir := t.TempDir() + + // package.json with no file: deps + pkgJSON := []byte(`{"dependencies": {"react": "19.0.0"}}`) + copyFileDeps(ctx, pkgJSON, srcDir, destDir) + + entries, err := os.ReadDir(destDir) + require.NoError(t, err) + assert.Empty(t, entries) +} + +func skipIfNoNpm(t *testing.T) { + t.Helper() + if _, err := exec.LookPath("npm"); err != nil { + t.Skip("npm not found in PATH, skipping") + } +} + +func TestStartBackgroundNpmInstall_NoLockFile(t *testing.T) { + srcDir := t.TempDir() + destDir := t.TempDir() + + // Only package.json, no lock file + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package.json"), []byte(`{"name":"test"}`), 0o644)) + + ch := startBackgroundNpmInstall(t.Context(), srcDir, destDir, "test-app") + assert.Nil(t, ch) +} + +func TestStartBackgroundNpmInstall_NoPackageJSON(t *testing.T) { + srcDir := t.TempDir() + destDir := t.TempDir() + + // Only lock file, no package.json + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package-lock.json"), []byte(`{}`), 0o644)) + + ch := startBackgroundNpmInstall(t.Context(), srcDir, destDir, "test-app") + assert.Nil(t, ch) +} + +func TestStartBackgroundNpmInstall_CopiesFiles(t *testing.T) { + skipIfNoNpm(t) + + srcDir := t.TempDir() + destDir := filepath.Join(t.TempDir(), "output") + + pkgJSON := []byte(`{"name":"{{.projectName}}","version":"1.0.0"}`) + lockJSON := []byte(`{"lockfileVersion":3,"packages":{}}`) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package.json"), pkgJSON, 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package-lock.json"), lockJSON, 0o644)) + + ch := startBackgroundNpmInstall(t.Context(), srcDir, destDir, "my-app") + require.NotNil(t, ch) + + // Drain the channel to avoid goroutine leak (npm ci will fail on fake data) + <-ch + + // package.json should be written with template substitution + got, err := os.ReadFile(filepath.Join(destDir, "package.json")) + require.NoError(t, err) + assert.Contains(t, string(got), `"my-app"`) + assert.NotContains(t, string(got), "{{.projectName}}") + + // package-lock.json should be copied verbatim + gotLock, err := os.ReadFile(filepath.Join(destDir, "package-lock.json")) + require.NoError(t, err) + assert.Equal(t, lockJSON, gotLock) +} + +func TestStartBackgroundNpmInstall_CopiesFileDeps(t *testing.T) { + skipIfNoNpm(t) + + srcDir := t.TempDir() + destDir := filepath.Join(t.TempDir(), "output") + + tgzContent := []byte("fake-tarball") + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "my-pkg-1.0.0.tgz"), tgzContent, 0o644)) + + pkgJSON := []byte(`{"name":"test","dependencies":{"my-pkg":"file:./my-pkg-1.0.0.tgz"}}`) + lockJSON := []byte(`{"lockfileVersion":3,"packages":{}}`) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package.json"), pkgJSON, 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package-lock.json"), lockJSON, 0o644)) + + ch := startBackgroundNpmInstall(t.Context(), srcDir, destDir, "test-app") + require.NotNil(t, ch) + <-ch + + // The file: dep tarball should be copied to destDir + copied, err := os.ReadFile(filepath.Join(destDir, "my-pkg-1.0.0.tgz")) + require.NoError(t, err) + assert.Equal(t, tgzContent, copied) +} + +func TestStartBackgroundNpmInstall_TemplateSubstitution(t *testing.T) { + skipIfNoNpm(t) + + srcDir := t.TempDir() + destDir := filepath.Join(t.TempDir(), "output") + + pkgJSON := []byte(`{"name":"{{.projectName}}","description":"{{.appDescription}}"}`) + lockJSON := []byte(`{"lockfileVersion":3}`) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package.json"), pkgJSON, 0o644)) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "package-lock.json"), lockJSON, 0o644)) + + ch := startBackgroundNpmInstall(t.Context(), srcDir, destDir, "cool-project") + require.NotNil(t, ch) + <-ch + + got, err := os.ReadFile(filepath.Join(destDir, "package.json")) + require.NoError(t, err) + assert.Contains(t, string(got), `"cool-project"`) + assert.NotContains(t, string(got), "{{.projectName}}") +} diff --git a/cmd/apps/manifest.go b/cmd/apps/manifest.go index 9b853769645..38df201acc0 100644 --- a/cmd/apps/manifest.go +++ b/cmd/apps/manifest.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io/fs" "os" "path/filepath" @@ -46,9 +47,9 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string) } templateDir := filepath.Join(resolvedPath, "generic") - if _, err := os.Stat(templateDir); os.IsNotExist(err) { + if _, err := os.Stat(templateDir); errors.Is(err, fs.ErrNotExist) { templateDir = resolvedPath - if _, err := os.Stat(templateDir); os.IsNotExist(err) { + if _, err := os.Stat(templateDir); errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("template not found at %s (also checked %s/generic)", resolvedPath, resolvedPath) } } diff --git a/cmd/apps/run_local.go b/cmd/apps/run_local.go index d54ab0d7262..d4bc546ab7c 100644 --- a/cmd/apps/run_local.go +++ b/cmd/apps/run_local.go @@ -191,7 +191,7 @@ func newRunLocal() *cobra.Command { This command starts an app locally.` - cmd.Flags().IntVar(&port, "port", 8001, "Port on which to run the app app proxy") + cmd.Flags().IntVar(&port, "port", 8001, "Port on which to run the app proxy") cmd.Flags().IntVar(&appPort, "app-port", runlocal.DEFAULT_PORT, "Port on which to run the app") cmd.Flags().BoolVar(&debug, "debug", false, "Enable debug mode") cmd.Flags().BoolVar(&prepareEnvironment, "prepare-environment", false, "Prepares the environment for running the app. Requires 'uv' to be installed.") diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index 6bca3f5962d..28aa1269df4 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -28,7 +28,6 @@ GCP: https://docs.gcp.databricks.com/dev-tools/auth/index.html`, var authArguments auth.AuthArguments cmd.PersistentFlags().StringVar(&authArguments.Host, "host", "", "Databricks Host") cmd.PersistentFlags().StringVar(&authArguments.AccountID, "account-id", "", "Databricks Account ID") - cmd.PersistentFlags().BoolVar(&authArguments.IsUnifiedHost, "experimental-is-unified-host", false, "Flag to indicate if the host is a unified host") cmd.PersistentFlags().StringVar(&authArguments.WorkspaceID, "workspace-id", "", "Databricks Workspace ID") cmd.AddCommand(newEnvCommand()) @@ -46,9 +45,9 @@ func promptForHost(ctx context.Context) (string, error) { return "", errors.New("the command is being run in a non-interactive environment, please specify a host using --host") } - prompt := cmdio.Prompt(ctx) - prompt.Label = "Databricks host (e.g. https://.cloud.databricks.com)" - return prompt.Run() + return cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Databricks host (e.g. https://.cloud.databricks.com)", + }) } func promptForAccountID(ctx context.Context) (string, error) { @@ -56,11 +55,9 @@ func promptForAccountID(ctx context.Context) (string, error) { return "", errors.New("the command is being run in a non-interactive environment, please specify an account ID using --account-id") } - prompt := cmdio.Prompt(ctx) - prompt.Label = "Databricks account ID" - prompt.Default = "" - prompt.AllowEdit = true - return prompt.Run() + return cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Databricks account ID", + }) } // validateProfileHostConflict checks that --profile and --host don't conflict. diff --git a/cmd/auth/auth_test.go b/cmd/auth/auth_test.go index c54d550f016..fc7e5d533d4 100644 --- a/cmd/auth/auth_test.go +++ b/cmd/auth/auth_test.go @@ -70,23 +70,23 @@ func TestValidateProfileHostConflict(t *testing.T) { // through Cobra's lifecycle (PreRunE on login) and that the root command's // PersistentPreRunE is NOT shadowed (it initializes logging, IO, user agent). func TestProfileHostConflictViaCobra(t *testing.T) { - // Point at a config file that has "profile-1" with host https://www.host1.com. + // Point at a config file that has "profile-1" with host https://www.host1.test. t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg") ctx := cmdctx.GenerateExecId(t.Context()) cli := root.New(ctx) cli.AddCommand(New()) - // Set args: auth login --profile profile-1 --host https://other.host.com + // Set args: auth login --profile profile-1 --host https://other.host.test cli.SetArgs([]string{ "auth", "login", "--profile", "profile-1", - "--host", "https://other.host.com", + "--host", "https://other.host.test", }) _, err := cli.ExecuteContextC(ctx) require.Error(t, err) - assert.Contains(t, err.Error(), `--profile "profile-1" has host "https://www.host1.com", which conflicts with --host "https://other.host.com"`) + assert.Contains(t, err.Error(), `--profile "profile-1" has host "https://www.host1.test", which conflicts with --host "https://other.host.test"`) assert.Contains(t, err.Error(), "Use --profile only to select a profile") } @@ -101,12 +101,12 @@ func TestProfileHostConflictTokenViaCobra(t *testing.T) { cli.SetArgs([]string{ "auth", "token", "--profile", "profile-1", - "--host", "https://other.host.com", + "--host", "https://other.host.test", }) _, err := cli.ExecuteContextC(ctx) require.Error(t, err) - assert.Contains(t, err.Error(), `--profile "profile-1" has host "https://www.host1.com", which conflicts with --host "https://other.host.com"`) + assert.Contains(t, err.Error(), `--profile "profile-1" has host "https://www.host1.test", which conflicts with --host "https://other.host.test"`) } // TestProfileHostCompatibleViaCobra verifies that matching --profile and --host @@ -122,7 +122,7 @@ func TestProfileHostCompatibleViaCobra(t *testing.T) { cli.SetArgs([]string{ "auth", "login", "--profile", "profile-1", - "--host", "https://www.host1.com", + "--host", "https://www.host1.test", }) _, err := cli.ExecuteContextC(ctx) diff --git a/cmd/auth/describe.go b/cmd/auth/describe.go index c21eab376c9..56f10630de1 100644 --- a/cmd/auth/describe.go +++ b/cmd/auth/describe.go @@ -4,11 +4,15 @@ import ( "context" "encoding/json" "fmt" + "path/filepath" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/auth/storage" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/flags" + "github.com/databricks/cli/libs/log" "github.com/databricks/databricks-sdk-go/config" "github.com/spf13/cobra" ) @@ -21,10 +25,16 @@ var authTemplate = `{{"Host:" | bold}} {{.Status.Details.Host}} {{"User:" | bold}} {{.Status.Username}} {{- end}} {{"Authenticated with:" | bold}} {{.Status.Details.AuthType}} +{{- if .Status.TokenStorage}} +{{"Token storage:" | bold}} {{.Status.TokenStorage.Mode}}, {{.Status.TokenStorage.Location}} {{ printf "(from %s)" .Status.TokenStorage.Source | italic}} +{{- end}} ----- ` + configurationTemplate var errorTemplate = `Unable to authenticate: {{.Status.Error}} +{{- if .Status.TokenStorage}} +{{"Token storage:" | bold}} {{.Status.TokenStorage.Mode}}, {{.Status.TokenStorage.Location}} {{ printf "(from %s)" .Status.TokenStorage.Source | italic}} +{{- end}} ----- ` + configurationTemplate @@ -80,10 +90,12 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try cfg, isAccount, err := fn(cmd, args) ctx := cmd.Context() if err != nil { - return &authStatus{ - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + details := getAuthDetails(cmd, cfg, showSensitive) + return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct + Status: "error", + Error: err, + Details: details, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), }, nil } @@ -93,18 +105,22 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try // Doing a simple API call to check if the auth is valid _, err := a.Workspaces.List(ctx) if err != nil { - return &authStatus{ - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + details := getAuthDetails(cmd, cfg, showSensitive) + return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct + Status: "error", + Error: err, + Details: details, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), }, nil } + details := getAuthDetails(cmd, a.Config, showSensitive) status := authStatus{ - Status: "success", - Details: getAuthDetails(cmd, a.Config, showSensitive), - AccountID: a.Config.AccountID, - Username: a.Config.Username, + Status: "success", + Details: details, + AccountID: a.Config.AccountID, + Username: a.Config.Username, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), } return &status, nil @@ -113,17 +129,21 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try w := cmdctx.WorkspaceClient(ctx) me, err := w.CurrentUser.Me(ctx) if err != nil { - return &authStatus{ - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + details := getAuthDetails(cmd, cfg, showSensitive) + return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct + Status: "error", + Error: err, + Details: details, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), }, nil } + details := getAuthDetails(cmd, w.Config, showSensitive) status := authStatus{ - Status: "success", - Details: getAuthDetails(cmd, w.Config, showSensitive), - Username: me.UserName, + Status: "success", + Details: details, + Username: me.UserName, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), } return &status, nil @@ -153,11 +173,85 @@ func render(ctx context.Context, cmd *cobra.Command, status *authStatus, templat } type authStatus struct { - Status string `json:"status"` - Error error `json:"error,omitempty"` - Username string `json:"username,omitempty"` - AccountID string `json:"account_id,omitempty"` - Details config.AuthDetails `json:"details"` + Status string `json:"status"` + Error error `json:"error,omitempty"` + Username string `json:"username,omitempty"` + AccountID string `json:"account_id,omitempty"` + Details config.AuthDetails `json:"details"` + TokenStorage *tokenStorageInfo `json:"token_storage,omitempty"` +} + +// tokenStorageInfo describes where the U2M (databricks-cli) token cache lives +// and which precedence level produced that choice. Populated only when the +// active auth type is "databricks-cli"; other auth types do not use the +// CLI's U2M token cache and the field is omitted. +type tokenStorageInfo struct { + Mode string `json:"mode"` + Location string `json:"location"` + Source string `json:"source"` +} + +const ( + plaintextLocation = "~/.databricks/token-cache.json" + secureLocation = "OS keyring (service: databricks-cli)" +) + +// resolveTokenStorageInfo returns storage info for the U2M token cache, or +// nil if the active auth type does not use the cache. Resolver errors are +// logged at debug level and treated as "no info available" rather than +// failing describe; the user-visible auth status is more important than +// secondary metadata about where a token would have been stored. +func resolveTokenStorageInfo(ctx context.Context, authType string) *tokenStorageInfo { + if authType != authTypeDatabricksCLI { + return nil + } + mode, source, err := storage.ResolveStorageModeWithSource(ctx, "") + if err != nil { + log.Debugf(ctx, "auth describe: resolve storage mode: %v", err) + return nil + } + info := &tokenStorageInfo{ + Mode: string(mode), + Source: storageSourceLabel(ctx, source), + } + switch mode { + case storage.StorageModePlaintext: + info.Location = plaintextLocation + case storage.StorageModeSecure: + info.Location = secureLocation + default: + return nil + } + return info +} + +// storageSourceLabel returns a user-facing label for source. For +// StorageSourceConfig, it appends the resolved config file path +// (DATABRICKS_CONFIG_FILE or /.databrickscfg) so the output matches +// the SDK's config.Source style ("from config file") rather than +// hardcoding ".databrickscfg" when a custom path is in use. +func storageSourceLabel(ctx context.Context, source storage.StorageSource) string { + if source != storage.StorageSourceConfig { + return source.String() + } + return "auth_storage in [__settings__] section of " + resolvedConfigPath(ctx) +} + +// resolvedConfigPath returns the path the storage-mode resolver loaded from +// for [__settings__].auth_storage: DATABRICKS_CONFIG_FILE if set, otherwise +// /.databrickscfg. Falls back to "~/.databrickscfg" only when the home +// directory cannot be determined (rare; describe should not crash on this +// secondary metadata path). +func resolvedConfigPath(ctx context.Context) string { + if path := env.Get(ctx, "DATABRICKS_CONFIG_FILE"); path != "" { + return path + } + home, err := env.UserHomeDir(ctx) + if err != nil { + log.Debugf(ctx, "auth describe: resolve home dir: %v", err) + return "~/.databrickscfg" + } + return filepath.ToSlash(filepath.Join(home, ".databrickscfg")) } func getAuthDetails(cmd *cobra.Command, cfg *config.Config, showSensitive bool) config.AuthDetails { diff --git a/cmd/auth/describe_test.go b/cmd/auth/describe_test.go index 1ee8d5122d3..528decf1022 100644 --- a/cmd/auth/describe_test.go +++ b/cmd/auth/describe_test.go @@ -2,13 +2,16 @@ package auth import ( "errors" + "path/filepath" "testing" + "github.com/databricks/cli/libs/auth/storage" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -44,7 +47,7 @@ func TestGetWorkspaceAuthStatus(t *testing.T) { status, err := getAuthStatus(cmd, []string{}, showSensitive, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { err := config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ - "host": "https://test.com", + "host": "https://test.test", "token": "test-token", "auth_type": "azure-cli", }) @@ -55,7 +58,7 @@ func TestGetWorkspaceAuthStatus(t *testing.T) { require.NotNil(t, status) require.Equal(t, "success", status.Status) require.Equal(t, "test-user", status.Username) - require.Equal(t, "https://test.com", status.Details.Host) + require.Equal(t, "https://test.test", status.Details.Host) require.Equal(t, "azure-cli", status.Details.AuthType) require.Equal(t, "azure-cli", status.Details.Configuration["auth_type"].Value) @@ -97,7 +100,7 @@ func TestGetWorkspaceAuthStatusError(t *testing.T) { status, err := getAuthStatus(cmd, []string{}, showSensitive, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { err = config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ - "host": "https://test.com", + "host": "https://test.test", "token": "test-token", "auth_type": "azure-cli", }) @@ -146,7 +149,7 @@ func TestGetWorkspaceAuthStatusSensitive(t *testing.T) { status, err := getAuthStatus(cmd, []string{}, showSensitive, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { err = config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ - "host": "https://test.com", + "host": "https://test.test", "token": "test-token", "auth_type": "azure-cli", }) @@ -196,7 +199,7 @@ func TestGetAccountAuthStatus(t *testing.T) { err = config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ "account_id": "test-account-id", "username": "test-user", - "host": "https://test.com", + "host": "https://test.test", "token": "test-token", "auth_type": "azure-cli", }) @@ -207,7 +210,7 @@ func TestGetAccountAuthStatus(t *testing.T) { require.Equal(t, "success", status.Status) require.Equal(t, "test-user", status.Username) - require.Equal(t, "https://test.com", status.Details.Host) + require.Equal(t, "https://test.test", status.Details.Host) require.Equal(t, "azure-cli", status.Details.AuthType) require.Equal(t, "test-account-id", status.AccountID) @@ -223,3 +226,141 @@ func TestGetAccountAuthStatus(t *testing.T) { require.Equal(t, "--profile flag", status.Details.Configuration["profile"].Source.String()) require.False(t, status.Details.Configuration["profile"].AuthTypeMismatch) } + +func TestResolveTokenStorageInfo(t *testing.T) { + cases := []struct { + name string + authType string + envValue string + want *tokenStorageInfo + }{ + { + name: "non-databricks-cli auth has no token storage", + authType: "pat", + want: nil, + }, + { + name: "databricks-cli with default plaintext", + authType: authTypeDatabricksCLI, + want: &tokenStorageInfo{ + Mode: "plaintext", + Location: plaintextLocation, + Source: "default", + }, + }, + { + name: "databricks-cli with secure from env", + authType: authTypeDatabricksCLI, + envValue: "secure", + want: &tokenStorageInfo{ + Mode: "secure", + Location: secureLocation, + Source: "DATABRICKS_AUTH_STORAGE environment variable", + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Setenv(storage.EnvVar, tc.envValue) + t.Setenv("DATABRICKS_CONFIG_FILE", t.TempDir()+"/.databrickscfg") + + got := resolveTokenStorageInfo(t.Context(), tc.authType) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestStorageSourceLabel_ConfigUsesResolvedPath(t *testing.T) { + ctx := t.Context() + t.Setenv("DATABRICKS_CONFIG_FILE", "/custom/path/.databrickscfg") + got := storageSourceLabel(ctx, storage.StorageSourceConfig) + assert.Equal(t, "auth_storage in [__settings__] section of /custom/path/.databrickscfg", got) +} + +func TestStorageSourceLabel_ConfigDefaultsToHome(t *testing.T) { + ctx := t.Context() + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + t.Setenv("DATABRICKS_CONFIG_FILE", "") + got := storageSourceLabel(ctx, storage.StorageSourceConfig) + expected := "auth_storage in [__settings__] section of " + filepath.ToSlash(filepath.Join(home, ".databrickscfg")) + assert.Equal(t, expected, got) +} + +func TestStorageSourceLabel_NonConfigDelegatesToSource(t *testing.T) { + ctx := t.Context() + t.Setenv("DATABRICKS_CONFIG_FILE", "/should/not/appear") + assert.Equal(t, "default", storageSourceLabel(ctx, storage.StorageSourceDefault)) + assert.Equal(t, "DATABRICKS_AUTH_STORAGE environment variable", storageSourceLabel(ctx, storage.StorageSourceEnvVar)) + assert.Equal(t, "command-line override", storageSourceLabel(ctx, storage.StorageSourceOverride)) +} + +func TestGetWorkspaceAuthStatus_U2M_PopulatesTokenStorage(t *testing.T) { + ctx := t.Context() + m := mocks.NewMockWorkspaceClient(t) + ctx = cmdctx.SetWorkspaceClient(ctx, m.WorkspaceClient) + + cmd := &cobra.Command{} + cmd.SetContext(ctx) + + currentUserApi := m.GetMockCurrentUserAPI() + currentUserApi.EXPECT().Me(mock.Anything).Return(&iam.User{UserName: "u2m-user"}, nil) + + cmd.Flags().String("host", "", "") + cmd.Flags().String("profile", "", "") + require.NoError(t, cmd.Flag("profile").Value.Set("u2m-profile")) + cmd.Flag("profile").Changed = true + + cfg := &config.Config{Profile: "u2m-profile"} + m.WorkspaceClient.Config = cfg + t.Setenv(storage.EnvVar, "secure") + t.Setenv("DATABRICKS_CONFIG_FILE", t.TempDir()+"/.databrickscfg") + require.NoError(t, config.ConfigAttributes.Configure(cfg)) + + status, err := getAuthStatus(cmd, []string{}, false, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { + require.NoError(t, config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ + "host": "https://test.test", + "auth_type": authTypeDatabricksCLI, + })) + return cfg, false, nil + }) + require.NoError(t, err) + require.NotNil(t, status) + require.NotNil(t, status.TokenStorage) + assert.Equal(t, "secure", status.TokenStorage.Mode) + assert.Equal(t, secureLocation, status.TokenStorage.Location) + assert.Equal(t, "DATABRICKS_AUTH_STORAGE environment variable", status.TokenStorage.Source) +} + +func TestGetWorkspaceAuthStatus_NonU2M_OmitsTokenStorage(t *testing.T) { + ctx := t.Context() + m := mocks.NewMockWorkspaceClient(t) + ctx = cmdctx.SetWorkspaceClient(ctx, m.WorkspaceClient) + + cmd := &cobra.Command{} + cmd.SetContext(ctx) + + currentUserApi := m.GetMockCurrentUserAPI() + currentUserApi.EXPECT().Me(mock.Anything).Return(&iam.User{UserName: "pat-user"}, nil) + + cmd.Flags().String("host", "", "") + cmd.Flags().String("profile", "", "") + + cfg := &config.Config{} + m.WorkspaceClient.Config = cfg + require.NoError(t, config.ConfigAttributes.Configure(cfg)) + + status, err := getAuthStatus(cmd, []string{}, false, func(cmd *cobra.Command, args []string) (*config.Config, bool, error) { + require.NoError(t, config.ConfigAttributes.ResolveFromStringMap(cfg, map[string]string{ + "host": "https://test.test", + "token": "pat-token", + "auth_type": "pat", + })) + return cfg, false, nil + }) + require.NoError(t, err) + require.NotNil(t, status) + assert.Nil(t, status.TokenStorage) +} diff --git a/cmd/auth/env.go b/cmd/auth/env.go index 11149af8c04..9861d491e42 100644 --- a/cmd/auth/env.go +++ b/cmd/auth/env.go @@ -93,8 +93,12 @@ func loadFromDatabricksCfg(ctx context.Context, cfg *config.Config) error { func newEnvCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "env", - Short: "Get env", + Use: "env", + Short: "Get env (deprecated)", + Hidden: true, + Long: `Get env. + +Deprecated: this command will be removed in a future release.`, } var host string @@ -103,6 +107,8 @@ func newEnvCommand() *cobra.Command { cmd.Flags().StringVar(&profile, "profile", profile, "Profile to get auth env for") cmd.RunE = func(cmd *cobra.Command, args []string) error { + fmt.Fprintln(cmd.ErrOrStderr(), "Warning: 'databricks auth env' is deprecated and will be removed in a future release.") + cfg := &config.Config{ Host: host, Profile: profile, diff --git a/cmd/auth/login.go b/cmd/auth/login.go index eaf42a3c925..aedaab06cf9 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -4,25 +4,25 @@ import ( "context" "errors" "fmt" - "io" "runtime" "strconv" "strings" "time" "github.com/databricks/cli/libs/auth" + "github.com/databricks/cli/libs/auth/storage" + "github.com/databricks/cli/libs/browser" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/cfgpickers" "github.com/databricks/cli/libs/databrickscfg/profile" "github.com/databricks/cli/libs/env" - "github.com/databricks/cli/libs/exec" "github.com/databricks/cli/libs/log" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/config/experimental/auth/authconv" "github.com/databricks/databricks-sdk-go/credentials/u2m" - browserpkg "github.com/pkg/browser" + "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" "github.com/spf13/cobra" "golang.org/x/oauth2" ) @@ -32,13 +32,10 @@ func promptForProfile(ctx context.Context, defaultValue string) (string, error) return "", nil } - prompt := cmdio.Prompt(ctx) - prompt.Label = "Databricks profile name [" + defaultValue + "]" - prompt.AllowEdit = true - result, err := prompt.Run() + result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Databricks profile name [" + defaultValue + "]", + }) if result == "" { - // Manually return the default value. We could use the prompt.Default - // field, but be inconsistent with other prompts in the CLI. return defaultValue, err } return result, err @@ -147,6 +144,15 @@ a new profile is created. ctx := cmd.Context() profileName := cmd.Flag("profile").Value.String() + // Resolve the cache before the browser step so a missing/locked keyring + // surfaces here rather than after the user completes OAuth. When secure + // is selected but the keyring is unreachable, this silently falls back + // to plaintext and persists auth_storage = plaintext for next time. + tokenCache, mode, err := storage.ResolveCacheForLogin(ctx, "") + if err != nil { + return err + } + // Cluster and Serverless are mutually exclusive. if configureCluster && configureServerless { return errors.New("please either configure serverless or cluster, not both") @@ -172,6 +178,43 @@ a new profile is created. } } + // When interactive and nothing was specified, show a picker that lets + // the user re-login to an existing profile, create a new one, or enter + // a host URL. With no profiles configured the picker still shows the + // two action entries so the user can choose between web-based discovery + // (Create a new profile) and a manual host URL. + if profileName == "" && authArguments.Host == "" && len(args) == 0 && cmdio.IsPromptSupported(ctx) { + allProfiles, err := profile.DefaultProfiler.LoadProfiles(ctx, profile.MatchAllProfiles) + if err != nil && !errors.Is(err, profile.ErrNoConfiguration) { + return err + } + label := "Select a profile" + if len(allProfiles) == 0 { + label = "How would you like to log in?" + } + currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, env.Get(ctx, "DATABRICKS_CONFIG_FILE")) + result, selected, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{ + Label: label, + Default: currentDefault, + IncludeExtras: true, + }) + if err != nil { + return err + } + switch result { + case profilePickerProfile: + profileName = selected + case profilePickerEnterHost: + host, err := promptForHost(ctx) + if err != nil { + return err + } + authArguments.Host = host + case profilePickerCreateNew: + // Fall through to the profile name prompt below. + } + } + // If the user has not specified a profile name, prompt for one. if profileName == "" { var err error @@ -197,14 +240,16 @@ a new profile is created. if err := validateDiscoveryFlagCompatibility(cmd); err != nil { return err } - return discoveryLogin(ctx, &defaultDiscoveryClient{}, profileName, loginTimeout, scopes, existingProfile, getBrowserFunc(cmd)) - } - - // Load unified host flag from the profile if not explicitly set via CLI flag. - // WorkspaceID is NOT loaded here; it is deferred to setHostAndAccountId() - // so that URL query params (?o=...) can override stale profile values. - if !cmd.Flag("experimental-is-unified-host").Changed && existingProfile != nil { - authArguments.IsUnifiedHost = existingProfile.IsUnifiedHost + return discoveryLogin(ctx, discoveryLoginInputs{ + dc: &defaultDiscoveryClient{}, + profileName: profileName, + timeout: loginTimeout, + scopes: scopes, + existingProfile: existingProfile, + browserFunc: getBrowserFunc(cmd), + tokenCache: tokenCache, + mode: mode, + }) } err = setHostAndAccountId(ctx, existingProfile, authArguments, args) @@ -232,6 +277,7 @@ a new profile is created. persistentAuthOpts := []u2m.PersistentAuthOption{ u2m.WithOAuthArgument(oauthArgument), u2m.WithBrowser(getBrowserFunc(cmd)), + u2m.WithTokenCache(storage.WrapForOAuthArgument(tokenCache, mode, oauthArgument)), } if len(scopesList) > 0 { persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList)) @@ -256,8 +302,7 @@ a new profile is created. // If discovery gave us an account_id but we still have no workspace_id, // prompt the user to select a workspace. This applies to any host where - // .well-known/databricks-config returned an account_id, regardless of - // whether IsUnifiedHost is set. + // .well-known/databricks-config returned an account_id. shouldPromptWorkspace := authArguments.AccountID != "" && authArguments.WorkspaceID == "" && !skipWorkspace @@ -281,15 +326,11 @@ a new profile is created. var clusterID, serverlessComputeID string // Keys to explicitly remove from the profile. OAuth login always - // clears incompatible credential fields (PAT, basic auth, M2M). + // clears incompatible credential fields (PAT, basic auth, M2M) and + // the deprecated experimental_is_unified_host key (routing now comes + // from .well-known discovery, so stale values would be misleading). clearKeys := oauthLoginClearKeys() - - // Boolean false is zero-valued and skipped by SaveToProfile's IsZero - // check. Explicitly clear experimental_is_unified_host when false so - // it doesn't remain sticky from a previous login. - if !authArguments.IsUnifiedHost { - clearKeys = append(clearKeys, "experimental_is_unified_host") - } + clearKeys = append(clearKeys, databrickscfg.ExperimentalIsUnifiedHostKey) switch { case configureCluster: @@ -297,11 +338,10 @@ a new profile is created. // We use a custom CredentialsStrategy that wraps the token we just minted, // avoiding the need to spawn a child CLI process (which AuthType "databricks-cli" does). w, err := databricks.NewWorkspaceClient(&databricks.Config{ - Host: authArguments.Host, - AccountID: authArguments.AccountID, - WorkspaceID: authArguments.WorkspaceID, - Experimental_IsUnifiedHost: authArguments.IsUnifiedHost, - Credentials: config.NewTokenSourceStrategy("login-token", authconv.AuthTokenSource(persistentAuth)), + Host: authArguments.Host, + AccountID: authArguments.AccountID, + WorkspaceID: authArguments.WorkspaceID, + Credentials: config.NewTokenSourceStrategy("login-token", authconv.AuthTokenSource(persistentAuth)), }) if err != nil { return err @@ -322,17 +362,19 @@ a new profile is created. } if profileName != "" { + // experimental_is_unified_host is no longer written to new profiles. + // Routing now comes from .well-known discovery; stale keys on existing + // profiles are cleaned up via clearKeys above. err := databrickscfg.SaveToProfile(ctx, &config.Config{ - Profile: profileName, - Host: authArguments.Host, - AuthType: authTypeDatabricksCLI, - AccountID: authArguments.AccountID, - WorkspaceID: authArguments.WorkspaceID, - Experimental_IsUnifiedHost: authArguments.IsUnifiedHost, - ClusterID: clusterID, - ConfigFile: env.Get(ctx, "DATABRICKS_CONFIG_FILE"), - ServerlessComputeID: serverlessComputeID, - Scopes: scopesList, + Profile: profileName, + Host: authArguments.Host, + AuthType: authTypeDatabricksCLI, + AccountID: authArguments.AccountID, + WorkspaceID: authArguments.WorkspaceID, + ClusterID: clusterID, + ConfigFile: env.Get(ctx, "DATABRICKS_CONFIG_FILE"), + ServerlessComputeID: serverlessComputeID, + Scopes: scopesList, }, clearKeys...) if err != nil { return err @@ -409,52 +451,32 @@ func setHostAndAccountId(ctx context.Context, existingProfile *profile.Profile, // are logged as warnings and never block login. runHostDiscovery(ctx, authArguments) - // Determine the host type and handle account ID / workspace ID accordingly - cfg := &config.Config{ - Host: authArguments.Host, - AccountID: authArguments.AccountID, - WorkspaceID: authArguments.WorkspaceID, - Experimental_IsUnifiedHost: authArguments.IsUnifiedHost, - } - - switch cfg.HostType() { - case config.AccountHost: - // Account host: prompt for account ID if not provided - if authArguments.AccountID == "" { - if existingProfile != nil && existingProfile.AccountID != "" { - authArguments.AccountID = existingProfile.AccountID - } else { - accountId, err := promptForAccountID(ctx) - if err != nil { - return err - } - authArguments.AccountID = accountId - } - } - case config.UnifiedHost: - // Unified host requires an account ID for OAuth URL construction. - // Workspace selection happens post-OAuth via promptForWorkspaceSelection. - if authArguments.AccountID == "" { - if existingProfile != nil && existingProfile.AccountID != "" { - authArguments.AccountID = existingProfile.AccountID - } else { - accountId, err := promptForAccountID(ctx) - if err != nil { - return err - } - authArguments.AccountID = accountId + if needsAccountIDPrompt(authArguments.Host, authArguments.DiscoveryURL) && authArguments.AccountID == "" { + if existingProfile != nil && existingProfile.AccountID != "" { + authArguments.AccountID = existingProfile.AccountID + } else { + accountId, err := promptForAccountID(ctx) + if err != nil { + return err } + authArguments.AccountID = accountId } - case config.WorkspaceHost: - // Regular workspace host: no additional prompts needed. - // If discovery already populated account_id/workspace_id, those are kept. - default: - return fmt.Errorf("unknown host type: %v", cfg.HostType()) } return nil } +// needsAccountIDPrompt reports whether the target host requires an account ID +// for OAuth URL construction. True for classic account hosts (accounts.*) and +// for unified hosts detected via account-scoped DiscoveryURL. +func needsAccountIDPrompt(host, discoveryURL string) bool { + canonicalHost := (&config.Config{Host: host}).CanonicalHostName() + if auth.IsClassicAccountHost(canonicalHost) { + return true + } + return auth.HasUnifiedHostSignal(discoveryURL) +} + // runHostDiscovery calls EnsureResolved() with a temporary config to fetch // .well-known/databricks-config from the host. Populates account_id and // workspace_id from discovery if not already set. @@ -544,7 +566,6 @@ func shouldUseDiscovery(hostFlag string, args []string, existingProfile *profile var discoveryIncompatibleFlags = []string{ "account-id", "workspace-id", - "experimental-is-unified-host", "configure-cluster", "configure-serverless", } @@ -561,50 +582,48 @@ func validateDiscoveryFlagCompatibility(cmd *cobra.Command) error { return nil } -// openURLSuppressingStderr opens a URL in the browser while suppressing stderr output. -// This prevents xdg-open error messages from being displayed to the user. -func openURLSuppressingStderr(url string) error { - // Save the original stderr from the browser package - originalStderr := browserpkg.Stderr - defer func() { - browserpkg.Stderr = originalStderr - }() - - // Redirect stderr to discard to suppress xdg-open errors - browserpkg.Stderr = io.Discard - - // Call the browser open function - return browserpkg.OpenURL(url) +// discoveryLoginInputs groups the dependencies of discoveryLogin. +// See https://google.github.io/styleguide/go/best-practices#option-structure. +type discoveryLoginInputs struct { + dc discoveryClient + profileName string + timeout time.Duration + scopes string + existingProfile *profile.Profile + browserFunc func(string) error + tokenCache cache.TokenCache + mode storage.StorageMode } // discoveryLogin runs the login.databricks.com discovery flow. The user // authenticates in the browser, selects a workspace, and the CLI receives // the workspace host from the OAuth callback's iss parameter. -func discoveryLogin(ctx context.Context, dc discoveryClient, profileName string, timeout time.Duration, scopes string, existingProfile *profile.Profile, browserFunc func(string) error) error { - arg, err := dc.NewOAuthArgument(profileName) +func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error { + arg, err := in.dc.NewOAuthArgument(in.profileName) if err != nil { return discoveryErr("setting up login.databricks.com", err) } - scopesList := splitScopes(scopes) - if len(scopesList) == 0 && existingProfile != nil && existingProfile.Scopes != "" { - scopesList = splitScopes(existingProfile.Scopes) + scopesList := splitScopes(in.scopes) + if len(scopesList) == 0 && in.existingProfile != nil && in.existingProfile.Scopes != "" { + scopesList = splitScopes(in.existingProfile.Scopes) } opts := []u2m.PersistentAuthOption{ u2m.WithOAuthArgument(arg), - u2m.WithBrowser(browserFunc), + u2m.WithBrowser(in.browserFunc), u2m.WithDiscoveryLogin(), + u2m.WithTokenCache(storage.WrapForOAuthArgument(in.tokenCache, in.mode, arg)), } if len(scopesList) > 0 { opts = append(opts, u2m.WithScopes(scopesList)) } // Apply timeout before creating PersistentAuth so Challenge() respects it. - ctx, cancel := context.WithTimeout(ctx, timeout) + ctx, cancel := context.WithTimeout(ctx, in.timeout) defer cancel() - persistentAuth, err := dc.NewPersistentAuth(ctx, opts...) + persistentAuth, err := in.dc.NewPersistentAuth(ctx, opts...) if err != nil { return discoveryErr("setting up login.databricks.com", err) } @@ -636,7 +655,7 @@ func discoveryLogin(ctx context.Context, dc discoveryClient, profileName string, return fmt.Errorf("retrieving token after login: %w", err) } - introspection, err := dc.IntrospectToken(ctx, discoveredHost, tok.AccessToken) + introspection, err := in.dc.IntrospectToken(ctx, discoveredHost, tok.AccessToken) if err != nil { log.Debugf(ctx, "token introspection failed (non-fatal): %v", err) } else { @@ -647,10 +666,10 @@ func discoveryLogin(ctx context.Context, dc discoveryClient, profileName string, accountID = introspection.AccountID } - if existingProfile != nil && existingProfile.AccountID != "" && introspection.AccountID != "" && - existingProfile.AccountID != introspection.AccountID { + if in.existingProfile != nil && in.existingProfile.AccountID != "" && introspection.AccountID != "" && + in.existingProfile.AccountID != introspection.AccountID { log.Warnf(ctx, "detected account ID %q differs from existing profile account ID %q", - introspection.AccountID, existingProfile.AccountID) + introspection.AccountID, in.existingProfile.AccountID) } } @@ -664,12 +683,12 @@ func discoveryLogin(ctx context.Context, dc discoveryClient, profileName string, clearKeys = append(clearKeys, "account_id", "workspace_id", - "experimental_is_unified_host", + databrickscfg.ExperimentalIsUnifiedHostKey, "cluster_id", "serverless_compute_id", ) err = databrickscfg.SaveToProfile(ctx, &config.Config{ - Profile: profileName, + Profile: in.profileName, Host: discoveredHost, AuthType: authTypeDatabricksCLI, AccountID: accountID, @@ -679,19 +698,19 @@ func discoveryLogin(ctx context.Context, dc discoveryClient, profileName string, }, clearKeys...) if err != nil { if configFile != "" { - return fmt.Errorf("saving profile %q to %s: %w", profileName, configFile, err) + return fmt.Errorf("saving profile %q to %s: %w", in.profileName, configFile, err) } - return fmt.Errorf("saving profile %q: %w", profileName, err) + return fmt.Errorf("saving profile %q: %w", in.profileName, err) } - cmdio.LogString(ctx, fmt.Sprintf("Profile %s was successfully saved", profileName)) + cmdio.LogString(ctx, fmt.Sprintf("Profile %s was successfully saved", in.profileName)) return nil } // splitScopes splits a comma-separated scopes string into a trimmed slice. func splitScopes(scopes string) []string { var result []string - for _, s := range strings.Split(scopes, ",") { + for s := range strings.SplitSeq(scopes, ",") { scope := strings.TrimSpace(s) if scope == "" { continue @@ -775,47 +794,24 @@ func promptForWorkspaceSelection(ctx context.Context, authArguments *auth.AuthAr // promptForWorkspaceID asks the user to manually enter a workspace ID. // Returns empty string if the user provides no input. func promptForWorkspaceID(ctx context.Context) (string, error) { - prompt := cmdio.Prompt(ctx) - prompt.Label = "Enter workspace ID (empty to skip)" - prompt.AllowEdit = true - result, err := prompt.Run() + result, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Enter workspace ID (empty to skip)", + }) if err != nil { return "", err } return strings.TrimSpace(result), nil } -// getBrowserFunc returns a function that opens the given URL in the browser. -// It respects the BROWSER environment variable: -// - empty string: uses the default browser -// - "none": prints the URL to stdout without opening a browser -// - custom command: executes the specified command with the URL as argument +// getBrowserFunc adapts libs/browser to the u2m.WithBrowser callback shape, +// overriding the BROWSER=none message with auth-specific phrasing. func getBrowserFunc(cmd *cobra.Command) func(url string) error { - browser := env.Get(cmd.Context(), "BROWSER") - switch browser { - case "": - return openURLSuppressingStderr - case "none": - return func(url string) error { - cmdio.LogString(cmd.Context(), "Please complete authentication by opening this link in your browser:\n"+url) + return func(url string) error { + ctx := cmd.Context() + if browser.IsDisabled(ctx) { + cmdio.LogString(ctx, "Please complete authentication by opening this link in your browser:\n"+url) return nil } - default: - return func(url string) error { - // Run the browser command via a shell. - // It can be a script or a binary and scripts cannot be executed directly on Windows. - e, err := exec.NewCommandExecutor(".") - if err != nil { - return err - } - - e.WithInheritOutput() - cmd, err := e.StartCommand(cmd.Context(), fmt.Sprintf("%q %q", browser, url)) - if err != nil { - return err - } - - return cmd.Wait() - } + return browser.Open(ctx, url) } } diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index 81924f027ad..d209c511154 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -20,12 +20,19 @@ import ( "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" "github.com/databricks/databricks-sdk-go/credentials/u2m" + "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/oauth2" ) +// newTestTokenCache returns an in-memory token cache for tests so that +// discoveryLogin and other login helpers don't touch ~/.databricks/token-cache.json. +func newTestTokenCache() cache.TokenCache { + return &inMemoryTokenCache{Tokens: map[string]*oauth2.Token{}} +} + // logBuffer is a thread-safe bytes.Buffer for capturing log output in tests. type logBuffer struct { mu sync.Mutex @@ -138,10 +145,10 @@ func TestSetHost(t *testing.T) { assert.Equal(t, "val from --host", authArguments.Host) // Test setting host from flag with trailing slash is stripped - authArguments.Host = "https://www.host1.com/" + authArguments.Host = "https://www.host1.test/" err = setHostAndAccountId(ctx, profile1, &authArguments, []string{}) assert.NoError(t, err) - assert.Equal(t, "https://www.host1.com", authArguments.Host) + assert.Equal(t, "https://www.host1.test", authArguments.Host) // Test setting host from argument authArguments.Host = "" @@ -151,21 +158,21 @@ func TestSetHost(t *testing.T) { // Test setting host from argument with trailing slash is stripped authArguments.Host = "" - err = setHostAndAccountId(ctx, profile1, &authArguments, []string{"https://www.host1.com/"}) + err = setHostAndAccountId(ctx, profile1, &authArguments, []string{"https://www.host1.test/"}) assert.NoError(t, err) - assert.Equal(t, "https://www.host1.com", authArguments.Host) + assert.Equal(t, "https://www.host1.test", authArguments.Host) // Test setting host from profile authArguments.Host = "" err = setHostAndAccountId(ctx, profile1, &authArguments, []string{}) assert.NoError(t, err) - assert.Equal(t, "https://www.host1.com", authArguments.Host) + assert.Equal(t, "https://www.host1.test", authArguments.Host) // Test setting host from profile authArguments.Host = "" err = setHostAndAccountId(ctx, profile2, &authArguments, []string{}) assert.NoError(t, err) - assert.Equal(t, "https://www.host2.com", authArguments.Host) + assert.Equal(t, "https://www.host2.test", authArguments.Host) // Test host is not set. Should prompt. authArguments.Host = "" @@ -211,10 +218,9 @@ func TestSetWorkspaceIDForUnifiedHost(t *testing.T) { // Test setting workspace-id from flag for unified host authArguments = auth.AuthArguments{ - Host: "https://unified.databricks.com", - AccountID: "test-unified-account", - WorkspaceID: "val from --workspace-id", - IsUnifiedHost: true, + Host: "https://unified.databricks.com", + AccountID: "test-unified-account", + WorkspaceID: "val from --workspace-id", } err := setHostAndAccountId(ctx, unifiedWorkspaceProfile, &authArguments, []string{}) assert.NoError(t, err) @@ -224,9 +230,8 @@ func TestSetWorkspaceIDForUnifiedHost(t *testing.T) { // Test setting workspace_id from profile for unified host authArguments = auth.AuthArguments{ - Host: "https://unified.databricks.com", - AccountID: "test-unified-account", - IsUnifiedHost: true, + Host: "https://unified.databricks.com", + AccountID: "test-unified-account", } err = setHostAndAccountId(ctx, unifiedWorkspaceProfile, &authArguments, []string{}) assert.NoError(t, err) @@ -236,9 +241,8 @@ func TestSetWorkspaceIDForUnifiedHost(t *testing.T) { // Test workspace_id is optional - should default to empty in non-interactive mode authArguments = auth.AuthArguments{ - Host: "https://unified.databricks.com", - AccountID: "test-unified-account", - IsUnifiedHost: true, + Host: "https://unified.databricks.com", + AccountID: "test-unified-account", } err = setHostAndAccountId(ctx, unifiedAccountProfile, &authArguments, []string{}) assert.NoError(t, err) @@ -248,9 +252,8 @@ func TestSetWorkspaceIDForUnifiedHost(t *testing.T) { // Test workspace_id is optional - should default to empty when no profile exists authArguments = auth.AuthArguments{ - Host: "https://unified.databricks.com", - AccountID: "test-unified-account", - IsUnifiedHost: true, + Host: "https://unified.databricks.com", + AccountID: "test-unified-account", } err = setHostAndAccountId(ctx, nil, &authArguments, []string{}) assert.NoError(t, err) @@ -272,14 +275,14 @@ func TestLoadProfileByNameAndClusterID(t *testing.T) { name: "cluster profile", profile: "cluster-profile", configFileEnv: "./testdata/.databrickscfg", - expectedHost: "https://www.host2.com", + expectedHost: "https://www.host2.test", expectedClusterID: "cluster-from-config", }, { name: "profile from home directory (existing)", profile: "cluster-profile", homeDirOverride: "testdata", - expectedHost: "https://www.host2.com", + expectedHost: "https://www.host2.test", expectedClusterID: "cluster-from-config", }, { @@ -392,6 +395,29 @@ func TestShouldUseDiscovery(t *testing.T) { } } +func TestNeedsAccountIDPrompt(t *testing.T) { + cases := []struct { + name string + host string + discoveryURL string + want bool + }{ + {name: "classic accounts host", host: "https://accounts.cloud.databricks.com", want: true}, + {name: "accounts-dod host", host: "https://accounts-dod.databricks.com", want: true}, + {name: "accounts host with path", host: "https://accounts.cloud.databricks.com/some/path", want: true}, + {name: "plain workspace host", host: "https://workspace.cloud.databricks.com"}, + {name: "account-scoped DiscoveryURL", host: "https://spog.cloud.databricks.com", discoveryURL: "https://spog.cloud.databricks.com/oidc/accounts/acct-123/.well-known/oauth-authorization-server", want: true}, + {name: "workspace-scoped DiscoveryURL", host: "https://workspace.cloud.databricks.com", discoveryURL: "https://workspace.cloud.databricks.com/oidc/.well-known/oauth-authorization-server"}, + {name: "workspace host no signals", host: "https://workspace.cloud.databricks.com"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got := needsAccountIDPrompt(tc.host, tc.discoveryURL) + assert.Equal(t, tc.want, got) + }) + } +} + func TestSplitScopes(t *testing.T) { tests := []struct { name string @@ -529,9 +555,8 @@ func TestSetHostAndAccountId_URLParamsOverrideProfile(t *testing.T) { // The profile has workspace_id=123456789, but the URL has ?o=99999. // URL params should win over profile values. args := auth.AuthArguments{ - Host: "https://unified.databricks.com?o=99999", - AccountID: "test-unified-account", - IsUnifiedHost: true, + Host: "https://unified.databricks.com?o=99999", + AccountID: "test-unified-account", } err := setHostAndAccountId(ctx, unifiedWorkspaceProfile, &args, []string{}) assert.NoError(t, err) @@ -558,12 +583,6 @@ func TestValidateDiscoveryFlagCompatibility(t *testing.T) { flagVal: "12345", wantErr: "--workspace-id requires --host to be specified", }, - { - name: "experimental-is-unified-host is incompatible", - setFlag: "experimental-is-unified-host", - flagVal: "true", - wantErr: "--experimental-is-unified-host requires --host to be specified", - }, { name: "configure-cluster is incompatible", setFlag: "configure-cluster", @@ -585,7 +604,6 @@ func TestValidateDiscoveryFlagCompatibility(t *testing.T) { cmd := &cobra.Command{} cmd.Flags().String("account-id", "", "") cmd.Flags().String("workspace-id", "", "") - cmd.Flags().Bool("experimental-is-unified-host", false, "") cmd.Flags().Bool("configure-cluster", false, "") cmd.Flags().Bool("configure-serverless", false, "") @@ -623,7 +641,14 @@ func TestDiscoveryLogin_IntrospectionFailureStillSavesProfile(t *testing.T) { } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "all-apis, ,sql,", nil, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + scopes: "all-apis, ,sql,", + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) assert.Equal(t, "https://workspace.example.com", dc.introspectHost) @@ -671,7 +696,14 @@ func TestDiscoveryLogin_AccountIDMismatchWarning(t *testing.T) { AccountID: "old-account-id", } - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) // Verify warning about mismatched account IDs was logged. @@ -719,7 +751,14 @@ func TestDiscoveryLogin_NoWarningWhenAccountIDsMatch(t *testing.T) { AccountID: "same-account-id", } - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) // No warning should be logged when account IDs match. @@ -739,7 +778,13 @@ func TestDiscoveryLogin_EmptyDiscoveredHostReturnsError(t *testing.T) { } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", nil, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.Error(t, err) assert.Contains(t, err.Error(), "no workspace host was discovered") } @@ -771,7 +816,14 @@ func TestDiscoveryLogin_ReloginPreservesExistingProfileScopes(t *testing.T) { // No --scopes flag (empty string), should fall back to existing profile scopes. ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) @@ -808,7 +860,15 @@ func TestDiscoveryLogin_ExplicitScopesOverrideExistingProfile(t *testing.T) { // Explicit --scopes flag should override existing profile scopes. ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "all-apis", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + scopes: "all-apis", + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) @@ -848,7 +908,13 @@ func TestDiscoveryLogin_SPOGHostPopulatesAccountIDFromDiscovery(t *testing.T) { } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", nil, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) @@ -883,7 +949,13 @@ func TestDiscoveryLogin_IntrospectionFallsBackWhenDiscoveryFails(t *testing.T) { } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", nil, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) @@ -924,15 +996,21 @@ auth_type = databricks-cli } existingProfile := &profile.Profile{ - Name: "DISCOVERY", - Host: "https://old-unified.databricks.com", - AccountID: "old-account", - WorkspaceID: "999999", - IsUnifiedHost: true, + Name: "DISCOVERY", + Host: "https://old-unified.databricks.com", + AccountID: "old-account", + WorkspaceID: "999999", } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) @@ -942,7 +1020,11 @@ auth_type = databricks-cli // Stale routing fields must be cleared. assert.Empty(t, savedProfile.AccountID, "stale account_id should be cleared") assert.Empty(t, savedProfile.WorkspaceID, "stale workspace_id should be cleared on introspection failure") - assert.False(t, savedProfile.IsUnifiedHost, "stale experimental_is_unified_host should be cleared") + + // Verify the experimental_is_unified_host INI key was also cleared from disk. + raw, err := os.ReadFile(configPath) + require.NoError(t, err) + assert.NotContains(t, string(raw), "experimental_is_unified_host") } func TestDiscoveryLogin_IntrospectionWritesFreshWorkspaceID(t *testing.T) { @@ -982,7 +1064,14 @@ auth_type = databricks-cli } ctx, _ := cmdio.NewTestContextWithStdout(t.Context()) - err = discoveryLogin(ctx, dc, "DISCOVERY", time.Second, "", existingProfile, func(string) error { return nil }) + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + existingProfile: existingProfile, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) require.NoError(t, err) savedProfile, err := loadProfileByName(ctx, "DISCOVERY", profile.DefaultProfiler) diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index 3beeeefec9b..cba94fd2752 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -22,6 +22,7 @@ import ( "strings" "github.com/databricks/cli/libs/auth" + "github.com/databricks/cli/libs/auth/storage" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" @@ -118,23 +119,25 @@ to specify it explicitly. if err != nil { return err } - selected, err := profile.SelectProfile(ctx, profile.SelectConfig{ - Label: "Select a profile to log out of", - Profiles: allProfiles, - StartInSearchMode: len(allProfiles) > 5, - ActiveTemplate: `▸ {{.PaddedName | bold}}{{if .AccountID}} (account: {{.AccountID}}){{else}} ({{.Host}}){{end}}`, - InactiveTemplate: ` {{.PaddedName}}{{if .AccountID}} (account: {{.AccountID | faint}}){{else}} ({{.Host | faint}}){{end}}`, - SelectedTemplate: `{{ "Selected profile" | faint }}: {{ .Name | bold }}`, + currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, env.Get(ctx, "DATABRICKS_CONFIG_FILE")) + result, selected, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{ + Label: "Select a profile to log out of", + SelectedNoun: "Selected profile", + Default: currentDefault, }) if err != nil { return err } + // Without IncludeExtras, the picker only returns profile selections. + if result != profilePickerProfile { + return fmt.Errorf("unexpected picker result: %v", result) + } profileName = selected } - tokenCache, err := cache.NewFileTokenCache() + tokenCache, _, err := storage.ResolveCache(ctx, "") if err != nil { - return fmt.Errorf("failed to open token cache, please check if the file version is up-to-date and that the file is not corrupted: %w", err) + return fmt.Errorf("failed to open token cache: %w", err) } return runLogout(ctx, logoutArgs{ @@ -302,13 +305,12 @@ func hostCacheKeyAndMatchFn(p profile.Profile) (string, profile.ProfileMatchFunc // Use ToOAuthArgument to derive the host-based cache key via the same // routing logic the SDK used when the token was written during login. // This includes a .well-known/databricks-config call that distinguishes - // classic workspace hosts from SPOG hosts — a distinction that cannot + // classic workspace hosts from SPOG hosts, a distinction that cannot // be made from the profile fields alone. arg, err := (auth.AuthArguments{ - Host: p.Host, - AccountID: p.AccountID, - WorkspaceID: p.WorkspaceID, - IsUnifiedHost: p.IsUnifiedHost, + Host: p.Host, + AccountID: p.AccountID, + WorkspaceID: p.WorkspaceID, // Profile is deliberately empty so GetCacheKey returns the host-based // key rather than the profile name. // DiscoveryURL is left empty to force a fresh .well-known resolution diff --git a/cmd/auth/logout_test.go b/cmd/auth/logout_test.go index ccf5ca64854..c9007e4a5f7 100644 --- a/cmd/auth/logout_test.go +++ b/cmd/auth/logout_test.go @@ -2,6 +2,7 @@ package auth import ( "encoding/json" + "maps" "net/http" "net/http/httptest" "os" @@ -40,38 +41,28 @@ host = https://accounts.cloud.databricks.com account_id = abc123 auth_type = databricks-cli -[my-unified] -host = https://unified.cloud.databricks.com -account_id = def456 -experimental_is_unified_host = true -auth_type = databricks-cli - [my-m2m] host = https://my-m2m.cloud.databricks.com token = dev-token ` var logoutTestTokensCacheConfig = map[string]*oauth2.Token{ - "my-workspace": {AccessToken: "shared-workspace-token"}, - "shared-workspace": {AccessToken: "shared-workspace-token"}, - "my-unique-workspace": {AccessToken: "my-unique-workspace-token"}, - "my-workspace-stale-account": {AccessToken: "stale-account-token"}, - "my-account": {AccessToken: "my-account-token"}, - "my-unified": {AccessToken: "my-unified-token"}, + "my-workspace": {AccessToken: "shared-workspace-token"}, + "shared-workspace": {AccessToken: "shared-workspace-token"}, + "my-unique-workspace": {AccessToken: "my-unique-workspace-token"}, + "my-workspace-stale-account": {AccessToken: "stale-account-token"}, + "my-account": {AccessToken: "my-account-token"}, "https://my-workspace.cloud.databricks.com": {AccessToken: "shared-workspace-host-token"}, "https://my-unique-workspace.cloud.databricks.com": {AccessToken: "unique-workspace-host-token"}, "https://stale-account.cloud.databricks.com": {AccessToken: "stale-account-host-token"}, "https://accounts.cloud.databricks.com/oidc/accounts/abc123": {AccessToken: "account-host-token"}, - "https://unified.cloud.databricks.com/oidc/accounts/def456": {AccessToken: "unified-host-token"}, "my-m2m": {AccessToken: "m2m-service-token"}, "https://my-m2m.cloud.databricks.com": {AccessToken: "m2m-host-token"}, } func copyTokens(src map[string]*oauth2.Token) map[string]*oauth2.Token { dst := make(map[string]*oauth2.Token, len(src)) - for k, v := range src { - dst[k] = v - } + maps.Copy(dst, src) return dst } @@ -121,13 +112,6 @@ func TestLogout(t *testing.T) { isSharedKey: false, autoApprove: true, }, - { - name: "existing unified profile", - profileName: "my-unified", - hostBasedKey: "https://unified.cloud.databricks.com/oidc/accounts/def456", - isSharedKey: false, - autoApprove: true, - }, { name: "existing workspace profile without auto-approve in non-interactive mode", profileName: "my-workspace", @@ -164,14 +148,6 @@ func TestLogout(t *testing.T) { autoApprove: true, deleteProfile: true, }, - { - name: "delete unified profile", - profileName: "my-unified", - hostBasedKey: "https://unified.cloud.databricks.com/oidc/accounts/def456", - isSharedKey: false, - autoApprove: true, - deleteProfile: true, - }, { name: "do not delete m2m profile tokens", profileName: "my-m2m", @@ -440,16 +416,6 @@ func TestHostCacheKeyAndMatchFn(t *testing.T) { }, wantKey: "https://accounts.cloud.databricks.com/oidc/accounts/abc123", }, - { - name: "unified host with flag", - profile: profile.Profile{ - Name: "unified", - Host: wsServer.URL, - AccountID: "def456", - IsUnifiedHost: true, - }, - wantKey: wsServer.URL + "/oidc/accounts/def456", - }, { name: "SPOG profile routes to account key via discovery", profile: profile.Profile{ diff --git a/cmd/auth/profile_picker.go b/cmd/auth/profile_picker.go new file mode 100644 index 00000000000..01279171ac7 --- /dev/null +++ b/cmd/auth/profile_picker.go @@ -0,0 +1,138 @@ +package auth + +import ( + "context" + "errors" + "strings" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/databrickscfg/profile" +) + +// profilePickerResult represents the user's choice from pickAuthProfile. +type profilePickerResult int + +const ( + profilePickerProfile profilePickerResult = iota // an existing profile was picked + profilePickerCreateNew // user chose "Create a new profile" + profilePickerEnterHost // user chose "Enter a host URL manually" +) + +const ( + profilePickerCreateNewLabel = "Create a new profile" + profilePickerEnterHostLabel = "Enter a host URL manually" +) + +// profilePickerOptions configures pickAuthProfile. +type profilePickerOptions struct { + // Label shown above the picker. + Label string + + // SelectedNoun is the noun shown after selection ("Using profile", + // "Selected profile", "Default profile"). Defaults to "Using profile". + SelectedNoun string + + // Default is the name of the default profile. When set, it is moved to the + // top of the list and decorated with "[default]". + Default string + + // IncludeExtras appends "Create a new profile" and "Enter a host URL + // manually" entries after the profile list. Picker action entries are + // shown even when the profile list is empty. + IncludeExtras bool +} + +// pickerItem is a single entry rendered by the picker. It can be either a real +// profile or one of the extra action entries (Create new / Enter host). +type pickerItem struct { + Name string + Host string + AccountID string + IsDefault bool + + // IsExtra distinguishes action entries (Create new / Enter host) from + // real profiles, so a profile that happens to share a label name still + // resolves correctly. + IsExtra bool + Extra profilePickerResult +} + +// buildPickerItems returns the items shown by pickAuthProfile, with the default +// profile moved to the top and the extras appended (when requested). +func buildPickerItems(profiles profile.Profiles, defaultName string, includeExtras bool) []pickerItem { + defaultIdx := -1 + if defaultName != "" { + for i, p := range profiles { + if p.Name == defaultName { + defaultIdx = i + break + } + } + } + + itemFor := func(p profile.Profile, isDefault bool) pickerItem { + return pickerItem{ + Name: p.Name, + Host: p.Host, + AccountID: p.AccountID, + IsDefault: isDefault, + } + } + + items := make([]pickerItem, 0, len(profiles)+2) + if defaultIdx >= 0 { + items = append(items, itemFor(profiles[defaultIdx], true)) + } + for i, p := range profiles { + if i == defaultIdx { + continue + } + items = append(items, itemFor(p, false)) + } + if includeExtras { + items = append(items, + pickerItem{Name: profilePickerCreateNewLabel, IsExtra: true, Extra: profilePickerCreateNew}, + pickerItem{Name: profilePickerEnterHostLabel, IsExtra: true, Extra: profilePickerEnterHost}, + ) + } + return items +} + +// pickAuthProfile shows the auth profile picker and returns the user's choice. +// When the result is profilePickerProfile, the second return value is the +// selected profile name. For the other results it is empty. +func pickAuthProfile(ctx context.Context, profiles profile.Profiles, opts profilePickerOptions) (profilePickerResult, string, error) { + items := buildPickerItems(profiles, opts.Default, opts.IncludeExtras) + if len(items) == 0 { + return 0, "", errors.New("no profiles configured. Run 'databricks auth login' to create a profile") + } + noun := opts.SelectedNoun + if noun == "" { + noun = "Using profile" + } + + idx, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{ + Label: opts.Label, + Items: items, + StartInSearchMode: len(profiles) > 5, + Searcher: func(input string, index int) bool { + input = strings.ToLower(input) + return strings.Contains(strings.ToLower(items[index].Name), input) || + strings.Contains(strings.ToLower(items[index].Host), input) || + strings.Contains(strings.ToLower(items[index].AccountID), input) + }, + LabelTemplate: "{{ . | faint }}", + Active: `▸ {{.Name | bold}}{{if .IsDefault}} {{ "[default]" | green }}{{end}}{{if .AccountID}} (account: {{.AccountID|faint}}){{else if .Host}} ({{.Host|faint}}){{end}}`, + Inactive: ` {{.Name}}{{if .IsDefault}} [default]{{end}}{{if .AccountID}} (account: {{.AccountID|faint}}){{else if .Host}} ({{.Host|faint}}){{end}}`, + Selected: `{{ "` + noun + `" | faint }}: {{ .Name | bold }}`, + }) + if err != nil { + return 0, "", err + } + + picked := items[idx] + if picked.IsExtra { + return picked.Extra, "", nil + } + return profilePickerProfile, picked.Name, nil +} diff --git a/cmd/auth/profile_picker_test.go b/cmd/auth/profile_picker_test.go new file mode 100644 index 00000000000..dba2f989c86 --- /dev/null +++ b/cmd/auth/profile_picker_test.go @@ -0,0 +1,91 @@ +package auth + +import ( + "testing" + + "github.com/databricks/cli/libs/databrickscfg/profile" + "github.com/stretchr/testify/assert" +) + +func TestBuildPickerItems(t *testing.T) { + profiles := profile.Profiles{ + {Name: "alpha", Host: "https://alpha.cloud.databricks.example"}, + {Name: "bravo", Host: "https://bravo.cloud.databricks.example"}, + {Name: "charlie", Host: "https://charlie.cloud.databricks.example"}, + } + + cases := []struct { + name string + defaultName string + includeExtras bool + wantNames []string + wantDefault string + wantExtras []profilePickerResult + }{ + { + name: "no default no extras", + wantNames: []string{"alpha", "bravo", "charlie"}, + wantDefault: "", + }, + { + name: "default moves to top", + defaultName: "bravo", + wantNames: []string{"bravo", "alpha", "charlie"}, + wantDefault: "bravo", + }, + { + name: "extras appended after profiles", + includeExtras: true, + wantNames: []string{"alpha", "bravo", "charlie", profilePickerCreateNewLabel, profilePickerEnterHostLabel}, + wantExtras: []profilePickerResult{profilePickerCreateNew, profilePickerEnterHost}, + }, + { + name: "default first, then extras at the bottom", + defaultName: "charlie", + includeExtras: true, + wantNames: []string{"charlie", "alpha", "bravo", profilePickerCreateNewLabel, profilePickerEnterHostLabel}, + wantDefault: "charlie", + wantExtras: []profilePickerResult{profilePickerCreateNew, profilePickerEnterHost}, + }, + { + name: "default not in profiles is ignored", + defaultName: "missing", + wantNames: []string{"alpha", "bravo", "charlie"}, + wantDefault: "", + }, + } + + t.Run("empty profiles with extras shows only extras", func(t *testing.T) { + items := buildPickerItems(profile.Profiles{}, "", true) + assert.Equal(t, []string{profilePickerCreateNewLabel, profilePickerEnterHostLabel}, namesOf(items)) + }) + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + items := buildPickerItems(profiles, tc.defaultName, tc.includeExtras) + + gotDefault := "" + var gotExtras []profilePickerResult + for _, it := range items { + if it.IsDefault { + assert.Empty(t, gotDefault) + gotDefault = it.Name + } + if it.IsExtra { + gotExtras = append(gotExtras, it.Extra) + } + } + assert.Equal(t, tc.wantNames, namesOf(items)) + assert.Equal(t, tc.wantDefault, gotDefault) + assert.Equal(t, tc.wantExtras, gotExtras) + }) + } +} + +func namesOf(items []pickerItem) []string { + names := make([]string, len(items)) + for i, it := range items { + names[i] = it.Name + } + return names +} diff --git a/cmd/auth/profiles.go b/cmd/auth/profiles.go index 38ba3599fcd..51c397a9ea9 100644 --- a/cmd/auth/profiles.go +++ b/cmd/auth/profiles.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" @@ -56,7 +57,12 @@ func (c *profileMetadata) Load(ctx context.Context, configFilePath string, skipV return } - switch cfg.ConfigType() { + configType := auth.ResolveConfigType(cfg) + if configType != cfg.ConfigType() { + log.Debugf(ctx, "Profile %q: overrode config type from %s to %s (SPOG host)", c.Name, cfg.ConfigType(), configType) + } + + switch configType { case config.AccountConfig: a, err := databricks.NewAccountClient((*databricks.Config)(cfg)) if err != nil { diff --git a/cmd/auth/profiles_test.go b/cmd/auth/profiles_test.go index afd7b0b548d..59803e210cf 100644 --- a/cmd/auth/profiles_test.go +++ b/cmd/auth/profiles_test.go @@ -1,6 +1,10 @@ package auth import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" "path/filepath" "runtime" "testing" @@ -74,3 +78,179 @@ func TestProfilesDefaultMarker(t *testing.T) { require.NoError(t, err) assert.Equal(t, "profile-a", defaultProfile) } + +// newSPOGServer creates a mock SPOG server that returns account-scoped OIDC. +// It serves both validation endpoints since SPOG workspace profiles (with a +// real workspace_id) need CurrentUser.Me, while account profiles need +// Workspaces.List. The workspace-only newWorkspaceServer omits the account +// endpoint to prove routing correctness for non-SPOG hosts. +func newSPOGServer(t *testing.T, accountID string) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/.well-known/databricks-config": + _ = json.NewEncoder(w).Encode(map[string]any{ + "account_id": accountID, + "oidc_endpoint": r.Host + "/oidc/accounts/" + accountID, + }) + case "/api/2.0/accounts/" + accountID + "/workspaces": + _ = json.NewEncoder(w).Encode([]map[string]any{}) + case "/api/2.0/preview/scim/v2/Me": + // SPOG workspace profiles also need CurrentUser.Me to succeed. + _ = json.NewEncoder(w).Encode(map[string]any{"userName": "test-user"}) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(server.Close) + return server +} + +// newWorkspaceServer creates a mock workspace server that returns workspace-scoped +// OIDC and only serves the workspace validation endpoint. The account validation +// endpoint returns 404 to prove the workspace path was taken. +func newWorkspaceServer(t *testing.T, accountID string) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/.well-known/databricks-config": + _ = json.NewEncoder(w).Encode(map[string]any{ + "account_id": accountID, + "oidc_endpoint": r.Host + "/oidc", + }) + case "/api/2.0/preview/scim/v2/Me": + _ = json.NewEncoder(w).Encode(map[string]any{"userName": "test-user"}) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(server.Close) + return server +} + +func TestProfileLoadSPOGConfigType(t *testing.T) { + spogServer := newSPOGServer(t, "spog-acct") + wsServer := newWorkspaceServer(t, "ws-acct") + + cases := []struct { + name string + host string + accountID string + workspaceID string + wantValid bool + }{ + { + name: "SPOG account profile validated as account", + host: spogServer.URL, + accountID: "spog-acct", + wantValid: true, + }, + { + name: "SPOG workspace profile validated as workspace", + host: spogServer.URL, + accountID: "spog-acct", + workspaceID: "ws-123", + wantValid: true, + }, + { + name: "SPOG profile with workspace_id=none validated as account", + host: spogServer.URL, + accountID: "spog-acct", + workspaceID: "none", + wantValid: true, + }, + { + name: "classic workspace with account_id from discovery stays workspace", + host: wsServer.URL, + accountID: "ws-acct", + wantValid: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + configFile := filepath.Join(dir, ".databrickscfg") + t.Setenv("HOME", dir) + if runtime.GOOS == "windows" { + t.Setenv("USERPROFILE", dir) + } + + content := "[test-profile]\nhost = " + tc.host + "\ntoken = test-token\n" + if tc.accountID != "" { + content += "account_id = " + tc.accountID + "\n" + } + if tc.workspaceID != "" { + content += "workspace_id = " + tc.workspaceID + "\n" + } + require.NoError(t, os.WriteFile(configFile, []byte(content), 0o600)) + + p := &profileMetadata{ + Name: "test-profile", + Host: tc.host, + AccountID: tc.accountID, + } + p.Load(t.Context(), configFile, false) + + assert.Equal(t, tc.wantValid, p.Valid, "Valid mismatch") + assert.NotEmpty(t, p.Host, "Host should be set") + assert.NotEmpty(t, p.AuthType, "AuthType should be set") + }) + } +} + +func TestClassicAccountsHostConfigType(t *testing.T) { + // Classic accounts.* hosts can't be tested through Load() because httptest + // generates 127.0.0.1 URLs. Verify directly that ConfigType() classifies + // them as AccountConfig, so the SPOG override is never needed. + cfg := &config.Config{ + Host: "https://accounts.cloud.databricks.com", + AccountID: "acct-123", + } + assert.Equal(t, config.AccountConfig, cfg.ConfigType()) + + // Even with SPOG-like discovery data, accounts.* stays AccountConfig. + cfg.DiscoveryURL = "https://accounts.cloud.databricks.com/oidc/accounts/acct-123/.well-known/oauth-authorization-server" + assert.Equal(t, config.AccountConfig, cfg.ConfigType()) +} + +func TestProfileLoadNoDiscoveryStaysWorkspace(t *testing.T) { + // When .well-known returns 404 and the unified-host fallback is false, + // the SPOG override should NOT trigger even if account_id is set. The + // profile should stay WorkspaceConfig and validate via CurrentUser.Me. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/.well-known/databricks-config": + w.WriteHeader(http.StatusNotFound) + case "/api/2.0/preview/scim/v2/Me": + _ = json.NewEncoder(w).Encode(map[string]any{"userName": "test-user"}) + default: + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(server.Close) + + dir := t.TempDir() + configFile := filepath.Join(dir, ".databrickscfg") + t.Setenv("HOME", dir) + if runtime.GOOS == "windows" { + t.Setenv("USERPROFILE", dir) + } + + content := "[ws-profile]\nhost = " + server.URL + "\ntoken = test-token\naccount_id = some-acct\n" + require.NoError(t, os.WriteFile(configFile, []byte(content), 0o600)) + + p := &profileMetadata{ + Name: "ws-profile", + Host: server.URL, + AccountID: "some-acct", + } + p.Load(t.Context(), configFile, false) + + assert.True(t, p.Valid, "should validate as workspace when discovery is unavailable") + assert.NotEmpty(t, p.Host) + assert.Equal(t, "pat", p.AuthType) +} diff --git a/cmd/auth/switch.go b/cmd/auth/switch.go index 12cfa72a64a..c91894fe85a 100644 --- a/cmd/auth/switch.go +++ b/cmd/auth/switch.go @@ -1,16 +1,13 @@ package auth import ( - "context" "errors" "fmt" - "strings" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" "github.com/databricks/cli/libs/env" - "github.com/manifoldco/promptui" "github.com/spf13/cobra" ) @@ -46,11 +43,23 @@ to see which profile is currently the default.`, } currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, configFile) - selectedName, err := promptForSwitchProfile(ctx, allProfiles, currentDefault) + label := "Select a profile to set as default" + if currentDefault != "" { + label = fmt.Sprintf("Current default: %s. Select a new default", currentDefault) + } + result, selected, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{ + Label: label, + SelectedNoun: "Default profile", + Default: currentDefault, + }) if err != nil { return err } - profileName = selectedName + // Without IncludeExtras, the picker only returns profile selections. + if result != profilePickerProfile { + return fmt.Errorf("unexpected picker result: %v", result) + } + profileName = selected } else { // Validate the profile exists. profiles, err := profile.DefaultProfiler.LoadProfiles(ctx, profile.WithName(profileName)) @@ -73,39 +82,3 @@ to see which profile is currently the default.`, return cmd } - -// promptForSwitchProfile shows an interactive profile picker for the switch command. -// Reuses profileSelectItem from token.go for consistent display. -func promptForSwitchProfile(ctx context.Context, profiles profile.Profiles, currentDefault string) (string, error) { - items := make([]profileSelectItem, 0, len(profiles)) - for _, p := range profiles { - items = append(items, profileSelectItem{Name: p.Name, Host: p.Host}) - } - - label := "Select a profile to set as default" - if currentDefault != "" { - label = fmt.Sprintf("Current default: %s. Select a new default", currentDefault) - } - - i, _, err := cmdio.RunSelect(ctx, &promptui.Select{ - Label: label, - Items: items, - StartInSearchMode: len(profiles) > 5, - Searcher: func(input string, index int) bool { - input = strings.ToLower(input) - name := strings.ToLower(items[index].Name) - host := strings.ToLower(items[index].Host) - return strings.Contains(name, input) || strings.Contains(host, input) - }, - Templates: &promptui.SelectTemplates{ - Label: "{{ . | faint }}", - Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`, - Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`, - Selected: `{{ "Default profile" | faint }}: {{ .Name | bold }}`, - }, - }) - if err != nil { - return "", err - } - return profiles[i].Name, nil -} diff --git a/cmd/auth/testdata/.databrickscfg b/cmd/auth/testdata/.databrickscfg index fe836a53b4c..acd227d515e 100644 --- a/cmd/auth/testdata/.databrickscfg +++ b/cmd/auth/testdata/.databrickscfg @@ -1,15 +1,15 @@ [profile-1] -host = https://www.host1.com +host = https://www.host1.test [profile-2] -host = https://www.host2.com +host = https://www.host2.test [account-profile] host = https://accounts.cloud.databricks.com account_id = id-from-profile [cluster-profile] -host = https://www.host2.com +host = https://www.host2.test cluster_id = cluster-from-config [invalid-profile] diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 592caf444ac..8c439f6e318 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -11,6 +11,8 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/auth" + "github.com/databricks/cli/libs/auth/storage" + "github.com/databricks/cli/libs/browser" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" @@ -19,7 +21,6 @@ import ( "github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/credentials/u2m" "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" - "github.com/manifoldco/promptui" "github.com/spf13/cobra" "golang.org/x/oauth2" ) @@ -29,29 +30,6 @@ func helpfulError(ctx context.Context, profile string, persistentAuth u2m.OAuthA return fmt.Sprintf("Try logging in again with `%s` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new", loginMsg) } -// profileSelectionResult represents the user's choice from the interactive -// profile picker. -type profileSelectionResult int - -const ( - profileSelected profileSelectionResult = iota // User picked a profile - enterHostSelected // User chose "Enter a host URL manually" - createNewSelected // User chose "Create a new profile" -) - -// applyUnifiedHostFlags copies unified host fields from the profile to the -// auth arguments when they are not already set. WorkspaceID is NOT copied -// here; it is deferred to setHostAndAccountId() so that URL query params -// (?o=...) can override stale profile values. -func applyUnifiedHostFlags(p *profile.Profile, args *auth.AuthArguments) { - if p == nil { - return - } - if !args.IsUnifiedHost && p.IsUnifiedHost { - args.IsUnifiedHost = p.IsUnifiedHost - } -} - func newTokenCommand(authArguments *auth.AuthArguments) *cobra.Command { cmd := &cobra.Command{ Use: "token [PROFILE]", @@ -77,6 +55,11 @@ and secret is not supported.`, ctx := cmd.Context() profileName := cmd.Flag("profile").Value.String() + tokenCache, mode, err := storage.ResolveCache(ctx, "") + if err != nil { + return err + } + t, err := loadToken(ctx, loadTokenArgs{ authArguments: authArguments, profileName: profileName, @@ -84,6 +67,8 @@ and secret is not supported.`, tokenTimeout: tokenTimeout, forceRefresh: forceRefresh, profiler: profile.DefaultProfiler, + tokenCache: tokenCache, + mode: mode, persistentAuthOpts: nil, }) if err != nil { @@ -132,6 +117,15 @@ type loadTokenArgs struct { // profiler is the profiler to use for reading the host and account ID from the .databrickscfg file. profiler profile.Profiler + // tokenCache is the underlying TokenCache used for OAuth tokens. The caller is + // responsible for construction so that tests can substitute an in-memory cache. + tokenCache cache.TokenCache + + // mode is the resolved storage mode. When set to StorageModePlaintext, + // login paths mirror freshly minted tokens under the legacy host-based + // key so older SDKs that still look up by host continue to find them. + mode storage.StorageMode + // persistentAuthOpts are the options to pass to the persistent auth client. persistentAuthOpts []u2m.PersistentAuthOption } @@ -177,18 +171,15 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { return nil, err } - applyUnifiedHostFlags(existingProfile, args.authArguments) - // When no explicit profile, host, or positional args are provided, attempt to // resolve the target through environment variables or interactive profile selection. if args.profileName == "" && args.authArguments.Host == "" && len(args.args) == 0 { var resolvedProfile string - resolvedProfile, existingProfile, err = resolveNoArgsToken(ctx, args.profiler, args.authArguments) + resolvedProfile, existingProfile, err = resolveNoArgsToken(ctx, args.profiler, args.authArguments, args.tokenCache, args.mode) if err != nil { return nil, err } args.profileName = resolvedProfile - applyUnifiedHostFlags(existingProfile, args.authArguments) } err = setHostAndAccountId(ctx, existingProfile, args.authArguments, args.args) @@ -272,7 +263,8 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { if err != nil { return nil, err } - allArgs := append(args.persistentAuthOpts, u2m.WithOAuthArgument(oauthArgument)) + allArgs := append([]u2m.PersistentAuthOption{u2m.WithTokenCache(args.tokenCache)}, args.persistentAuthOpts...) + allArgs = append(allArgs, u2m.WithOAuthArgument(oauthArgument)) persistentAuth, err := u2m.NewPersistentAuth(ctx, allArgs...) if err != nil { helpMsg := helpfulError(ctx, args.profileName, oauthArgument) @@ -313,7 +305,7 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { // // Returns the resolved profile name and profile (if any). The host and related // fields on authArgs are updated in place when resolved via environment variables. -func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs *auth.AuthArguments) (string, *profile.Profile, error) { +func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs *auth.AuthArguments, tokenCache cache.TokenCache, mode storage.StorageMode) (string, *profile.Profile, error) { // Step 1: Try DATABRICKS_HOST env var (highest priority). if envHost := env.Get(ctx, "DATABRICKS_HOST"); envHost != "" { authArgs.Host = envHost @@ -323,9 +315,6 @@ func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs if v := env.Get(ctx, "DATABRICKS_WORKSPACE_ID"); v != "" { authArgs.WorkspaceID = v } - if ok, _ := env.GetBool(ctx, "DATABRICKS_EXPERIMENTAL_IS_UNIFIED_HOST"); ok { - authArgs.IsUnifiedHost = true - } return "", nil, nil } @@ -353,16 +342,21 @@ func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs } // Interactive: show profile picker. - result, selectedName, err := promptForProfileSelection(ctx, allProfiles) + currentDefault, _ := databrickscfg.GetDefaultProfile(ctx, env.Get(ctx, "DATABRICKS_CONFIG_FILE")) + result, selectedName, err := pickAuthProfile(ctx, allProfiles, profilePickerOptions{ + Label: "Select a profile", + Default: currentDefault, + IncludeExtras: true, + }) if err != nil { return "", nil, err } switch result { - case enterHostSelected: + case profilePickerEnterHost: // Fall through — setHostAndAccountId will prompt for the host. return "", nil, nil - case createNewSelected: - return runInlineLogin(ctx, profiler) + case profilePickerCreateNew: + return runInlineLogin(ctx, profiler, tokenCache, mode) default: p, err := loadProfileByName(ctx, selectedName, profiler) if err != nil { @@ -372,61 +366,10 @@ func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs } } -// profileSelectItem is used by promptForProfileSelection to render both -// regular profiles and special action options in the same select list. -type profileSelectItem struct { - Name string - Host string -} - -// promptForProfileSelection shows a promptui select list with all configured -// profiles plus "Enter a host URL" and "Create a new profile" options. -// Returns the selection type and, when a profile is selected, its name. -func promptForProfileSelection(ctx context.Context, profiles profile.Profiles) (profileSelectionResult, string, error) { - items := make([]profileSelectItem, 0, len(profiles)+2) - for _, p := range profiles { - items = append(items, profileSelectItem{Name: p.Name, Host: p.Host}) - } - createProfileIdx := len(items) - items = append(items, profileSelectItem{Name: "Create a new profile"}) - enterHostIdx := len(items) - items = append(items, profileSelectItem{Name: "Enter a host URL manually"}) - - i, _, err := cmdio.RunSelect(ctx, &promptui.Select{ - Label: "Select a profile", - Items: items, - StartInSearchMode: len(profiles) > 5, - Searcher: func(input string, index int) bool { - input = strings.ToLower(input) - name := strings.ToLower(items[index].Name) - host := strings.ToLower(items[index].Host) - return strings.Contains(name, input) || strings.Contains(host, input) - }, - Templates: &promptui.SelectTemplates{ - Label: "{{ . | faint }}", - Active: `{{.Name | bold}}{{if .Host}} ({{.Host|faint}}){{end}}`, - Inactive: `{{.Name}}{{if .Host}} ({{.Host}}){{end}}`, - Selected: `{{ "Using profile" | faint }}: {{ .Name | bold }}`, - }, - }) - if err != nil { - return 0, "", err - } - - switch i { - case enterHostIdx: - return enterHostSelected, "", nil - case createProfileIdx: - return createNewSelected, "", nil - default: - return profileSelected, profiles[i].Name, nil - } -} - // runInlineLogin runs a minimal interactive login flow: prompts for a profile // name and host, performs the OAuth challenge, saves the profile to // .databrickscfg, and returns the new profile name and profile. -func runInlineLogin(ctx context.Context, profiler profile.Profiler) (string, *profile.Profile, error) { +func runInlineLogin(ctx context.Context, profiler profile.Profiler, tokenCache cache.TokenCache, mode storage.StorageMode) (string, *profile.Profile, error) { profileName, err := promptForProfile(ctx, "DEFAULT") if err != nil { return "", nil, err @@ -438,7 +381,6 @@ func runInlineLogin(ctx context.Context, profiler profile.Profiler) (string, *pr } loginArgs := &auth.AuthArguments{} - applyUnifiedHostFlags(existingProfile, loginArgs) err = setHostAndAccountId(ctx, existingProfile, loginArgs, nil) if err != nil { @@ -460,7 +402,8 @@ func runInlineLogin(ctx context.Context, profiler profile.Profiler) (string, *pr } persistentAuthOpts := []u2m.PersistentAuthOption{ u2m.WithOAuthArgument(oauthArgument), - u2m.WithBrowser(openURLSuppressingStderr), + u2m.WithBrowser(func(url string) error { return browser.Open(ctx, url) }), + u2m.WithTokenCache(storage.WrapForOAuthArgument(tokenCache, mode, oauthArgument)), } if len(scopesList) > 0 { persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList)) @@ -479,19 +422,16 @@ func runInlineLogin(ctx context.Context, profiler profile.Profiler) (string, *pr } clearKeys := oauthLoginClearKeys() - if !loginArgs.IsUnifiedHost { - clearKeys = append(clearKeys, "experimental_is_unified_host") - } + clearKeys = append(clearKeys, databrickscfg.ExperimentalIsUnifiedHostKey) err = databrickscfg.SaveToProfile(ctx, &config.Config{ - Profile: profileName, - Host: loginArgs.Host, - AuthType: authTypeDatabricksCLI, - AccountID: loginArgs.AccountID, - WorkspaceID: loginArgs.WorkspaceID, - Experimental_IsUnifiedHost: loginArgs.IsUnifiedHost, - ConfigFile: env.Get(ctx, "DATABRICKS_CONFIG_FILE"), - Scopes: scopesList, + Profile: profileName, + Host: loginArgs.Host, + AuthType: authTypeDatabricksCLI, + AccountID: loginArgs.AccountID, + WorkspaceID: loginArgs.WorkspaceID, + ConfigFile: env.Get(ctx, "DATABRICKS_CONFIG_FILE"), + Scopes: scopesList, }, clearKeys...) if err != nil { return "", nil, err diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index ec7fe2004ab..3dfa4e5d21a 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -221,6 +221,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -241,6 +242,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -258,6 +260,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -275,6 +278,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -292,6 +296,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -308,6 +313,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -324,6 +330,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -340,6 +347,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{"workspace-a"}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -356,6 +364,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{"workspace-a.cloud.databricks.com"}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -372,6 +381,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{"default.dev"}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -388,6 +398,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{"nonexistent.cloud.databricks.com"}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -419,6 +430,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -436,6 +448,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -454,6 +467,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -473,6 +487,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -490,6 +505,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -508,6 +524,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -526,6 +543,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -542,6 +560,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{"workspace-a"}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -557,6 +576,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: nil, }, wantErr: "no profile specified. Use --profile to specify which profile to use", @@ -569,6 +589,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profile.InMemoryProfiler{}, + tokenCache: tokenCache, persistentAuthOpts: nil, }, wantErr: "no profiles configured. Run 'databricks auth login' to create a profile", @@ -581,6 +602,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: errProfiler{err: profile.ErrNoConfiguration}, + tokenCache: tokenCache, persistentAuthOpts: nil, }, wantErr: "no profiles configured. Run 'databricks auth login' to create a profile", @@ -638,6 +660,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -658,6 +681,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -678,6 +702,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -699,6 +724,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -734,6 +760,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -750,6 +777,7 @@ func TestToken_loadToken(t *testing.T) { args: []string{}, tokenTimeout: 1 * time.Hour, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -769,6 +797,7 @@ func TestToken_loadToken(t *testing.T) { tokenTimeout: 1 * time.Hour, forceRefresh: true, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), @@ -786,6 +815,7 @@ func TestToken_loadToken(t *testing.T) { tokenTimeout: 1 * time.Hour, forceRefresh: true, profiler: profiler, + tokenCache: tokenCache, persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), diff --git a/cmd/bundle/config_remote_sync.go b/cmd/bundle/config_remote_sync.go index b172223a83d..c50e613d71a 100644 --- a/cmd/bundle/config_remote_sync.go +++ b/cmd/bundle/config_remote_sync.go @@ -13,6 +13,7 @@ import ( "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/flags" + "github.com/databricks/cli/libs/log" "github.com/spf13/cobra" ) @@ -63,6 +64,10 @@ Examples: return fmt.Errorf("failed to resolve field changes: %w", err) } + if err := configsync.RestoreVariableReferences(ctx, b, fieldChanges); err != nil { + log.Warnf(ctx, "variable restoration skipped: %v", err) + } + files, err := configsync.ApplyChangesToYAML(ctx, b, fieldChanges) if err != nil { return fmt.Errorf("failed to generate YAML files: %w", err) diff --git a/cmd/bundle/debug.go b/cmd/bundle/debug.go index 2af948ecac4..c62c75080cc 100644 --- a/cmd/bundle/debug.go +++ b/cmd/bundle/debug.go @@ -17,5 +17,6 @@ func newDebugCommand() *cobra.Command { cmd.AddCommand(debug.NewRefSchemaCommand()) cmd.AddCommand(debug.NewStatesCommand()) cmd.AddCommand(debug.NewRenderTemplateSchemaCommand()) + cmd.AddCommand(debug.NewListTargetsCommand()) return cmd } diff --git a/cmd/bundle/debug/list_targets.go b/cmd/bundle/debug/list_targets.go new file mode 100644 index 00000000000..6349ba46725 --- /dev/null +++ b/cmd/bundle/debug/list_targets.go @@ -0,0 +1,113 @@ +package debug + +import ( + "encoding/json" + "fmt" + "maps" + "slices" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/phases" + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/cli/libs/logdiag" + "github.com/spf13/cobra" +) + +type targetInfo struct { + Name string `json:"name"` + Default bool `json:"default,omitempty"` + Mode config.Mode `json:"mode,omitempty"` + Host string `json:"host,omitempty"` +} + +type listTargetsOutput struct { + Targets []targetInfo `json:"targets"` +} + +func collectTargets(targets map[string]*config.Target) []targetInfo { + names := slices.Sorted(maps.Keys(targets)) + + result := make([]targetInfo, 0, len(names)) + for _, name := range names { + t := targets[name] + // YAML decoding can leave a nil entry in the map when a target is + // declared with a null value. Skip rather than dereference and panic. + if t == nil { + result = append(result, targetInfo{Name: name}) + continue + } + info := targetInfo{ + Name: name, + Default: t.Default, + Mode: t.Mode, + } + if t.Workspace != nil { + info.Host = t.Workspace.Host + } + result = append(result, info) + } + return result +} + +// NewListTargetsCommand returns a command that lists all bundle targets +// with their name, default, mode, and workspace host fields. +func NewListTargetsCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "list-targets", + Short: "List all available bundle targets", + Args: root.NoArgs, + Hidden: true, + } + + cmd.RunE = func(cmd *cobra.Command, args []string) error { + ctx := logdiag.InitContext(cmd.Context()) + cmd.SetContext(ctx) + logdiag.SetSeverity(ctx, diag.Warning) + + b := bundle.MustLoad(ctx) + if b == nil || logdiag.HasError(ctx) { + return root.ErrAlreadyPrinted + } + + phases.Load(ctx, b) + if logdiag.HasError(ctx) { + return root.ErrAlreadyPrinted + } + + targets := collectTargets(b.Config.Targets) + + switch root.OutputType(cmd) { + case flags.OutputText: + for _, t := range targets { + parts := []string{t.Name} + if t.Default { + parts = append(parts, "(default)") + } + if t.Mode != "" { + parts = append(parts, string(t.Mode)) + } + if t.Host != "" { + parts = append(parts, t.Host) + } + cmdio.LogString(ctx, strings.Join(parts, " ")) + } + case flags.OutputJSON: + buf, err := json.MarshalIndent(listTargetsOutput{Targets: targets}, "", " ") + if err != nil { + return err + } + _, _ = cmd.OutOrStdout().Write(buf) + default: + return fmt.Errorf("unknown output type %s", root.OutputType(cmd)) + } + + return nil + } + + return cmd +} diff --git a/cmd/bundle/debug/refschema.go b/cmd/bundle/debug/refschema.go index ba22d28ae12..4ba1ae999fb 100644 --- a/cmd/bundle/debug/refschema.go +++ b/cmd/bundle/debug/refschema.go @@ -3,15 +3,15 @@ package debug import ( "fmt" "io" + "maps" "reflect" - "sort" + "slices" "strings" "github.com/databricks/cli/bundle/direct/dresources" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/cli/libs/structs/structwalk" - "github.com/databricks/cli/libs/utils" "github.com/spf13/cobra" ) @@ -42,7 +42,7 @@ func dumpRemoteSchemas(out io.Writer) error { return fmt.Errorf("failed to initialize adapters: %w", err) } - for _, resourceName := range utils.SortedKeys(adapters) { + for _, resourceName := range slices.Sorted(maps.Keys(adapters)) { adapter := adapters[resourceName] var resourcePrefix string @@ -100,9 +100,9 @@ func dumpRemoteSchemas(out io.Writer) error { } var lines []string - for _, p := range utils.SortedKeys(pathTypes) { + for _, p := range slices.Sorted(maps.Keys(pathTypes)) { byType := pathTypes[p] - for _, t := range utils.SortedKeys(byType) { + for _, t := range slices.Sorted(maps.Keys(byType)) { info := formatTags(byType[t]) sep := "." if strings.HasPrefix(p, "[") { @@ -112,7 +112,7 @@ func dumpRemoteSchemas(out io.Writer) error { } } - sort.Strings(lines) + slices.Sort(lines) for _, l := range lines { fmt.Fprint(out, l) } @@ -125,5 +125,5 @@ func formatTags(sources map[string]struct{}) string { if len(sources) == 3 { return "ALL" } - return strings.Join(utils.SortedKeys(sources), "\t") + return strings.Join(slices.Sorted(maps.Keys(sources)), "\t") } diff --git a/cmd/bundle/deployment/bind_resource.go b/cmd/bundle/deployment/bind_resource.go index ee20ae22a9b..fc972d4d7c6 100644 --- a/cmd/bundle/deployment/bind_resource.go +++ b/cmd/bundle/deployment/bind_resource.go @@ -33,7 +33,7 @@ func BindResource(cmd *cobra.Command, resourceKey, resourceId string, autoApprov return err } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) exists, err := resource.Exists(ctx, w, resourceId) if err != nil { return fmt.Errorf("failed to fetch the resource, err: %w", err) diff --git a/cmd/bundle/deployment/migrate.go b/cmd/bundle/deployment/migrate.go index e859199c45f..5020d88e73a 100644 --- a/cmd/bundle/deployment/migrate.go +++ b/cmd/bundle/deployment/migrate.go @@ -250,7 +250,7 @@ To start using direct engine, set "engine: direct" under bundle in your databric return root.ErrAlreadyPrinted } - plan, err := deploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(), &b.Config) + plan, err := deploymentBundle.CalculatePlan(ctx, b.WorkspaceClient(ctx), &b.Config) if err != nil { return err } @@ -281,7 +281,7 @@ To start using direct engine, set "engine: direct" under bundle in your databric } } - deploymentBundle.Apply(ctx, b.WorkspaceClient(), plan, direct.MigrateMode(true)) + deploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(true)) if err := deploymentBundle.StateDB.Finalize(); err != nil { logdiag.LogError(ctx, err) } diff --git a/cmd/bundle/generate/alert.go b/cmd/bundle/generate/alert.go index f79a579171a..87ba3eacb9e 100644 --- a/cmd/bundle/generate/alert.go +++ b/cmd/bundle/generate/alert.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "errors" "fmt" + "net/http" "os" "path" "path/filepath" @@ -74,14 +75,14 @@ After generation, you can deploy this alert to other targets using: return root.ErrAlreadyPrinted } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) // Get alert from Databricks alert, err := w.AlertsV2.GetAlert(ctx, sql.GetAlertV2Request{Id: alertID}) if err != nil { // Check if it's a not found error to provide a better message var apiErr *apierr.APIError - if errors.As(err, &apiErr) && apiErr.StatusCode == 404 { + if errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound { return fmt.Errorf("alert with ID %s not found", alertID) } return err diff --git a/cmd/bundle/generate/app.go b/cmd/bundle/generate/app.go index 323d92835cb..120c405e793 100644 --- a/cmd/bundle/generate/app.go +++ b/cmd/bundle/generate/app.go @@ -70,7 +70,7 @@ per target environment.`, return root.ErrAlreadyPrinted } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) cmdio.LogString(ctx, fmt.Sprintf("Loading app '%s' configuration", appName)) app, err := w.Apps.Get(ctx, apps.GetAppRequest{Name: appName}) if err != nil { diff --git a/cmd/bundle/generate/dashboard.go b/cmd/bundle/generate/dashboard.go index fe546549e6b..70de46225c2 100644 --- a/cmd/bundle/generate/dashboard.go +++ b/cmd/bundle/generate/dashboard.go @@ -7,9 +7,11 @@ import ( "errors" "fmt" "io" + "maps" "os" "path" "path/filepath" + "slices" "strings" "time" @@ -33,7 +35,6 @@ import ( "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/spf13/cobra" "go.yaml.in/yaml/v3" - "golang.org/x/exp/maps" ) type dashboard struct { @@ -81,8 +82,8 @@ func (d *dashboard) resolveID(ctx context.Context, b *bundle.Bundle) string { } func (d *dashboard) resolveFromPath(ctx context.Context, b *bundle.Bundle) string { - w := b.WorkspaceClient() - obj, err := w.Workspace.GetStatusByPath(ctx, d.existingPath) + w := b.WorkspaceClient(ctx) + obj, err := w.Workspace.GetStatusByPath(ctx, d.existingPath) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { if apierr.IsMissing(err) { logdiag.LogError(ctx, fmt.Errorf("dashboard %q not found", path.Base(d.existingPath))) @@ -128,7 +129,7 @@ func (d *dashboard) resolveFromPath(ctx context.Context, b *bundle.Bundle) strin } func (d *dashboard) resolveFromID(ctx context.Context, b *bundle.Bundle) string { - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) obj, err := w.Lakeview.GetByDashboardId(ctx, d.existingID) if err != nil { if apierr.IsMissing(err) { @@ -260,7 +261,7 @@ func waitForChanges(ctx context.Context, w *databricks.WorkspaceClient, dashboar } for { - obj, err := w.Workspace.GetStatusByPath(ctx, dashboard.Path) + obj, err := w.Workspace.GetStatusByPath(ctx, dashboard.Path) //nolint:staticcheck // Deprecated in SDK v0.127.0. Migration to WorkspaceHierarchyService tracked separately. if err != nil { logdiag.LogError(ctx, err) return @@ -294,7 +295,7 @@ func (d *dashboard) updateDashboardForResource(ctx context.Context, b *bundle.Bu // Overwrite the dashboard at the path referenced from the resource. dashboardPath := resource.FilePath - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) // Start polling the underlying dashboard for changes. var etag string @@ -330,7 +331,7 @@ func (d *dashboard) updateDashboardForResource(ctx context.Context, b *bundle.Bu } func (d *dashboard) generateForExisting(ctx context.Context, b *bundle.Bundle, dashboardID string) { - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) dashboard, err := w.Lakeview.GetByDashboardId(ctx, dashboardID) if err != nil { logdiag.LogError(ctx, err) @@ -459,7 +460,7 @@ func dashboardResourceCompletion(cmd *cobra.Command, args []string, toComplete s return nil, cobra.ShellCompDirectiveNoFileComp } - return maps.Keys(resources.Completions(b, filterDashboards)), cobra.ShellCompDirectiveNoFileComp + return slices.Collect(maps.Keys(resources.Completions(b, filterDashboards))), cobra.ShellCompDirectiveNoFileComp } func NewGenerateDashboardCommand() *cobra.Command { diff --git a/cmd/bundle/generate/job.go b/cmd/bundle/generate/job.go index 1dbb8521bf9..c3aba49c5f2 100644 --- a/cmd/bundle/generate/job.go +++ b/cmd/bundle/generate/job.go @@ -74,7 +74,7 @@ After generation, you can deploy this job to other targets using: return root.ErrAlreadyPrinted } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) job, err := w.Jobs.Get(ctx, jobs.GetJobRequest{JobId: jobId}) if err != nil { return err @@ -92,11 +92,9 @@ After generation, you can deploy this job to other targets using: if job.Settings.GitSource != nil { cmdio.LogString(ctx, "Job is using Git source, skipping downloading files") } else { - for _, task := range job.Settings.Tasks { - err := downloader.MarkTaskForDownload(ctx, &task) - if err != nil { - return err - } + err = downloader.MarkTasksForDownload(ctx, job.Settings.Tasks) + if err != nil { + return err } } @@ -123,6 +121,8 @@ After generation, you can deploy this job to other targets using: return err } + downloader.CleanupOldFiles(ctx) + oldFilename := filepath.Join(configDir, jobKey+".yml") filename := filepath.Join(configDir, jobKey+".job.yml") diff --git a/cmd/bundle/generate/pipeline.go b/cmd/bundle/generate/pipeline.go index dd422d78086..35fb073cadd 100644 --- a/cmd/bundle/generate/pipeline.go +++ b/cmd/bundle/generate/pipeline.go @@ -30,14 +30,14 @@ func NewGeneratePipelineCommand() *cobra.Command { cmd := &cobra.Command{ Use: "pipeline", Short: "Generate bundle configuration for a pipeline", - Long: `Generate bundle configuration for an existing Delta Live Tables pipeline. + Long: `Generate bundle configuration for an existing pipeline. -This command downloads an existing Lakeflow Spark Declarative Pipeline's configuration and any associated +This command downloads an existing pipeline's configuration and any associated notebooks, creating bundle files that you can use to deploy the pipeline to other environments or manage it as code. Examples: - # Import a production Lakeflow Spark Declarative Pipeline + # Import a production pipeline databricks bundle generate pipeline --existing-pipeline-id abc123 --key etl_pipeline # Organize files in custom directories @@ -73,7 +73,7 @@ like catalogs, schemas, and compute configurations per target.`, return root.ErrAlreadyPrinted } - w := b.WorkspaceClient() + w := b.WorkspaceClient(ctx) pipeline, err := w.Pipelines.Get(ctx, pipelines.GetPipelineRequest{PipelineId: pipelineId}) if err != nil { return err diff --git a/cmd/bundle/open.go b/cmd/bundle/open.go index 8cddd51ca3c..d357b4f39e1 100644 --- a/cmd/bundle/open.go +++ b/cmd/bundle/open.go @@ -6,17 +6,17 @@ import ( "context" "errors" "fmt" + "maps" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/resources" "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/browser" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" - "golang.org/x/exp/maps" - - "github.com/pkg/browser" ) func promptOpenArgument(ctx context.Context, b *bundle.Bundle) (string, error) { @@ -56,7 +56,7 @@ func newOpenCommand() *cobra.Command { Examples: databricks bundle open # Prompts to select a resource to open - databricks bundle open my_job # Open specific job in Workflows UI + databricks bundle open my_job # Open specific job in Jobs UI databricks bundle open my_dashboard # Open dashboard in browser Use after deployment to quickly navigate to your resources in the workspace.`, @@ -74,7 +74,8 @@ Use after deployment to quickly navigate to your resources in the workspace.`, arg, err = resolveOpenArgument(ctx, b, args) return err }, - InitIDs: true, + AlwaysPull: forcePull, + InitIDs: true, }) if err != nil { return err @@ -92,8 +93,13 @@ Use after deployment to quickly navigate to your resources in the workspace.`, return errors.New("resource does not have a URL associated with it (has it been deployed?)") } - cmdio.LogString(cmd.Context(), "Opening browser at "+url) - return browser.OpenURL(url) + ctx := cmd.Context() + if browser.IsDisabled(ctx) { + cmdio.LogString(ctx, "Open this URL in your browser:\n"+url) + return nil + } + cmdio.LogString(ctx, "Opening browser at "+url) + return browser.Open(ctx, url) } cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { @@ -113,7 +119,7 @@ Use after deployment to quickly navigate to your resources in the workspace.`, if len(args) == 0 { completions := resources.Completions(b) - return maps.Keys(completions), cobra.ShellCompDirectiveNoFileComp + return slices.Collect(maps.Keys(completions)), cobra.ShellCompDirectiveNoFileComp } else { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/bundle/run.go b/cmd/bundle/run.go index 825175904d9..e98fe59ac4e 100644 --- a/cmd/bundle/run.go +++ b/cmd/bundle/run.go @@ -5,7 +5,9 @@ import ( "encoding/json" "errors" "fmt" + "maps" "os" + "slices" "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/env" @@ -22,7 +24,6 @@ import ( "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" - "golang.org/x/exp/maps" ) func promptRunArgument(ctx context.Context, b *bundle.Bundle) (string, error) { @@ -239,7 +240,7 @@ Example usage: if len(args) == 0 { completions := resources.Completions(b, run.IsRunnable) - return maps.Keys(completions), cobra.ShellCompDirectiveNoFileComp + return slices.Collect(maps.Keys(completions)), cobra.ShellCompDirectiveNoFileComp } else { // If we know the resource to run, we can complete additional positional arguments. runner, err := keyToRunner(b, args[0]) diff --git a/cmd/bundle/summary.go b/cmd/bundle/summary.go index 9f2aa3e54bb..ff7356e2c60 100644 --- a/cmd/bundle/summary.go +++ b/cmd/bundle/summary.go @@ -46,6 +46,7 @@ Useful after deployment to see what was created and where to find it.`, } else { b, err := utils.ProcessBundle(cmd, utils.ProcessOptions{ ReadState: true, + AlwaysPull: forcePull, IncludeLocations: includeLocations, InitIDs: true, }) diff --git a/cmd/bundle/utils/utils.go b/cmd/bundle/utils/utils.go index a7568b8f6ef..3c4bd1a5b98 100644 --- a/cmd/bundle/utils/utils.go +++ b/cmd/bundle/utils/utils.go @@ -4,10 +4,6 @@ import ( "context" "github.com/databricks/cli/bundle" - bundleenv "github.com/databricks/cli/bundle/env" - "github.com/databricks/cli/bundle/phases" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" ) @@ -20,85 +16,3 @@ func configureVariables(cmd *cobra.Command, b *bundle.Bundle, variables []string } }) } - -// getTargetFromCmd returns the target name from command flags or environment. -func getTargetFromCmd(cmd *cobra.Command) string { - // Check command line flag first - if flag := cmd.Flag("target"); flag != nil { - if value := flag.Value.String(); value != "" { - return value - } - } - - // Check deprecated environment flag - if flag := cmd.Flag("environment"); flag != nil { - if value := flag.Value.String(); value != "" { - return value - } - } - - // Fall back to environment variable - target, _ := bundleenv.Target(cmd.Context()) - return target -} - -// ReloadBundle reloads the bundle configuration without modifying the command context. -// This is useful when you need to refresh the bundle configuration after changes -// without side effects like setting values on the context. -func ReloadBundle(cmd *cobra.Command) *bundle.Bundle { - ctx := cmd.Context() - - // Load the bundle configuration fresh from the filesystem - b := bundle.MustLoad(ctx) - if b == nil || logdiag.HasError(ctx) { - return b - } - - // Load the target configuration - if target := getTargetFromCmd(cmd); target == "" { - phases.LoadDefaultTarget(ctx, b) - } else { - phases.LoadNamedTarget(ctx, b, target) - } - - if logdiag.HasError(ctx) { - return b - } - - // Configure the workspace profile if provided - configureProfile(cmd, b) - - // Configure variables if provided - variables, err := cmd.Flags().GetStringSlice("var") - if err != nil { - logdiag.LogDiag(ctx, diag.FromErr(err)[0]) - return b - } - configureVariables(cmd, b, variables) - return b -} - -// configureProfile applies the profile flag to the bundle. -func configureProfile(cmd *cobra.Command, b *bundle.Bundle) { - profile := getProfileFromCmd(cmd) - if profile == "" { - return - } - - bundle.ApplyFuncContext(cmd.Context(), b, func(ctx context.Context, b *bundle.Bundle) { - b.Config.Workspace.Profile = profile - }) -} - -// getProfileFromCmd returns the profile from command flags or environment. -func getProfileFromCmd(cmd *cobra.Command) string { - // Check command line flag first - if flag := cmd.Flag("profile"); flag != nil { - if value := flag.Value.String(); value != "" { - return value - } - } - - // Fall back to environment variable - return env.Get(cmd.Context(), "DATABRICKS_CONFIG_PROFILE") -} diff --git a/cmd/cmd.go b/cmd/cmd.go index 014471f7638..d8a8c09f044 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -17,6 +17,7 @@ import ( "github.com/databricks/cli/cmd/experimental" "github.com/databricks/cli/cmd/fs" "github.com/databricks/cli/cmd/labs" + "github.com/databricks/cli/cmd/lakebox" "github.com/databricks/cli/cmd/pipelines" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/cmd/selftest" @@ -103,6 +104,7 @@ func New(ctx context.Context) *cobra.Command { cli.AddCommand(configure.New()) cli.AddCommand(fs.New()) cli.AddCommand(labs.New(ctx)) + cli.AddCommand(lakebox.New()) cli.AddCommand(sync.New()) cli.AddCommand(version.New()) cli.AddCommand(selftest.New()) diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go index b0ce8c92801..489e4e3050b 100644 --- a/cmd/configure/configure.go +++ b/cmd/configure/configure.go @@ -27,14 +27,13 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config // Ask user to specify the host if not already set. if cfg.Host == "" { - prompt := cmdio.Prompt(ctx) - prompt.Label = "Databricks workspace host (https://...)" - prompt.AllowEdit = true - prompt.Validate = func(input string) error { - normalized := normalizeHost(input) - return validateHost(normalized) - } - out, err := prompt.Run() + out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Databricks workspace host (https://...)", + Validate: func(input string) error { + normalized := normalizeHost(input) + return validateHost(normalized) + }, + }) if err != nil { return err } @@ -43,10 +42,10 @@ func configureInteractive(cmd *cobra.Command, flags *configureFlags, cfg *config // Ask user to specify the token is not already set. if cfg.Token == "" { - prompt := cmdio.Prompt(ctx) - prompt.Label = "Personal access token" - prompt.Mask = '*' - out, err := prompt.Run() + out, err := cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Personal access token", + Mask: '*', + }) if err != nil { return err } @@ -162,9 +161,9 @@ The host must be specified with the --host flag or the DATABRICKS_HOST environme clearKeys = append(clearKeys, "serverless_compute_id") } - // Clear stale unified-host metadata — PAT profiles don't use it, + // Clear stale unified-host metadata, PAT profiles don't use it, // and leaving it can change HostType() routing. - clearKeys = append(clearKeys, "experimental_is_unified_host") + clearKeys = append(clearKeys, databrickscfg.ExperimentalIsUnifiedHostKey) err = databrickscfg.SaveToProfile(ctx, &config.Config{ Profile: cfg.Profile, diff --git a/cmd/experimental/experimental.go b/cmd/experimental/experimental.go index eb3b7814e1a..52c6bac79b0 100644 --- a/cmd/experimental/experimental.go +++ b/cmd/experimental/experimental.go @@ -2,6 +2,7 @@ package experimental import ( aitoolscmd "github.com/databricks/cli/experimental/aitools/cmd" + postgrescmd "github.com/databricks/cli/experimental/postgres/cmd" "github.com/spf13/cobra" ) @@ -21,6 +22,8 @@ development. They may change or be removed in future versions without notice.`, } cmd.AddCommand(aitoolscmd.NewAitoolsCmd()) + cmd.AddCommand(postgrescmd.New()) + cmd.AddCommand(newWorkspaceOpenCommand()) return cmd } diff --git a/cmd/experimental/workspace_open.go b/cmd/experimental/workspace_open.go new file mode 100644 index 00000000000..e48bdcc6d96 --- /dev/null +++ b/cmd/experimental/workspace_open.go @@ -0,0 +1,84 @@ +package experimental + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/browser" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/workspaceurls" +) + +var currentWorkspaceID = func(ctx context.Context) (int64, error) { + return cmdctx.WorkspaceClient(ctx).CurrentWorkspaceID(ctx) +} + +var openWorkspaceURL = browser.Open + +func newWorkspaceOpenCommand() *cobra.Command { + var printURL bool + + cmd := &cobra.Command{ + Use: "open [flags] RESOURCE_TYPE ID_OR_PATH", + Short: "Open a workspace resource or print its URL", + Long: fmt.Sprintf(`Open a workspace resource in the default browser, or print its URL. + +Supported resource types: %s. + +For registered_models, use the dot-separated name (catalog.schema.model) +and it will be converted to the correct URL path automatically. + +Examples: + databricks experimental open jobs 123456789 + databricks experimental open notebooks /Users/user@example.com/my-notebook + databricks experimental open clusters 0123-456789-abcdef + databricks experimental open registered_models catalog.schema.my_model + databricks experimental open jobs 123456789 --url`, strings.Join(workspaceurls.ResourceTypes(), ", ")), + Args: cobra.ExactArgs(2), + PreRunE: root.MustWorkspaceClient, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + resourceType := args[0] + id := args[1] + + workspaceID, err := currentWorkspaceID(ctx) + if err != nil { + log.Warnf(ctx, "Could not determine workspace ID: %v", err) + } + + resourceURL, err := workspaceurls.BuildResourceURL(w.Config.Host, resourceType, id, workspaceID) + if err != nil { + return err + } + + if printURL { + _, err := fmt.Fprintln(cmd.OutOrStdout(), resourceURL) + return err + } + + if !browser.IsDisabled(ctx) { + cmdio.LogString(ctx, fmt.Sprintf("Opening %s %s in the browser...", resourceType, id)) + } + + return openWorkspaceURL(ctx, resourceURL) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return workspaceurls.ResourceTypes(), cobra.ShellCompDirectiveNoFileComp + } + return nil, cobra.ShellCompDirectiveNoFileComp + }, + } + + cmd.Flags().BoolVar(&printURL, "url", false, "Print the workspace URL instead of opening the browser") + + return cmd +} diff --git a/cmd/experimental/workspace_open_test.go b/cmd/experimental/workspace_open_test.go new file mode 100644 index 00000000000..7ec8e937ece --- /dev/null +++ b/cmd/experimental/workspace_open_test.go @@ -0,0 +1,267 @@ +package experimental + +import ( + "bytes" + "context" + "errors" + "log/slog" + "testing" + + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/log" + "github.com/databricks/cli/libs/log/handler" + "github.com/databricks/cli/libs/workspaceurls" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/config" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBuildWorkspaceURLPathBasedResources(t *testing.T) { + tests := []struct { + resourceType string + id string + expected string + }{ + {"jobs", "123", "https://myworkspace.databricks.com/jobs/123"}, + {"pipelines", "abc-def", "https://myworkspace.databricks.com/pipelines/abc-def"}, + {"dashboards", "dash-1", "https://myworkspace.databricks.com/dashboardsv3/dash-1/published"}, + {"experiments", "exp-1", "https://myworkspace.databricks.com/ml/experiments/exp-1"}, + {"warehouses", "wh-1", "https://myworkspace.databricks.com/sql/warehouses/wh-1"}, + {"queries", "q-1", "https://myworkspace.databricks.com/sql/editor/q-1"}, + {"apps", "my-app", "https://myworkspace.databricks.com/apps/my-app"}, + {"clusters", "0123-456789-abc", "https://myworkspace.databricks.com/compute/clusters/0123-456789-abc"}, + {"registered_models", "catalog.schema.model", "https://myworkspace.databricks.com/explore/data/models/catalog/schema/model"}, + } + + for _, tt := range tests { + t.Run(tt.resourceType+"/"+tt.id, func(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, 0) + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + }) + } +} + +func TestBuildWorkspaceURLFragmentBasedResources(t *testing.T) { + tests := []struct { + resourceType string + id string + expected string + }{ + {"notebooks", "12345", "https://myworkspace.databricks.com/#notebook/12345"}, + {"notebooks", "/Users/user@example.com/my-notebook", "https://myworkspace.databricks.com/#notebook//Users/user@example.com/my-notebook"}, + } + + for _, tt := range tests { + t.Run(tt.id, func(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", tt.resourceType, tt.id, 0) + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + }) + } +} + +func TestBuildWorkspaceURLUnknownResourceType(t *testing.T) { + _, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "unknown", "123", 0) + assert.ErrorContains(t, err, "unknown resource type \"unknown\"") + assert.ErrorContains(t, err, "alerts, apps, clusters, dashboards, experiments, jobs, model_serving_endpoints, models, notebooks, pipelines, queries, registered_models, warehouses") +} + +func TestBuildWorkspaceURLHostWithTrailingSlash(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com/", "jobs", "123", 0) + require.NoError(t, err) + assert.Equal(t, "https://myworkspace.databricks.com/jobs/123", got) +} + +func TestBuildWorkspaceURLWithWorkspaceID(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "jobs", "123", 123456) + require.NoError(t, err) + assert.Equal(t, "https://myworkspace.databricks.com/jobs/123?o=123456", got) +} + +func TestBuildWorkspaceURLWithWorkspaceIDInHostname(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://adb-123456.azuredatabricks.net", "jobs", "123", 123456) + require.NoError(t, err) + // Workspace ID is already in the hostname, so ?o= should not be appended. + assert.Equal(t, "https://adb-123456.azuredatabricks.net/jobs/123", got) +} + +func TestBuildWorkspaceURLWithWorkspaceIDInVanityHostname(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://workspace-123456.example.com", "jobs", "123", 123456) + require.NoError(t, err) + assert.Equal(t, "https://workspace-123456.example.com/jobs/123?o=123456", got) +} + +func TestBuildWorkspaceURLFragmentWithWorkspaceID(t *testing.T) { + got, err := workspaceurls.BuildResourceURL("https://myworkspace.databricks.com", "notebooks", "12345", 789) + require.NoError(t, err) + assert.Equal(t, "https://myworkspace.databricks.com/?o=789#notebook/12345", got) +} + +func TestWorkspaceOpenCommandCompletion(t *testing.T) { + cmd := newWorkspaceOpenCommand() + + completions, directive := cmd.ValidArgsFunction(cmd, []string{}, "") + assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive) + assert.Contains(t, completions, "alerts") + assert.Contains(t, completions, "apps") + assert.Contains(t, completions, "clusters") + assert.Contains(t, completions, "dashboards") + assert.Contains(t, completions, "experiments") + assert.Contains(t, completions, "jobs") + assert.Contains(t, completions, "models") + assert.Contains(t, completions, "model_serving_endpoints") + assert.Contains(t, completions, "notebooks") + assert.Contains(t, completions, "pipelines") + assert.Contains(t, completions, "queries") + assert.Contains(t, completions, "registered_models") + assert.Contains(t, completions, "warehouses") + assert.Len(t, completions, 13) +} + +func TestWorkspaceOpenCommandCompletionSecondArg(t *testing.T) { + cmd := newWorkspaceOpenCommand() + + completions, directive := cmd.ValidArgsFunction(cmd, []string{"jobs"}, "") + assert.Equal(t, cobra.ShellCompDirectiveNoFileComp, directive) + assert.Nil(t, completions) +} + +func TestWorkspaceOpenCommandHelpText(t *testing.T) { + cmd := newWorkspaceOpenCommand() + + assert.Contains(t, cmd.Long, "Supported resource types: alerts, apps, clusters, dashboards, experiments, jobs, model_serving_endpoints, models, notebooks, pipelines, queries, registered_models, warehouses.") + assert.Contains(t, cmd.Long, "databricks experimental open jobs 123456789") + assert.Contains(t, cmd.Long, "databricks experimental open notebooks /Users/user@example.com/my-notebook") + assert.Contains(t, cmd.Long, "databricks experimental open registered_models catalog.schema.my_model") + assert.Contains(t, cmd.Long, "databricks experimental open jobs 123456789 --url") + assert.Contains(t, cmd.Long, "dot-separated name") + + flag := cmd.Flags().Lookup("url") + require.NotNil(t, flag) + assert.Equal(t, "false", flag.DefValue) +} + +func TestWorkspaceOpenCommandOpensBrowserByDefault(t *testing.T) { + originalCurrentWorkspaceID := currentWorkspaceID + originalOpenWorkspaceURL := openWorkspaceURL + t.Cleanup(func() { + currentWorkspaceID = originalCurrentWorkspaceID + openWorkspaceURL = originalOpenWorkspaceURL + }) + + currentWorkspaceID = func(context.Context) (int64, error) { + return 0, nil + } + + var gotURL string + openWorkspaceURL = func(ctx context.Context, targetURL string) error { + gotURL = targetURL + return nil + } + + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + ctx = cmdctx.SetWorkspaceClient(ctx, &databricks.WorkspaceClient{ + Config: &config.Config{ + Host: "https://myworkspace.databricks.com", + }, + }) + + cmd := newWorkspaceOpenCommand() + cmd.SetContext(ctx) + + var stdout bytes.Buffer + cmd.SetOut(&stdout) + + err := cmd.RunE(cmd, []string{"jobs", "123"}) + require.NoError(t, err) + + assert.Equal(t, "https://myworkspace.databricks.com/jobs/123", gotURL) + assert.Equal(t, "", stdout.String()) + assert.Contains(t, stderr.String(), "Opening jobs 123 in the browser...") +} + +func TestWorkspaceOpenCommandURLFlag(t *testing.T) { + originalCurrentWorkspaceID := currentWorkspaceID + originalOpenWorkspaceURL := openWorkspaceURL + t.Cleanup(func() { + currentWorkspaceID = originalCurrentWorkspaceID + openWorkspaceURL = originalOpenWorkspaceURL + }) + + currentWorkspaceID = func(context.Context) (int64, error) { + return 789, nil + } + + browserOpened := false + openWorkspaceURL = func(ctx context.Context, targetURL string) error { + browserOpened = true + return nil + } + + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + ctx = cmdctx.SetWorkspaceClient(ctx, &databricks.WorkspaceClient{ + Config: &config.Config{ + Host: "https://myworkspace.databricks.com", + }, + }) + + cmd := newWorkspaceOpenCommand() + cmd.SetContext(ctx) + + var stdout bytes.Buffer + cmd.SetOut(&stdout) + + require.NoError(t, cmd.Flags().Set("url", "true")) + + err := cmd.RunE(cmd, []string{"jobs", "123"}) + require.NoError(t, err) + + assert.False(t, browserOpened) + assert.Equal(t, "https://myworkspace.databricks.com/jobs/123?o=789\n", stdout.String()) + assert.Equal(t, "", stderr.String()) +} + +func TestWorkspaceOpenCommandWarnsWhenWorkspaceIDLookupFails(t *testing.T) { + originalCurrentWorkspaceID := currentWorkspaceID + originalOpenWorkspaceURL := openWorkspaceURL + t.Cleanup(func() { + currentWorkspaceID = originalCurrentWorkspaceID + openWorkspaceURL = originalOpenWorkspaceURL + }) + + currentWorkspaceID = func(context.Context) (int64, error) { + return 0, errors.New("lookup failed") + } + + openWorkspaceURL = func(ctx context.Context, targetURL string) error { + return nil + } + + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + ctx = log.NewContext(ctx, slog.New(handler.NewFriendlyHandler(stderr, &handler.Options{ + Level: log.LevelWarn, + }))) + ctx = cmdctx.SetWorkspaceClient(ctx, &databricks.WorkspaceClient{ + Config: &config.Config{ + Host: "https://myworkspace.databricks.com", + }, + }) + + cmd := newWorkspaceOpenCommand() + cmd.SetContext(ctx) + + var stdout bytes.Buffer + cmd.SetOut(&stdout) + + require.NoError(t, cmd.Flags().Set("url", "true")) + + err := cmd.RunE(cmd, []string{"jobs", "123"}) + require.NoError(t, err) + + assert.Equal(t, "https://myworkspace.databricks.com/jobs/123\n", stdout.String()) + assert.Contains(t, stderr.String(), "Could not determine workspace ID: lookup failed") +} diff --git a/cmd/fs/ls.go b/cmd/fs/ls.go index d7eac513a55..1e856a35e8d 100644 --- a/cmd/fs/ls.go +++ b/cmd/fs/ls.go @@ -1,9 +1,10 @@ package fs import ( + "cmp" "io/fs" "path" - "sort" + "slices" "time" "github.com/databricks/cli/cmd/root" @@ -72,8 +73,8 @@ func newLsCommand() *cobra.Command { } jsonDirEntries[i] = *jsonDirEntry } - sort.Slice(jsonDirEntries, func(i, j int) bool { - return jsonDirEntries[i].Name < jsonDirEntries[j].Name + slices.SortFunc(jsonDirEntries, func(a, b jsonDirEntry) int { + return cmp.Compare(a.Name, b.Name) }) // Use template for long mode if the flag is set diff --git a/cmd/fuzz_panic_test.go b/cmd/fuzz_panic_test.go new file mode 100644 index 00000000000..e4037b4ef85 --- /dev/null +++ b/cmd/fuzz_panic_test.go @@ -0,0 +1,254 @@ +package cmd_test + +import ( + "bytes" + "context" + "io" + "net/http" + "regexp" + "runtime/debug" + "strings" + "testing" + "time" + + "github.com/databricks/cli/cmd" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/config" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" +) + +// fuzzStubTransport short-circuits SDK HTTP requests without going through a +// real listener. The SDK treats 4xx as terminal (no retries), so LROs and +// paginated lists fail fast instead of looping. +type fuzzStubTransport struct{} + +func (fuzzStubTransport) RoundTrip(_ *http.Request) (*http.Response, error) { + body := `{"error_code":"FUZZ_STUB","message":"fuzz"}` + return &http.Response{ + StatusCode: http.StatusBadRequest, + Header: http.Header{"Content-Type": []string{"application/json"}}, + Body: io.NopCloser(bytes.NewBufferString(body)), + }, nil +} + +// perInvocationTimeout bounds a single Execute call as a backstop against +// hangs. Some auto-generated commands poll long-running operations (e.g. +// apps create-space) that don't terminate even when fuzzStubTransport +// returns 4xx. +const perInvocationTimeout = 200 * time.Millisecond + +// fuzzHarness holds the root command for a fuzz test plus a base context +// applied to each leaf before invocation. The base context carries the parts +// of CLI setup that auto-generated RunE bodies reach for: fake workspace and +// account clients, an initialized logdiag, and a discarding cmdio. The full +// cli tree is built so leaves inherit persistent flags (notably --output) +// from the root via cobra's parent-walking flag lookup. HTTP traffic from the +// SDK clients is short-circuited by a fuzzStubTransport that 400s every +// request. +type fuzzHarness struct { + cli *cobra.Command + baseCtx context.Context + leaves []leafCommand +} + +type leafCommand struct { + cmd *cobra.Command + path []string // tokens from cli root to this leaf, exclusive of root + declaredArgs int +} + +func newFuzzHarness(t *testing.T) *fuzzHarness { + t.Helper() + + wc, err := databricks.NewWorkspaceClient((*databricks.Config)(&config.Config{ + Host: "https://fuzz.invalid", + Token: "fuzz", + AuthType: "pat", + HTTPTransport: fuzzStubTransport{}, + })) + require.NoError(t, err) + + ac, err := databricks.NewAccountClient((*databricks.Config)(&config.Config{ + Host: "https://fuzz.invalid", + Token: "fuzz", + AuthType: "pat", + AccountID: "00000000-0000-0000-0000-000000000000", + HTTPTransport: fuzzStubTransport{}, + })) + require.NoError(t, err) + + // Pre-install everything auto-generated RunE bodies reach for into a base + // context. Cobra propagates this into subcommands during execute, so we + // don't need a PersistentPreRunE to re-install it per invocation. + baseCtx := t.Context() + baseCtx = logdiag.InitContext(baseCtx) + baseCtx = cmdctx.SetWorkspaceClient(baseCtx, wc) + baseCtx = cmdctx.SetAccountClient(baseCtx, ac) + baseCtx = cmdio.MockDiscard(baseCtx) + + cli := cmd.New(baseCtx) + + // Replace the real PersistentPreRunE (IO/logger/telemetry/user-agent init) + // with a no-op so it doesn't clobber the context we just set up. + cli.PersistentPreRunE = nil + cli.PersistentPostRunE = nil + + // Zero out PreRunE on every leaf so MustWorkspaceClient / MustAccountClient + // can't reach out for real credentials. + leaves := collectLeaves(cli) + for _, l := range leaves { + l.cmd.PreRunE = nil + } + + return &fuzzHarness{cli: cli, baseCtx: baseCtx, leaves: leaves} +} + +// collectLeaves walks the tree under root and returns every command that has a +// RunE and no subcommands. The returned path excludes root's own name, so it's +// ready to hand to cli.SetArgs. +func collectLeaves(root *cobra.Command) []leafCommand { + var out []leafCommand + for _, child := range root.Commands() { + collectLeavesInto(child, nil, &out) + } + return out +} + +func collectLeavesInto(cmd *cobra.Command, parentPath []string, out *[]leafCommand) { + path := append(append([]string{}, parentPath...), cmd.Name()) + children := cmd.Commands() + if len(children) == 0 { + if cmd.RunE != nil { + *out = append(*out, leafCommand{cmd: cmd, path: path, declaredArgs: declaredArgCount(cmd)}) + } + return + } + for _, child := range children { + collectLeavesInto(child, path, out) + } +} + +// declaredArgCount returns how many positional placeholders follow the command +// name in cmd.Use (e.g. "update-default-warehouse-override NAME UPDATE_MASK TYPE" → 3). +func declaredArgCount(cmd *cobra.Command) int { + fields := strings.Fields(cmd.Use) + if len(fields) <= 1 { + return 0 + } + return len(fields) - 1 +} + +var testNameSanitizer = regexp.MustCompile(`[^A-Za-z0-9_./=#-]+`) + +func sanitizeTestName(s string) string { + return testNameSanitizer.ReplaceAllString(s, "_") +} + +// run invokes a leaf's RunE directly, recovering panics as test failures. +// Non-panic errors are ignored — we only care about panics here. +// +// This bypasses cobra's full Execute path (flag parsing, PreRun chain, +// telemetry) for speed. Flag-defined-on-root concerns (e.g. root.OutputType +// reading --output) still work because the leaf is parented to the real root, +// and cobra.Command.Flag walks up the parent chain. +func (h *fuzzHarness) run(t *testing.T, leaf leafCommand, args []string) { + t.Helper() + + ctx, cancel := context.WithTimeout(h.baseCtx, perInvocationTimeout) + defer cancel() + leaf.cmd.SetContext(ctx) + + defer func() { + if r := recover(); r != nil { + t.Fatalf("panic in %q with args=%#v: %v\n\n%s", strings.Join(leaf.path, "/"), args, r, debug.Stack()) + } + }() + + // Honour cobra's arg validator. Counts it would reject are unreachable in + // practice, so we don't fail on panics behind them. + if leaf.cmd.Args != nil { + if err := leaf.cmd.Args(leaf.cmd, args); err != nil { + return + } + } + _ = leaf.cmd.RunE(leaf.cmd, args) +} + +// leafPathName formats a leaf's path as a slash-joined test name. +func leafPathName(leaf leafCommand) string { + return strings.Join(leaf.path, "/") +} + +// isAutoGenerated returns true for workspace/account leaves. We restrict +// count-fuzzing to auto-generated commands because that's where codegen +// regressions hide; manually written commands like `bundle` get PR review. +func isAutoGenerated(leaf leafCommand) bool { + if len(leaf.path) == 0 { + return false + } + if leaf.path[0] == "account" { + return true + } + // Everything under a workspace service command (e.g. "warehouses") is + // auto-generated. Manually-written commands live under named roots like + // "bundle", "auth", "sync", "fs", etc. The heuristic: anything whose + // root isn't in this block list is auto-generated. + manualRoots := map[string]bool{ + "bundle": true, + "auth": true, + "sync": true, + "fs": true, + "api": true, + "cache": true, + "completion": true, + "configure": true, + "experimental": true, + "labs": true, + "lakebox": true, + "pipelines": true, + "psql": true, + "selftest": true, + "ssh": true, + "version": true, + "help": true, + } + return !manualRoots[leaf.path[0]] +} + +// fuzzableLeaves returns the auto-generated leaves we want to fuzz. +func (h *fuzzHarness) fuzzableLeaves() []leafCommand { + var out []leafCommand + for _, leaf := range h.leaves { + if isAutoGenerated(leaf) { + out = append(out, leaf) + } + } + return out +} + +// TestCountFuzz count-fuzzes every auto-generated workspace/account command. +// For each leaf it invokes RunE with positional-arg counts from 0 to +// declared+1. Guards against codegen regressions like +// https://github.com/databricks/cli/issues/5070, where interactive fallbacks +// in auto-generated commands access positional args out of bounds. +func TestCountFuzz(t *testing.T) { + h := newFuzzHarness(t) + leaves := h.fuzzableLeaves() + + for _, leaf := range leaves { + t.Run(sanitizeTestName(leafPathName(leaf)), func(t *testing.T) { + t.Parallel() + for n := 0; n <= leaf.declaredArgs+1; n++ { + args := make([]string, n) + for i := range args { + args[i] = "x" + } + h.run(t, leaf, args) + } + }) + } +} diff --git a/cmd/labs/github/github.go b/cmd/labs/github/github.go index d875b48cbaa..4251bc3e1e7 100644 --- a/cmd/labs/github/github.go +++ b/cmd/labs/github/github.go @@ -56,7 +56,7 @@ func getPagedBytes(ctx context.Context, method, url string, body io.Reader) (*pa url = strings.Replace(url, gitHubUserContent, uco, 1) } log.Tracef(ctx, "%s %s", method, url) - req, err := http.NewRequestWithContext(ctx, "GET", url, body) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, body) if err != nil { return nil, err } @@ -64,7 +64,7 @@ func getPagedBytes(ctx context.Context, method, url string, body io.Reader) (*pa if err != nil { return nil, err } - if res.StatusCode == 404 { + if res.StatusCode == http.StatusNotFound { return nil, ErrNotFound } if res.StatusCode >= 400 { @@ -90,8 +90,8 @@ func parseNextLink(linkHeader string) string { // https://docs.github.com/en/rest/using-the-rest-api/using-pagination-in-the-rest-api?apiVersion=2022-11-28#using-link-headers // An example link header to handle: // link: ; rel="prev", ; rel="next", ; rel="last", ; rel="first" - links := strings.Split(linkHeader, ",") - for _, link := range links { + links := strings.SplitSeq(linkHeader, ",") + for link := range links { parts := strings.Split(strings.TrimSpace(link), ";") if len(parts) != 2 { continue diff --git a/cmd/labs/project/entrypoint.go b/cmd/labs/project/entrypoint.go index 335f7c1301a..1ffb4a8aaf8 100644 --- a/cmd/labs/project/entrypoint.go +++ b/cmd/labs/project/entrypoint.go @@ -205,7 +205,7 @@ func (e *Entrypoint) getLoginConfig(cmd *cobra.Command) (*loginConfig, *config.C b := root.TryConfigureBundle(cmd) if b != nil { log.Infof(ctx, "Using login configuration from Databricks Asset Bundle") - return &loginConfig{}, b.WorkspaceClient().Config, nil + return &loginConfig{}, b.WorkspaceClient(ctx).Config, nil } } log.Debugf(ctx, "Using workspace-level login profile: %s", lc.WorkspaceProfile) diff --git a/cmd/labs/project/fetcher.go b/cmd/labs/project/fetcher.go index 7f240ab010d..c6969ae1ba7 100644 --- a/cmd/labs/project/fetcher.go +++ b/cmd/labs/project/fetcher.go @@ -9,8 +9,8 @@ import ( "strings" "github.com/databricks/cli/cmd/labs/github" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" - "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -64,7 +64,7 @@ func NewInstaller(cmd *cobra.Command, name string, offlineInstall bool) (install if err != nil { return nil, fmt.Errorf("load: %w", err) } - cmd.PrintErrln(color.YellowString("Installing %s in development mode from %s", prj.Name, wd)) + cmd.PrintErrln(cmdio.Yellow(cmd.Context(), fmt.Sprintf("Installing %s in development mode from %s", prj.Name, wd))) return &devInstallation{ Project: prj, Command: cmd, @@ -141,7 +141,7 @@ func (f *fetcher) checkReleasedVersions(cmd *cobra.Command, version string, offl log.Debugf(ctx, "Latest %s version is: %s", f.name, versions[0].Version) return versions[0].Version, nil } - cmd.PrintErrln(color.YellowString("[WARNING] Installing unreleased version: %s", version)) + cmd.PrintErrln(cmdio.Yellow(ctx, "[WARNING] Installing unreleased version: "+version)) return version, nil } diff --git a/cmd/labs/project/installer.go b/cmd/labs/project/installer.go index b3fc0471647..32a74b6808f 100644 --- a/cmd/labs/project/installer.go +++ b/cmd/labs/project/installer.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "io/fs" "os" "strings" @@ -19,7 +20,6 @@ import ( "github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/databricks/databricks-sdk-go/service/sql" - "github.com/fatih/color" "github.com/spf13/cobra" ) @@ -110,7 +110,7 @@ func (i *installer) Install(ctx context.Context) error { } } - if _, err := os.Stat(i.LibDir()); os.IsNotExist(err) { + if _, err := os.Stat(i.LibDir()); errors.Is(err, fs.ErrNotExist) { return fmt.Errorf("no local installation found: %w", err) } err = i.setupPythonVirtualEnvironment(ctx, w) @@ -151,8 +151,8 @@ func (i *installer) Upgrade(ctx context.Context) error { return nil } -func (i *installer) warningf(text string, v ...any) { - i.cmd.PrintErrln(color.YellowString(text, v...)) +func (i *installer) warning(s string) { + i.cmd.PrintErrln(cmdio.Yellow(i.cmd.Context(), s)) } func (i *installer) cleanupLib(ctx context.Context) error { @@ -287,7 +287,7 @@ func (i *installer) installPythonDependencies(ctx context.Context, spec string) process.WithCombinedOutput(&buf), process.WithDir(libDir)) if err != nil { - i.warningf(buf.String()) + i.warning(buf.String()) return fmt.Errorf("failed to install dependencies of %s", spec) } return nil diff --git a/cmd/labs/project/installer_test.go b/cmd/labs/project/installer_test.go index 86752b7047c..8cfbbf034b3 100644 --- a/cmd/labs/project/installer_test.go +++ b/cmd/labs/project/installer_test.go @@ -384,7 +384,7 @@ func TestInstallerWorksForDevelopment(t *testing.T) { ctx = env.Set(ctx, "DATABRICKS_WAREHOUSE_ID", "efg-id") home, _ := env.UserHomeDir(ctx) - err := os.WriteFile(filepath.Join(home, ".databrickscfg"), []byte(fmt.Sprintf(` + err := os.WriteFile(filepath.Join(home, ".databrickscfg"), fmt.Appendf(nil, ` [profile-one] host = %s token = ... @@ -392,7 +392,7 @@ token = ... [acc] host = %s account_id = abc - `, server.URL, server.URL)), ownerRW) + `, server.URL, server.URL), ownerRW) require.NoError(t, err) // We have the following state at this point: diff --git a/cmd/labs/project/interpreters.go b/cmd/labs/project/interpreters.go index 7bde3d60938..5b6bff16fc2 100644 --- a/cmd/labs/project/interpreters.go +++ b/cmd/labs/project/interpreters.go @@ -1,6 +1,7 @@ package project import ( + "cmp" "context" "errors" "fmt" @@ -8,7 +9,7 @@ import ( "os" "path/filepath" "runtime" - "sort" + "slices" "strings" "github.com/databricks/cli/libs/env" @@ -100,21 +101,19 @@ func DetectInterpreters(ctx context.Context) (allInterpreters, error) { if len(found) == 0 { return nil, ErrNoPythonInterpreters } - sort.Slice(found, func(i, j int) bool { - a := found[i].Version - b := found[j].Version - cmp := semver.Compare(a, b) - if cmp != 0 { - return cmp < 0 + slices.SortFunc(found, func(a, b Interpreter) int { + c := semver.Compare(a.Version, b.Version) + if c != 0 { + return c } - return a < b + return cmp.Compare(a.Version, b.Version) }) return found, nil } func pythonicExecutablesFromPathEnvironment(ctx context.Context) (out []string, err error) { - paths := strings.Split(env.Get(ctx, "PATH"), string(os.PathListSeparator)) - for _, prefix := range paths { + paths := strings.SplitSeq(env.Get(ctx, "PATH"), string(os.PathListSeparator)) + for prefix := range paths { info, err := os.Stat(prefix) if errors.Is(err, fs.ErrNotExist) { // some directories in $PATH may not exist diff --git a/cmd/labs/project/project.go b/cmd/labs/project/project.go index 11bf74c2991..a8228126bdf 100644 --- a/cmd/labs/project/project.go +++ b/cmd/labs/project/project.go @@ -11,11 +11,11 @@ import ( "time" "github.com/databricks/cli/cmd/labs/github" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/python" "github.com/databricks/databricks-sdk-go/logger" - "github.com/fatih/color" "go.yaml.in/yaml/v3" "github.com/spf13/cobra" @@ -318,7 +318,7 @@ func (p *Project) checkUpdates(cmd *cobra.Command) error { } ago := time.Since(latest.PublishedAt) msg := "[UPGRADE ADVISED] Newer %s version was released %s ago. Please run `databricks labs upgrade %s` to upgrade: %s -> %s" - cmd.PrintErrln(color.YellowString(msg, p.Name, p.timeAgo(ago), p.Name, installed.Version, latest.Version)) + cmd.PrintErrln(cmdio.Yellow(ctx, fmt.Sprintf(msg, p.Name, p.timeAgo(ago), p.Name, installed.Version, latest.Version))) return nil } diff --git a/cmd/labs/project/testdata/installed-in-home/.databrickscfg b/cmd/labs/project/testdata/installed-in-home/.databrickscfg index ec1bf7bdcf2..0906ec9d729 100644 --- a/cmd/labs/project/testdata/installed-in-home/.databrickscfg +++ b/cmd/labs/project/testdata/installed-in-home/.databrickscfg @@ -1,5 +1,5 @@ [workspace-profile] -host = https://abc +host = https://abc.test token = bcd cluster_id = cde warehouse_id = def diff --git a/cmd/labs/unpack/zipball.go b/cmd/labs/unpack/zipball.go index a235bf90966..4a1181a69e7 100644 --- a/cmd/labs/unpack/zipball.go +++ b/cmd/labs/unpack/zipball.go @@ -3,6 +3,7 @@ package unpack import ( "archive/zip" "bytes" + "errors" "fmt" "io" "os" @@ -25,6 +26,9 @@ func (v GitHubZipball) UnpackTo(libTarget string) error { if err != nil { return fmt.Errorf("zip: %w", err) } + if len(zipReader.File) == 0 { + return errors.New("empty zip archive") + } // GitHub packages entire repo contents into a top-level folder, e.g. databrickslabs-ucx-2800c6b rootDirInZIP := zipReader.File[0].Name for _, zf := range zipReader.File { @@ -32,7 +36,14 @@ func (v GitHubZipball) UnpackTo(libTarget string) error { continue } normalizedName := strings.TrimPrefix(zf.Name, rootDirInZIP) + if filepath.IsAbs(normalizedName) || strings.Contains(normalizedName, `\`) { + return fmt.Errorf("invalid zip entry name: %q", zf.Name) + } targetName := filepath.Join(libTarget, normalizedName) + rel, err := filepath.Rel(libTarget, targetName) + if err != nil || strings.HasPrefix(rel, "..") { + return fmt.Errorf("zip entry escapes target directory: %q", zf.Name) + } if zf.FileInfo().IsDir() { err = os.MkdirAll(targetName, ownerRWXworldRX) if err != nil { @@ -54,7 +65,7 @@ func (v GitHubZipball) extractFile(zf *zip.File, targetName string) error { return fmt.Errorf("source: %w", err) } defer reader.Close() - writer, err := os.OpenFile(targetName, os.O_CREATE|os.O_RDWR, zf.Mode()) + writer, err := os.OpenFile(targetName, os.O_CREATE|os.O_WRONLY, zf.Mode()&0o755) if err != nil { return fmt.Errorf("target: %w", err) } diff --git a/cmd/lakebox/api.go b/cmd/lakebox/api.go new file mode 100644 index 00000000000..241c27f7111 --- /dev/null +++ b/cmd/lakebox/api.go @@ -0,0 +1,328 @@ +package lakebox + +import ( + "context" + "fmt" + "net/http" + "strings" + "time" + + "github.com/databricks/cli/libs/auth" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/client" +) + +// Sandboxes live under the `/sandboxes` sub-collection of the lakebox service +// namespace (see `lakebox.proto` `LakeboxService.CreateSandbox`). +const lakeboxAPIPath = "/api/2.0/lakebox/sandboxes" + +// SSH keys are nested under the lakebox service namespace alongside +// `sandboxes/` (see `LakeboxService.CreateSshKey`). +const lakeboxKeysAPIPath = "/api/2.0/lakebox/ssh-keys" + +// orgIDHeader is sent by multi-workspace gateways (e.g. dogfood staging) so +// the gateway can scope the credential to a specific workspace. Without it, +// requests fail with "Credential was not sent or was of an unsupported type +// for this API." +const orgIDHeader = "X-Databricks-Org-Id" + +// lakeboxAPI wraps the SDK ApiClient with workspace-id-aware request headers. +type lakeboxAPI struct { + c *client.DatabricksClient +} + +// sandboxCreateBody is the inner `Sandbox` message in the create payload. +// Only `name` is caller-settable today; all other fields are server-chosen. +type sandboxCreateBody struct { + Name string `json:"name,omitempty"` +} + +// createRequest is the JSON body for POST /api/2.0/lakebox/sandboxes. +// `CreateSandboxRequest { Sandbox sandbox = 1 }` has `body: "*"`, so the +// wire body is the full request with a `sandbox` wrapper. +type createRequest struct { + Sandbox sandboxCreateBody `json:"sandbox"` +} + +// createResponse is the JSON body returned by POST /api/2.0/lakebox/sandboxes. +// Mirrors the `Sandbox` proto message after JSON transcoding. +// +// `FQDN` is the manager's internal routing hostname — not user-actionable, +// SSH always goes through the gateway. Tagged `omitempty` so the day the +// manager stops returning it, both this struct and downstream `--json` +// output drop the field cleanly instead of leaking a ghost empty string. +type createResponse struct { + SandboxID string `json:"sandboxId"` + Status string `json:"status"` + FQDN string `json:"fqdn,omitempty"` +} + +// sandboxEntry is a single item in the list response. +// Mirrors the `Sandbox` proto message after JSON transcoding. +// +// IdleTimeout and NoAutostop correspond to the proto's `optional` fields; +// they're pointers so we can tell "field absent on the wire" (server has +// the global default) from "explicitly set to 0 / false." +// +// `IdleTimeout` is a `google.protobuf.Duration`. Proto3 JSON canonical +// form serializes Duration as a string with an `s` suffix (e.g. +// `"900s"`), so the Go field is `*string` and we parse on read. +type sandboxEntry struct { + SandboxID string `json:"sandboxId"` + Status string `json:"status"` + FQDN string `json:"fqdn,omitempty"` + Name string `json:"name,omitempty"` + CreateTime string `json:"createTime,omitempty"` + LastStartTime string `json:"lastStartTime,omitempty"` + IdleTimeout *string `json:"idleTimeout,omitempty"` + NoAutostop *bool `json:"noAutostop,omitempty"` +} + +// idleTimeoutSecs parses the proto3-canonical Duration string off +// `IdleTimeout` (e.g. `"900s"` → `900`). Returns 0 when unset or when +// the string is not a recognizable Duration. Sub-second precision is +// dropped — the watchdog only acts on whole seconds. +func (e *sandboxEntry) idleTimeoutSecs() int64 { + if e.IdleTimeout == nil { + return 0 + } + s := *e.IdleTimeout + if !strings.HasSuffix(s, "s") { + return 0 + } + d, err := time.ParseDuration(s) + if err != nil { + return 0 + } + return int64(d.Seconds()) +} + +// defaultAutoStopSecs mirrors the manager's `watchdog_idle_grace_secs` +// fallback (10 minutes) used when a sandbox has no per-record override. +// The value is also documented in `lakebox/CLAUDE.md` ("Sandbox +// Watchdog" section). Hardcoded here so list/status can render the +// effective timeout without an extra round-trip to fetch manager config. +const defaultAutoStopSecs int64 = 600 + +// autoStopLabel renders the auto-stop policy advertised by the manager +// for one sandbox into a short human-readable string. Mirrors the wire +// semantics from `lakebox/proto/lakebox.proto`: +// - `no_autostop == true` → never auto-stops +// - `idle_timeout` set and positive → that many seconds +// - otherwise → manager's global default (`defaultAutoStopSecs`) +func (e *sandboxEntry) autoStopLabel() string { + if e.NoAutostop != nil && *e.NoAutostop { + return "never" + } + if secs := e.idleTimeoutSecs(); secs > 0 { + return formatDurationSecs(secs) + } + return formatDurationSecs(defaultAutoStopSecs) +} + +// formatDurationSecs prints `secs` as a compact duration (e.g. `90s`, +// `15m`, `2h`, `1h30m`). Falls back to seconds if it's not a clean +// minute/hour multiple. Avoids pulling in a dependency just for this. +func formatDurationSecs(secs int64) string { + if secs < 60 { + return fmt.Sprintf("%ds", secs) + } + if secs%3600 == 0 { + return fmt.Sprintf("%dh", secs/3600) + } + if secs >= 3600 { + return fmt.Sprintf("%dh%dm", secs/3600, (secs%3600)/60) + } + if secs%60 == 0 { + return fmt.Sprintf("%dm", secs/60) + } + return fmt.Sprintf("%ds", secs) +} + +// listResponse is the JSON body returned by GET /api/2.0/lakebox/sandboxes. +// `nextPageToken` is empty on the final page (or when the result fits in one). +type listResponse struct { + Sandboxes []sandboxEntry `json:"sandboxes"` + NextPageToken string `json:"nextPageToken,omitempty"` +} + +// listPageSize matches the manager-side default. Typical user fleets are +// well under this, so one round-trip covers them; the pagination loop in +// `list` handles the rare larger fleet. +const listPageSize = 100 + +// updateBody is the PATCH request body. The proto declares +// `UpdateSandboxRequest { Sandbox sandbox = 1 }` with `body: "sandbox"` +// in the (google.api.http) annotation, so the HTTP body is the inner +// `Sandbox` message directly — there is no `{"sandbox": {...}}` +// wrapping on the wire. +// +// Pointer fields encode the proto3 `optional` semantics — only the +// fields we explicitly set are emitted, leaving everything else +// server-untouched. `IdleTimeout` is a proto3-canonical Duration +// string (e.g. `"900s"`); the server-side wire type is +// `google.protobuf.Duration`. +type updateBody struct { + SandboxID string `json:"sandbox_id"` + Name *string `json:"name,omitempty"` + IdleTimeout *string `json:"idle_timeout,omitempty"` + NoAutostop *bool `json:"no_autostop,omitempty"` +} + +// registerKeyRequest is the JSON body for POST /api/2.0/lakebox/ssh-keys. +type registerKeyRequest struct { + PublicKey string `json:"public_key"` + Name string `json:"name,omitempty"` +} + +func newLakeboxAPI(w *databricks.WorkspaceClient) (*lakeboxAPI, error) { + c, err := client.New(w.Config) + if err != nil { + return nil, fmt.Errorf("failed to create lakebox API client: %w", err) + } + return &lakeboxAPI{c: c}, nil +} + +// headers attaches the workspace routing identifier so multi-workspace +// gateways (e.g. SPOG hosts) can scope the credential. Mirrors the pattern +// in libs/telemetry, libs/filer, and SDK-generated workspace services. The +// auth.WorkspaceIDNone sentinel ("none") is treated as unset so the literal +// string never goes on the wire. +func (a *lakeboxAPI) headers() map[string]string { + wsID := a.c.Config.WorkspaceID + if wsID == "" || wsID == auth.WorkspaceIDNone { + return nil + } + return map[string]string{orgIDHeader: wsID} +} + +// create calls POST /api/2.0/lakebox/sandboxes. An empty `name` is omitted +// from the wire payload so the server treats it as "unset" rather than +// "explicit empty string." +func (a *lakeboxAPI) create(ctx context.Context, name string) (*createResponse, error) { + body := createRequest{Sandbox: sandboxCreateBody{Name: name}} + var resp createResponse + err := a.c.Do(ctx, http.MethodPost, lakeboxAPIPath, a.headers(), nil, body, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// list calls GET /api/2.0/lakebox/sandboxes, following pagination until the +// server stops sending `next_page_token`. Returns the full set in one slice. +func (a *lakeboxAPI) list(ctx context.Context) ([]sandboxEntry, error) { + var all []sandboxEntry + pageToken := "" + for { + page, err := a.listPage(ctx, pageToken) + if err != nil { + return nil, err + } + all = append(all, page.Sandboxes...) + if page.NextPageToken == "" { + return all, nil + } + pageToken = page.NextPageToken + } +} + +// listPage fetches a single page of sandboxes. An empty `pageToken` requests +// the first page; the server enforces ordering across pages. +// +// `query` is passed in slot 6 (`request`), not slot 5 (`queryParams`). On +// GET, the SDK's makeRequestBody serializes `request` into the URL query +// string and sends an empty body. Routing through `queryParams` instead +// makes it write a literal `null` body, which the lakebox manager rejects +// with `INVALID_PARAMETER_VALUE: Request body must be a JSON object`. See +// databricks-sdk-go/httpclient/request.go:makeRequestBody. +func (a *lakeboxAPI) listPage(ctx context.Context, pageToken string) (*listResponse, error) { + query := map[string]any{"page_size": listPageSize} + if pageToken != "" { + query["page_token"] = pageToken + } + var resp listResponse + err := a.c.Do(ctx, http.MethodGet, lakeboxAPIPath, a.headers(), nil, query, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// get calls GET /api/2.0/lakebox/sandboxes/{id}. +func (a *lakeboxAPI) get(ctx context.Context, id string) (*sandboxEntry, error) { + var resp sandboxEntry + err := a.c.Do(ctx, http.MethodGet, lakeboxAPIPath+"/"+id, a.headers(), nil, nil, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// update calls PATCH /api/2.0/lakebox/sandboxes/{id} with whichever of +// `idle_timeout` / `no_autostop` the caller chose to set. Fields left +// nil are omitted from the wire payload, so the server preserves their +// current values. Returns the refreshed `sandboxEntry`. +func (a *lakeboxAPI) update(ctx context.Context, id string, name *string, idleTimeoutSecs *int64, noAutostop *bool) (*sandboxEntry, error) { + var idleTimeout *string + if idleTimeoutSecs != nil { + s := fmt.Sprintf("%ds", *idleTimeoutSecs) + idleTimeout = &s + } + body := updateBody{ + SandboxID: id, + Name: name, + IdleTimeout: idleTimeout, + NoAutostop: noAutostop, + } + var resp sandboxEntry + err := a.c.Do(ctx, http.MethodPatch, lakeboxAPIPath+"/"+id, a.headers(), nil, body, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +// delete calls DELETE /api/2.0/lakebox/sandboxes/{id}. +func (a *lakeboxAPI) delete(ctx context.Context, id string) error { + return a.c.Do(ctx, http.MethodDelete, lakeboxAPIPath+"/"+id, a.headers(), nil, nil, nil) +} + +// registerKey calls POST /api/2.0/lakebox/ssh-keys. An empty `name` is +// omitted from the wire payload so the server records "unset" rather than +// an explicit empty string. +func (a *lakeboxAPI) registerKey(ctx context.Context, publicKey, name string) error { + return a.c.Do(ctx, http.MethodPost, lakeboxKeysAPIPath, a.headers(), nil, registerKeyRequest{PublicKey: publicKey, Name: name}, nil) +} + +// sshKeyEntry is a single item in the ssh-key list response. Mirrors the +// `SshKey` proto message after JSON transcoding (`key_hash` → `keyHash`, +// timestamps as RFC 3339 strings). +type sshKeyEntry struct { + KeyHash string `json:"keyHash"` + Name string `json:"name,omitempty"` + CreateTime string `json:"createTime,omitempty"` + LastUseTime string `json:"lastUseTime,omitempty"` +} + +// listKeysResponse is the JSON body returned by GET /api/2.0/lakebox/ssh-keys. +// Per-user keys are hard-capped at 100 server-side, so the full set fits in +// one response — no pagination. +type listKeysResponse struct { + SshKeys []sshKeyEntry `json:"sshKeys"` +} + +// listKeys calls GET /api/2.0/lakebox/ssh-keys. +func (a *lakeboxAPI) listKeys(ctx context.Context) ([]sshKeyEntry, error) { + var resp listKeysResponse + err := a.c.Do(ctx, http.MethodGet, lakeboxKeysAPIPath, a.headers(), nil, nil, &resp) + if err != nil { + return nil, err + } + return resp.SshKeys, nil +} + +// deleteKey calls DELETE /api/2.0/lakebox/ssh-keys/{key_hash}. +func (a *lakeboxAPI) deleteKey(ctx context.Context, keyHash string) error { + return a.c.Do(ctx, http.MethodDelete, lakeboxKeysAPIPath+"/"+keyHash, a.headers(), nil, nil, nil) +} diff --git a/cmd/lakebox/config.go b/cmd/lakebox/config.go new file mode 100644 index 00000000000..9f1ef6429a5 --- /dev/null +++ b/cmd/lakebox/config.go @@ -0,0 +1,152 @@ +package lakebox + +import ( + "errors" + "fmt" + "time" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +// MIN_IDLE_TIMEOUT_SECS / MAX_IDLE_TIMEOUT_SECS mirror the manager-side +// constants in lakebox/src/api/handlers/sandbox.rs. Pre-flighting client-side +// gives a clearer error than waiting for the server's INVALID_ARGUMENT. +const ( + minIdleTimeoutSecs = 60 + maxIdleTimeoutSecs = 86_400 +) + +func newConfigCommand() *cobra.Command { + var idleTimeoutFlag string + var noAutostopFlag bool + var nameFlag string + + cmd := &cobra.Command{ + Use: "config ", + Short: "Configure a Lakebox's name and auto-stop policy", + Long: `Configure a Lakebox's name and auto-stop policy. + +Three knobs are independent — pass any combination: + + --name