diff --git a/.agent/skills/bump-cli-compat/SKILL.md b/.agent/skills/bump-cli-compat/SKILL.md new file mode 100644 index 00000000000..a0612a2ba0e --- /dev/null +++ b/.agent/skills/bump-cli-compat/SKILL.md @@ -0,0 +1,153 @@ +--- +name: bump-cli-compat +description: "Bump cli-compat.json with new AppKit and Agent Skills versions, then create a PR. Use when the user says 'bump cli-compat', 'update cli-compat', 'bump compatibility manifest', 'new appkit release cli-compat', or wants to update the CLI compatibility manifest after an AppKit or Agent Skills release." +user-invocable: true +allowed-tools: Read, Edit, Write, Bash, Glob, Grep, AskUserQuestion +--- + +# Bump CLI Compatibility Manifest + +Updates `internal/build/cli-compat.json` with new AppKit and Agent Skills versions, validates the result, and creates a PR. + +## Arguments + +Parse the user's input for optional named flags: + +- `--appkit ` → AppKit version (e.g. `0.28.0`) +- `--skills ` → Agent Skills version (e.g. `0.1.6`) +- No args → auto-detect latest versions from GitHub tags + +Versions should be provided **without** the `v` prefix (e.g. `0.28.0`, not `v0.28.0`). If provided with the prefix, strip it. + +## Workflow + +### Step 1: Resolve versions + +If both `--appkit` and `--skills` versions were provided, skip to Step 2. + +For any missing version, fetch the latest tag from GitHub: + +```bash +# Latest appkit version (strip leading 'v') +gh api repos/databricks/appkit/tags --jq '.[0].name' | sed 's/^v//' + +# Latest skills version (strip leading 'v') +gh api repos/databricks/databricks-agent-skills/tags --jq '.[0].name' | sed 's/^v//' +``` + +Show the resolved versions to the user and ask: + +> The latest versions are: +> - AppKit: `{appkit_version}` +> - Agent Skills: `{skills_version}` +> +> Have these versions been evaluated (evals passed with no regressions)? + +**Do NOT proceed until the user confirms.** If the user says no or wants different versions, ask them to provide the correct versions. + +### Step 2: Validate tags exist + +Verify that the corresponding Git tags exist on GitHub. For AppKit, also validate the `template-v` tag (used by `apps init`): + +```bash +# AppKit release tag +gh api repos/databricks/appkit/git/ref/tags/v{appkit_version} --jq '.ref' + +# AppKit template tag (used by apps init) +gh api repos/databricks/appkit/git/ref/tags/template-v{appkit_version} --jq '.ref' + +# Agent Skills tag +gh api repos/databricks/databricks-agent-skills/git/ref/tags/v{skills_version} --jq '.ref' +``` + +If any tag doesn't exist, report the error and stop. + +### Step 3: Read current manifest + +Read `internal/build/cli-compat.json`. Note the current versions and the list of versioned entries. + +### Step 4: Determine update type + +Ask the user: + +> Do any of these apply? +> - **AppKit**: The new templates require new CLI logic in `apps init` (e.g. new flags, prompts, or template handling that older CLIs don't have) +> - **Skills**: The new skills version uses CLI commands that older CLIs don't support +> +> If **neither** applies, this is a non-breaking bump (default). + +- **No breaking changes** (default): proceed to Step 4a. +- **Breaking changes**: proceed to Step 4b. + +### Step 4a: No breaking changes (update in-place) + +Update the **highest versioned entry** to the new appkit and skills versions. Do NOT add new versioned keys. The manifest is range-based: updating the highest entry automatically covers all CLI versions in that range. + +Write the updated `internal/build/cli-compat.json`. + +### Step 4b: Breaking changes (add new entry) + +Ask the user for the **minimum CLI version** that supports the new features. + +Add a new entry keyed to that CLI version with the new appkit and skills versions. Keep older entries unchanged so older CLI binaries stay compatible. + +Write the updated `internal/build/cli-compat.json`. + +### Step 5: Validate + +Run the Go tests to ensure the manifest is well-formed: + +```bash +go test ./libs/clicompat/... -run TestEmbeddedManifest -v +``` + +If validation fails, show the errors and fix them before proceeding. + +### Step 6: Create branch, commit, and PR + +```bash +# Create a new branch from the current branch (or main) +git checkout -b bump-cli-compat-appkit-{appkit_version}-skills-{skills_version} + +# Stage and commit +git add internal/build/cli-compat.json +git commit -s -m "Bump cli-compat to appkit {appkit_version}, skills {skills_version}" + +# Push and create PR +git push -u origin HEAD +gh pr create \ + --title "Bump cli-compat to appkit {appkit_version}, skills {skills_version}" \ + --body "$(cat <<'EOF' +## Summary +Bump `cli-compat.json` to use: +- AppKit `{appkit_version}` +- Agent Skills `{skills_version}` + +## Checklist +- [ ] Evals passed with no regressions +- [ ] `go test ./libs/clicompat/... -run TestEmbeddedManifest` passes +EOF +)" +``` + +Show the PR URL to the user when done. + +## Examples + +### Example: With explicit versions +``` +/bump-cli-compat --appkit 0.28.0 --skills 0.1.6 +``` +Validates tags exist (including `template-v0.28.0`), updates manifest, creates PR. + +### Example: Auto-detect latest +``` +/bump-cli-compat +``` +Fetches latest tags, asks for eval confirmation, then updates and creates PR. + +### Example: Only bump AppKit +``` +/bump-cli-compat --appkit 0.28.0 +``` +Auto-detects latest skills version, asks for confirmation, then updates both. diff --git a/.agent/skills/pr-checklist/SKILL.md b/.agent/skills/pr-checklist/SKILL.md index 3eae258d6e7..e01058a0120 100644 --- a/.agent/skills/pr-checklist/SKILL.md +++ b/.agent/skills/pr-checklist/SKILL.md @@ -16,14 +16,15 @@ Before submitting a PR, run these commands to match what CI checks. CI uses the # 3. Tests (CI runs with both deployment engines) ./task test -# 4. If you changed bundle config structs or schema-related code: +# 4. If you changed bundle config structs, schema, or direct-engine resource code: ./task generate-schema +./task generate-direct # 5. If you changed files in python/: ./task pydabs-codegen pydabs-test pydabs-lint pydabs-docs -# 6. If you changed experimental/aitools or experimental/ssh: -./task test-exp-aitools # only if aitools code changed +# 6. If you changed cmd/aitools/, libs/aitools/, experimental/aitools/, or experimental/ssh/: +./task test-exp-aitools # only if aitools code changed (top-level or experimental) ./task test-exp-ssh # only if ssh code changed ``` diff --git a/.codegen/_openapi_sha b/.codegen/_openapi_sha index 15378345074..0373270428a 100644 --- a/.codegen/_openapi_sha +++ b/.codegen/_openapi_sha @@ -1 +1 @@ -11ae6f9d98f0d0838a5e53c27032f178fecc4ee0 \ No newline at end of file +0555d6a59265799ed8ea12f355eee662e739430d \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index ffe19eb43c4..595b0dba8c2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ cmd/account/credentials/credentials.go linguist-generated=true cmd/account/csp-enablement-account/csp-enablement-account.go linguist-generated=true cmd/account/custom-app-integration/custom-app-integration.go linguist-generated=true cmd/account/disable-legacy-features/disable-legacy-features.go linguist-generated=true +cmd/account/disaster-recovery/disaster-recovery.go linguist-generated=true cmd/account/enable-ip-access-lists/enable-ip-access-lists.go linguist-generated=true cmd/account/encryption-keys/encryption-keys.go linguist-generated=true cmd/account/endpoints/endpoints.go linguist-generated=true @@ -53,6 +54,7 @@ cmd/workspace/apps-settings/apps-settings.go linguist-generated=true cmd/workspace/apps/apps.go linguist-generated=true cmd/workspace/artifact-allowlists/artifact-allowlists.go linguist-generated=true cmd/workspace/automatic-cluster-update/automatic-cluster-update.go linguist-generated=true +cmd/workspace/bundle/bundle.go linguist-generated=true cmd/workspace/catalogs/catalogs.go linguist-generated=true cmd/workspace/clean-room-asset-revisions/clean-room-asset-revisions.go linguist-generated=true cmd/workspace/clean-room-assets/clean-room-assets.go linguist-generated=true @@ -151,6 +153,7 @@ cmd/workspace/resource-quotas/resource-quotas.go linguist-generated=true cmd/workspace/restrict-workspace-admins/restrict-workspace-admins.go linguist-generated=true cmd/workspace/rfa/rfa.go linguist-generated=true cmd/workspace/schemas/schemas.go linguist-generated=true +cmd/workspace/secrets-uc/secrets-uc.go linguist-generated=true cmd/workspace/secrets/secrets.go linguist-generated=true cmd/workspace/service-principal-secrets-proxy/service-principal-secrets-proxy.go linguist-generated=true cmd/workspace/service-principals-v2/service-principals-v2.go linguist-generated=true @@ -159,12 +162,14 @@ cmd/workspace/settings/settings.go linguist-generated=true cmd/workspace/shares/shares.go linguist-generated=true cmd/workspace/sql-results-download/sql-results-download.go linguist-generated=true cmd/workspace/storage-credentials/storage-credentials.go linguist-generated=true +cmd/workspace/supervisor-agents/supervisor-agents.go linguist-generated=true cmd/workspace/system-schemas/system-schemas.go linguist-generated=true cmd/workspace/table-constraints/table-constraints.go linguist-generated=true cmd/workspace/tables/tables.go linguist-generated=true cmd/workspace/tag-policies/tag-policies.go linguist-generated=true cmd/workspace/temporary-path-credentials/temporary-path-credentials.go linguist-generated=true cmd/workspace/temporary-table-credentials/temporary-table-credentials.go linguist-generated=true +cmd/workspace/temporary-volume-credentials/temporary-volume-credentials.go linguist-generated=true cmd/workspace/token-management/token-management.go linguist-generated=true cmd/workspace/tokens/tokens.go linguist-generated=true cmd/workspace/users-v2/users-v2.go linguist-generated=true diff --git a/.github/OWNERS b/.github/OWNERS index 7cae525465a..cf8464e99e7 100644 --- a/.github/OWNERS +++ b/.github/OWNERS @@ -59,5 +59,13 @@ # Internal /internal/ team:platform +# AI tools +/cmd/aitools/ team:eng-apps-devex team:platform @lennartkats-db +/libs/aitools/ team:eng-apps-devex team:platform @lennartkats-db + +# CLI compatibility manifest +/internal/build/cli-compat.json team:eng-apps-devex team:platform +/libs/clicompat/ team:eng-apps-devex team:platform + # Experimental /experimental/aitools/ team:eng-apps-devex @lennartkats-db diff --git a/.github/actions/setup-build-environment/action.yml b/.github/actions/setup-build-environment/action.yml index 58ff10d8b34..3fd492c70f6 100644 --- a/.github/actions/setup-build-environment/action.yml +++ b/.github/actions/setup-build-environment/action.yml @@ -33,7 +33,7 @@ runs: python-version: '3.13' - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.8.9" diff --git a/.github/workflows/bump-go-toolchain.yml b/.github/workflows/bump-go-toolchain.yml index 1429a2c157a..080a15e5f9f 100644 --- a/.github/workflows/bump-go-toolchain.yml +++ b/.github/workflows/bump-go-toolchain.yml @@ -93,7 +93,7 @@ jobs: - name: Create pull request if: steps.check.outputs.needed == 'true' && inputs.version == '' - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + 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 }}" diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index bac8ff33e4a..5bff4cf9935 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -45,7 +45,7 @@ jobs: args: "format --check" - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.8.9" diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 0365591183b..2be5811c93c 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -88,6 +88,9 @@ jobs: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -150,8 +153,15 @@ jobs: ENVFILTER: DATABRICKS_BUNDLE_ENGINE=${{ matrix.deployment }} run: go tool -modfile=tools/task/go.mod task cover - - name: Analyze slow tests - run: go tool -modfile=tools/task/go.mod task 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 run: | @@ -228,6 +238,9 @@ jobs: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -277,6 +290,9 @@ jobs: id-token: write contents: read + env: + TASK_CONCURRENCY: ${{ matrix.os.name == 'windows' && '1' || '' }} + strategy: fail-fast: false matrix: @@ -381,7 +397,7 @@ jobs: go-version-file: go.mod - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" diff --git a/.github/workflows/python_push.yml b/.github/workflows/python_push.yml index e3d3ce3bca2..be62bcd22be 100644 --- a/.github/workflows/python_push.yml +++ b/.github/workflows/python_push.yml @@ -38,7 +38,7 @@ jobs: go-version-file: go.mod - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: python-version: ${{ matrix.pyVersion }} version: "0.6.5" @@ -60,7 +60,7 @@ jobs: go-version-file: go.mod - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" @@ -81,7 +81,7 @@ jobs: go-version-file: go.mod - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index bf082cc2580..1ebbf6f1859 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -107,7 +107,7 @@ jobs: # 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 ${{ !inputs.publish && '--skip=publish' || '' }} --config .workflow-actions/.goreleaser.yaml --skip=docker ${{ (!startsWith(github.ref, 'refs/tags/') && !inputs.tag) && '--snapshot' || '' }} @@ -128,7 +128,7 @@ jobs: 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: | @@ -170,7 +170,7 @@ jobs: uses: ./.workflow-actions/.github/actions/setup-jfrog - name: Install uv - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: version: "0.6.5" @@ -181,7 +181,7 @@ jobs: 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/tagging.yml b/.github/workflows/tagging.yml index cbb9cfb591c..b1a2e3f93ed 100644 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -4,7 +4,12 @@ name: tagging on: # Manual dispatch. workflow_dispatch: - # No inputs are required for the manual dispatch. + inputs: + packages: + description: 'Comma-separated list of packages to tag (e.g. "pkg1,pkg2"). Leave empty to tag all packages with pending releases.' + required: false + type: string + default: '' # NOTE: Temporarily disable automated releases. # @@ -31,8 +36,8 @@ jobs: github.repository == 'databricks/databricks-sdk-java' environment: "release-is" runs-on: - group: databricks-deco-testing-runner-group - labels: ubuntu-latest-deco + group: databricks-protected-runner-group + labels: linux-ubuntu-latest steps: - name: Generate GitHub App Token id: generate-token @@ -44,6 +49,12 @@ jobs: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + # Force re-resolution of ``main`` at step time. Without + # ``ref:``, checkout pins to ``github.sha`` — the SHA frozen + # at workflow_dispatch time — which means re-running a stale + # dispatch checks out an older main even when newer commits + # exist. + ref: main fetch-depth: 0 token: ${{ steps.generate-token.outputs.token }} @@ -60,4 +71,18 @@ jobs: env: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} - run: uv run --locked internal/genkit/tagging.py + PACKAGES: ${{ inputs.packages }} + run: | + if [ -n "$PACKAGES" ]; then + uv run --locked internal/genkit/tagging.py --package "$PACKAGES" + else + uv run --locked internal/genkit/tagging.py + fi + + - name: Upload created tags artifact + if: always() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: created-tags + path: created_tags.json + if-no-files-found: ignore 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/.release_metadata.json b/.release_metadata.json index a17521e3b54..5c5dcbc1b1e 100644 --- a/.release_metadata.json +++ b/.release_metadata.json @@ -1,3 +1,3 @@ { - "timestamp": "2026-04-29 13:09:01+0000" + "timestamp": "2026-05-21 08:07:33+0000" } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index 85b27748a66..3cdcc3406ad 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -123,6 +123,8 @@ parentPath = "/Workspace" + parentPath **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 diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3ff24965c..9f982fa676d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,75 @@ # Version changelog +## Release v1.0.0 (2026-05-21) + +### Notable Changes + +* The Databricks CLI is now generally available with version v1.0.0 as the first major release 🚀. From this version on, the CLI follows semantic versioning (see [README](README.md)). This change does not impact DABs or other existing commands beyond the changes listed below. +* The 0.299.x line continues to receive security-critical patches through May 20, 2027; see [SECURITY](SECURITY.md) for the support policy. +* Starting with v1.0.0, the CLI will use [immutable release tags](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases) to increase security against supply chain attacks. +* Breaking change: OAuth tokens for interactive logins (`auth_type = databricks-cli`) are now stored in the OS-native secure store by default (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux) instead of `~/.databricks/token-cache.json`. After upgrading, run `databricks auth login` once per profile to re-authenticate; cached tokens from older versions are not migrated. To keep the previous file-backed storage, set `DATABRICKS_AUTH_STORAGE=plaintext` or add `auth_storage = plaintext` under `[__settings__]` in `~/.databrickscfg` (the env var takes precedence over the config setting), then re-run `databricks auth login`. On systems where the OS keyring is not reachable (e.g. Linux containers without a D-Bus session bus), the CLI transparently falls back to the file cache when reading tokens so legacy `token-cache.json` entries remain accessible without manual configuration. + +### CLI + +* Added `databricks aitools` command group for installing Databricks skills into your coding agents (Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity). Skills are fetched from [github.com/databricks/databricks-agent-skills](https://github.com/databricks/databricks-agent-skills) and either symlinked into each agent's skills directory or copied into the current project. Use `databricks aitools install` to set up, `update` to pull newer versions, `list` to see what's available, and `uninstall` to remove them. Pick where they go with `--scope=project|global` (`--scope=both` is accepted on `update` and `list`). +* `[__settings__].default_profile` is now consulted as a fallback by `databricks api`, `databricks auth token`, and bundle commands when neither `--profile` nor `DATABRICKS_CONFIG_PROFILE` is set. `databricks auth token` continues to give precedence to `DATABRICKS_HOST` over `default_profile`. For bundle commands, `default_profile` only applies when the bundle does not pin its own `workspace.host`. +* Fixed bug where auth commands did not load the DEFAULT profile properly during auth where type is `databricks-cli`. +* `databricks workspace import-dir` now skips `.git`, `.databricks`, and `node_modules` directories during recursive imports. To import one of these directories deliberately, pass it as `SOURCE_PATH` ([#5118](https://github.com/databricks/cli/pull/5118)). +* `databricks postgres create-role --help` now documents the `--json` body shape and rejects the common mistake of wrapping the body in `{"role": ...}` client-side with a hint pointing at the correct shape ([#5111](https://github.com/databricks/cli/pull/5111)). +* `databricks aitools list` honors `--output json`, emitting a structured `{release, skills[...], summary{}}` document so coding agents and CI can consume the skill/version/installation matrix without scraping the tabular text output ([#5233](https://github.com/databricks/cli/pull/5233)). + +### Bundles +* Make sure warnings asking for approval are understood by agents ([#5239](https://github.com/databricks/cli/pull/5239)) +* Support `replace_existing: true` on `postgres_branches` and `postgres_endpoints` so bundles can manage the implicitly-created production branch and primary read-write endpoint of a Lakebase project. +* Add `postgres_catalogs` resource to bind a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch ([#5265](https://github.com/databricks/cli/pull/5265)). +* Add `postgres_synced_tables` resource to sync a Unity Catalog Delta table into a Postgres table on a Lakebase Autoscaling branch ([#5268](https://github.com/databricks/cli/pull/5268)). +* engine/direct: Changes to state file now persisted to .wal file right away instead of being saved in the end ([#5149](https://github.com/databricks/cli/pull/5149)) + + +## Release v0.299.2 (2026-05-13) + +### Notable Changes +* Breaking change: `vector_search_endpoints` renamed `min_qps` to `target_qps` in DABs configuration and the `vector-search-endpoints` commands, following the SDK rename in v0.131.0. Update any `databricks.yml` using `min_qps:` to `target_qps:` and any CLI invocations using `--min-qps` to `--target-qps`. + +### CLI + +* `auth login` no longer falls back to plaintext when the OS keyring is reachable but locked. The unlock prompt shown by the probe now runs in parallel with the OAuth flow, and the token is stored in the keyring once the user has typed their password. +* `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. +* The interactive auth profile pickers now start in search mode so typing immediately filters the list, and the action entries (`+ Create a new profile`, `→ Enter a host URL manually`) are visually distinct from real profiles and stay visible regardless of the search query. +* Shortened the host prompt label shown after `→ Enter a host URL manually` in `databricks auth login` so the prompt no longer leaves stale lines on screen when typing or pasting a host URL. + +### Bundles +* 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 + +* Bump Go toolchain to 1.25.10 ([#5213](https://github.com/databricks/cli/pull/5213)). +* Bump `github.com/databricks/databricks-sdk-go` from v0.128.0 to v0.132.0. +* Bump Terraform provider to v1.115.0. + + +## 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 diff --git a/Makefile b/Makefile deleted file mode 100644 index 97ed6b82491..00000000000 --- a/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -default: - ./task - -# Delegates every make target to the equivalent ./task target. -# Intentional semantic changes from the old Makefile: -# make fmt → ./task fmt (full format, was incremental; use make fmt-q for incremental) -# make lint → ./task lint (full lint, was incremental; use make lint-q for incremental) -.DEFAULT: - @./task "$@" diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 00152d550ea..004cd4da4c0 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -1,9 +1,15 @@ # NEXT CHANGELOG -## Release v0.300.0 +## Release v1.1.0 + +### Notable Changes ### CLI ### Bundles +* The error reported when a direct-only resource (catalogs, external locations, vector search endpoints) is used with the terraform engine now also suggests setting `bundle.engine: direct` in `databricks.yml`, in addition to the `DATABRICKS_BUNDLE_ENGINE` environment variable ([#5295](https://github.com/databricks/cli/pull/5295)). ### Dependency updates + +* Bump Go toolchain to 1.26.3 ([#5302](https://github.com/databricks/cli/pull/5302)). +* Bump `github.com/databricks/databricks-sdk-go` from v0.132.0 to v0.136.0. diff --git a/NOTICE b/NOTICE index 1e286df6f91..01f9df98246 100644 --- a/NOTICE +++ b/NOTICE @@ -66,10 +66,6 @@ google/uuid - https://github.com/google/uuid Copyright (c) 2009,2014 Google Inc. All rights reserved. License - https://github.com/google/uuid/blob/master/LICENSE -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 @@ -79,10 +75,6 @@ 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 -gorilla/mux - https://github.com/gorilla/mux -Copyright (c) 2023 The Gorilla Authors. All rights reserved. -License - https://github.com/gorilla/mux/blob/main/LICENSE - palantir/pkg - https://github.com/palantir/pkg Copyright (c) 2016, Palantir Technologies, Inc. License - https://github.com/palantir/pkg/blob/master/LICENSE @@ -127,6 +119,10 @@ 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 @@ -143,9 +139,9 @@ 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 +charmbracelet/x/ansi - https://github.com/charmbracelet/x +Copyright (c) 2023-2025 Charmbracelet, Inc +License - https://github.com/charmbracelet/x/blob/main/ansi/LICENSE Masterminds/semver - https://github.com/Masterminds/semver Copyright (C) 2014-2019, Matt Butcher and Matt Farina @@ -155,10 +151,6 @@ mattn/go-isatty - https://github.com/mattn/go-isatty Copyright (c) Yasuhiro MATSUMOTO License - 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 - sabhiram/go-gitignore - https://github.com/sabhiram/go-gitignore Copyright (c) 2015 Shaba Abhiram License - https://github.com/sabhiram/go-gitignore/blob/master/LICENSE diff --git a/README.md b/README.md index 3c238702cf2..64878dc4539 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![build](https://github.com/databricks/cli/workflows/build/badge.svg?branch=main)](https://github.com/databricks/cli/actions?query=workflow%3Abuild+branch%3Amain) -This project is in Public Preview. - Documentation is available at https://docs.databricks.com/dev-tools/cli/databricks-cli.html. ## Installation @@ -36,5 +34,30 @@ This CLI follows the Databricks Unified Authentication principles. You can find a detailed description at https://github.com/databricks/databricks-sdk-go#authentication. +## Stability Policy + +### Feature stability + +Commands and flags are stable by default and will not break within a major version. + +Some features are unstable and may change in any MINOR release: + +- Commands and flags marked **Beta** or **Private Preview** in their `--help` output. +- Commands in the `databricks experimental` group. + +### Versioning + +The CLI follows [Semantic Versioning](https://semver.org) (`MAJOR.MINOR.PATCH`): + +- `MAJOR` is incremented for breaking changes to **stable** features. +- `MINOR` is incremented for new features and for breaking changes to **unstable** features. +- `PATCH` is incremented for backward-compatible bug fixes, security fixes, and dependency updates. + +Databricks may ship a breaking change to a stable feature without a major version bump in exceptional circumstances where waiting for the next major version would itself cause greater harm: an active security incident, a legal or compliance requirement, or a regression introduced in the current major version. Any such exceptional change is announced in the release notes. + +### Security patches + +Security patches ship on the current release, and on specific older versions listed in [`SECURITY.md`](./SECURITY.md). The CLI does not currently offer a broader long-term support commitment. + ## Privacy Notice Databricks CLI use is subject to the [Databricks License](https://github.com/databricks/cli/blob/main/LICENSE) and [Databricks Privacy Notice](https://www.databricks.com/legal/privacynotice), including any Usage Data provisions. diff --git a/SECURITY.md b/SECURITY.md index 54ee11213af..9471ff46657 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,14 @@ ## Reporting a Vulnerability -We appreciate any security concerns brought to our attention and encourage you to notify us of any potential vulnerabilities discovered in our systems or products. -If you believe you have found a security vulnerability, please report it to us at [security@databricks.com](mailto:security@databricks.com). +We appreciate any security concerns brought to our attention and encourage you to notify us of any potential vulnerabilities discovered in our systems or products. If you believe you have found a security vulnerability, please report it to us at [security@databricks.com](mailto:security@databricks.com). + +## Supported Versions + +The following versions receive security patches: + +| Version | Notes | +| :---- | :---- | +| Latest release | Active development line. | +| `v0.299.x` | Extended security support until 2027-05-20. | + +Other versions of the CLI do not receive patches. diff --git a/Taskfile.yml b/Taskfile.yml index c82f9e9848e..7a8fb12adef 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -564,13 +564,15 @@ tasks: 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.json \ + --jsonfile test-output-unit.json \ --rerun-fails \ --packages "{{.TEST_PACKAGES}}" \ -- -coverprofile=coverage.txt -timeout=${LOCAL_TIMEOUT:-30m} @@ -578,10 +580,11 @@ tasks: VERBOSE_TEST=1 CLI_GOCOVERDIR=build/cover {{.GO_TOOL}} gotestsum \ --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ --no-summary=skipped \ - --jsonfile test-output.json \ + --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/" @@ -606,24 +609,27 @@ tasks: # generic `test` target (the catch-all) instead. test-exp-aitools: - desc: Run experimental aitools unit and acceptance tests + desc: Run aitools (top-level + experimental) unit and acceptance tests sources: + - cmd/aitools/** + - libs/aitools/** - experimental/aitools/** - acceptance/apps/** + - acceptance/experimental/aitools/** - "{{.EMBED_SOURCES}}" cmds: - | {{.GO_TOOL}} gotestsum \ --format ${GOTESTSUM_FORMAT:-pkgname-and-test-fails} \ --no-summary=skipped \ - --packages ./experimental/aitools/... \ + --packages "./cmd/aitools/... ./libs/aitools/... ./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" + -- -timeout=${LOCAL_TIMEOUT:-30m} -run "TestAccept/(apps|experimental/aitools)" test-exp-ssh: desc: Run experimental SSH unit and acceptance tests diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 67ec78ffded..f6ec0805fb2 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -198,6 +198,22 @@ func testAccept(t *testing.T, inprocessMode bool, singleTest string) int { // Consistent behavior of locale-dependent tools, such as 'sort' t.Setenv("LC_ALL", "C") + // Unset AI-agent detection env vars so the SDK's user-agent does not + // pick up the host's agent. Setting these to "" via test.toml is not + // enough: the SDK (since v0.132.0) treats empty values as a truthy + // signal because os.LookupEnv reports them as present. + for _, v := range []string{ + "ANTIGRAVITY_AGENT", + "CLAUDECODE", + "CLINE_ACTIVE", + "CODEX_CI", + "CURSOR_AGENT", + "GEMINI_CLI", + "OPENCODE", + } { + os.Unsetenv(v) //nolint:usetesting // t.Setenv cannot unset + } + buildDir := getBuildDir(t, cwd, runtime.GOOS, runtime.GOARCH) // Set up terraform for tests. Skip on DBR - tests with RunsOnDbr only use direct deployment. 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/output.txt b/acceptance/apps/deploy/no-bundle-no-args/output.txt index affcf94d8f5..0401016dacf 100644 --- a/acceptance/apps/deploy/no-bundle-no-args/output.txt +++ b/acceptance/apps/deploy/no-bundle-no-args/output.txt @@ -1,3 +1,9 @@ -Error: accepts 1 arg(s), received 0 +Error: missing required argument: APP_NAME + +Usage: databricks apps deploy APP_NAME + +APP_NAME is the name of the Databricks app to operate on. +Alternatively, run this command from a project directory containing +databricks.yml to auto-detect the app name. Exit code: 1 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/apps/deploy/test.toml b/acceptance/apps/deploy/test.toml deleted file mode 100644 index 743db70ac76..00000000000 --- a/acceptance/apps/deploy/test.toml +++ /dev/null @@ -1 +0,0 @@ -# Common configuration for apps deploy tests diff --git a/acceptance/auth/bundle_and_profile/test.toml b/acceptance/auth/bundle_and_profile/test.toml index 92458e9d303..504b4c63eee 100644 --- a/acceptance/auth/bundle_and_profile/test.toml +++ b/acceptance/auth/bundle_and_profile/test.toml @@ -10,5 +10,5 @@ Old='DATABRICKS_URL' New='DATABRICKS_TARGET' [[Repls]] -Old='Get "https://non.existing.subdomain.databricks.com/api/2.0/preview/scim/v2/Me": .*' +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/bundle_default_profile/databricks.yml.no-host.tmpl b/acceptance/auth/bundle_default_profile/databricks.yml.no-host.tmpl new file mode 100644 index 00000000000..be6fb0c8f5e --- /dev/null +++ b/acceptance/auth/bundle_default_profile/databricks.yml.no-host.tmpl @@ -0,0 +1,5 @@ +bundle: + name: test-default-profile + +# No workspace.host on purpose: this is the surface where +# [__settings__].default_profile should be applied. diff --git a/acceptance/auth/bundle_default_profile/databricks.yml.with-host.tmpl b/acceptance/auth/bundle_default_profile/databricks.yml.with-host.tmpl new file mode 100644 index 00000000000..4c2f0748389 --- /dev/null +++ b/acceptance/auth/bundle_default_profile/databricks.yml.with-host.tmpl @@ -0,0 +1,5 @@ +bundle: + name: bundle-with-host + +workspace: + host: $DATABRICKS_HOST diff --git a/acceptance/auth/bundle_default_profile/databricks.yml.with-profile.tmpl b/acceptance/auth/bundle_default_profile/databricks.yml.with-profile.tmpl new file mode 100644 index 00000000000..18e719f0ea8 --- /dev/null +++ b/acceptance/auth/bundle_default_profile/databricks.yml.with-profile.tmpl @@ -0,0 +1,5 @@ +bundle: + name: bundle-with-profile + +workspace: + profile: other diff --git a/acceptance/bundle/summary/show-full-config/out.test.toml b/acceptance/auth/bundle_default_profile/out.test.toml similarity index 100% rename from acceptance/bundle/summary/show-full-config/out.test.toml rename to acceptance/auth/bundle_default_profile/out.test.toml diff --git a/acceptance/auth/bundle_default_profile/output.txt b/acceptance/auth/bundle_default_profile/output.txt new file mode 100644 index 00000000000..f8e8a3c0062 --- /dev/null +++ b/acceptance/auth/bundle_default_profile/output.txt @@ -0,0 +1,41 @@ + +=== Bundle without workspace.host: default_profile is honored + +>>> [CLI] bundle validate -o json +{ + "host": null, + "profile": "default-target" +} + +=== --profile overrides default_profile (negative case) + +>>> errcode [CLI] bundle validate -p other -o json +Warn: [hostmetadata] failed to fetch host metadata for https://other.test, will skip for 1m0s +Error: Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted) + + +Exit code: 1 +{ + "host": null, + "profile": "other" +} + +=== Bundle with workspace.host: default_profile is NOT applied + +>>> errcode [CLI] bundle validate -o json +{ + "host": "[DATABRICKS_URL]", + "profile": null +} + +=== Bundle with workspace.profile: pinned profile wins over default_profile + +>>> errcode [CLI] bundle validate -o json +Error: Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted) + + +Exit code: 1 +{ + "host": null, + "profile": "other" +} diff --git a/acceptance/auth/bundle_default_profile/script b/acceptance/auth/bundle_default_profile/script new file mode 100644 index 00000000000..793176c4b52 --- /dev/null +++ b/acceptance/auth/bundle_default_profile/script @@ -0,0 +1,44 @@ +sethome "./home" + +# Save the test server host so we can pin a bundle-with-host variant below. +host_value="$DATABRICKS_HOST" + +cat > "./home/.databrickscfg" < databricks.yml +title "Bundle with workspace.host: default_profile is NOT applied\n" +trace errcode $CLI bundle validate -o json | jq '{host: .workspace.host, profile: .workspace.profile}' + +# Switch to a bundle that pins workspace.profile but no host. The pinned +# profile must win over default_profile — configureProfile's guard skips +# default_profile when workspace.profile is already set. +cp databricks.yml.with-profile.tmpl databricks.yml +title "Bundle with workspace.profile: pinned profile wins over default_profile\n" +trace errcode $CLI bundle validate -o json | jq '{host: .workspace.host, profile: .workspace.profile}' diff --git a/acceptance/auth/bundle_default_profile/test.toml b/acceptance/auth/bundle_default_profile/test.toml new file mode 100644 index 00000000000..5d5b0283f42 --- /dev/null +++ b/acceptance/auth/bundle_default_profile/test.toml @@ -0,0 +1,11 @@ +Ignore = [ + "home", + ".databricks", + "databricks.yml", +] + +# Negative case: -p other tries to reach a non-existing host. Redact the +# OS-/network-dependent suffix so the test is stable across runners. +[[Repls]] +Old = 'Get "https://other.test/api/2.0/preview/scim/v2/Me\??": .*' +New = 'Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted)' 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/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/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/host-metadata-cache/output.txt b/acceptance/auth/host-metadata-cache/output.txt index 266c9fa93eb..0b99e9579c8 100644 --- a/acceptance/auth/host-metadata-cache/output.txt +++ b/acceptance/auth/host-metadata-cache/output.txt @@ -3,11 +3,11 @@ { "profiles": [ { - "name":"cached", - "host":"[DATABRICKS_URL]", - "cloud":"aws", - "auth_type":"", - "valid":false + "name": "cached", + "host": "[DATABRICKS_URL]", + "cloud": "aws", + "auth_type": "", + "valid": false } ] } @@ -16,11 +16,11 @@ { "profiles": [ { - "name":"cached", - "host":"[DATABRICKS_URL]", - "cloud":"aws", - "auth_type":"", - "valid":false + "name": "cached", + "host": "[DATABRICKS_URL]", + "cloud": "aws", + "auth_type": "", + "valid": false } ] } diff --git a/acceptance/bin/assert_exists.py b/acceptance/bin/assert_exists.py new file mode 100755 index 00000000000..0d33b46d2aa --- /dev/null +++ b/acceptance/bin/assert_exists.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +import os, sys + +errors = 0 + +for filename in sys.argv[1:]: + if not os.path.exists(filename): + sys.stderr.write(f"Unexpected: {filename} does not exist.\n") + errors += 1 + +if errors: + sys.exit(1) diff --git a/acceptance/bin/assert_not_exists.py b/acceptance/bin/assert_not_exists.py new file mode 100755 index 00000000000..76d467e4515 --- /dev/null +++ b/acceptance/bin/assert_not_exists.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +import os, sys + +errors = 0 + +for filename in sys.argv[1:]: + if os.path.exists(filename): + sys.stderr.write(f"Unexpected: {filename} exists.\n") + errors += 1 + +if errors: + sys.exit(1) diff --git a/acceptance/bin/echo_browser.py b/acceptance/bin/echo_browser.py new file mode 100755 index 00000000000..3d451cf4526 --- /dev/null +++ b/acceptance/bin/echo_browser.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +""" +Fake browser that prints the URL it was asked to open and exits. + +Used by acceptance tests that exercise commands which call libs/browser.Open +but don't need to follow the URL (unlike auth tests, which use browser.py to +close the OAuth callback loop). Setting BROWSER=echo_browser.py is portable +across darwin/linux/windows because libs/browser routes through libs/exec. + +Usage: echo_browser.py +""" + +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/kill_after.py b/acceptance/bin/kill_after.py new file mode 100755 index 00000000000..029123a13f5 --- /dev/null +++ b/acceptance/bin/kill_after.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Set up a kill rule on the testserver for the current test token. + +Usage: kill_after.py PATTERN OFFSET TIMES + + PATTERN HTTP method and path, e.g. "POST /api/2.2/jobs/create" + OFFSET number of requests to let through before killing starts + TIMES number of times to kill the caller + +The rule is scoped to the current DATABRICKS_TOKEN so it only affects +the test that registers it, even when tests share a server. +""" + +import json +import os +import sys +import urllib.request + +host = os.environ.get("DATABRICKS_HOST", "") +token = os.environ.get("DATABRICKS_TOKEN", "") + +if not host: + print("DATABRICKS_HOST not set", file=sys.stderr) + sys.exit(1) + +if len(sys.argv) != 4: + print(f"usage: {sys.argv[0]} PATTERN OFFSET TIMES", file=sys.stderr) + sys.exit(1) + +pattern, offset, times = sys.argv[1], int(sys.argv[2]), int(sys.argv[3]) + +data = json.dumps({"pattern": pattern, "offset": offset, "times": times}).encode() +req = urllib.request.Request( + f"{host}/__testserver/kill", + data=data, + headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"}, + method="POST", +) +urllib.request.urlopen(req) diff --git a/acceptance/bundle/artifacts/whl_prebuilt_outside/test.toml b/acceptance/bundle/artifacts/whl_prebuilt_outside/test.toml deleted file mode 100644 index e69de29bb2d..00000000000 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/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/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/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_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/output.txt b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt index 24c69a9675f..6c45a66bed1 100644 --- a/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt +++ b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt @@ -125,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 @@ -142,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_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/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/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/task_rename_revert/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/task_rename_revert/databricks.yml.tmpl new file mode 100644 index 00000000000..7aa0dbecc25 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/task_rename_revert/databricks.yml.tmpl @@ -0,0 +1,18 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + jobs: + sample_job: + tasks: + - task_key: new_task + notebook_task: + notebook_path: /Users/{{workspace_user_name}}/new_task + new_cluster: + 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/task_rename_revert/out.test.toml b/acceptance/bundle/config-remote-sync/task_rename_revert/out.test.toml new file mode 100644 index 00000000000..579b1e4a3c9 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/task_rename_revert/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = true +GOOS.windows = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/config-remote-sync/task_rename_revert/output.txt b/acceptance/bundle/config-remote-sync/task_rename_revert/output.txt new file mode 100644 index 00000000000..f05ccc1c743 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/task_rename_revert/output.txt @@ -0,0 +1,67 @@ +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +=== Rename task to new_task_2 remotely + +=== Sync the rename into config (no redeploy) +Detected changes in 1 resource(s): + +Resource: resources.jobs.sample_job + tasks[task_key='new_task']: remove + tasks[task_key='new_task_2']: add + + + +>>> diff.py databricks.yml.backup databricks.yml +--- databricks.yml.backup ++++ databricks.yml +@@ -6,11 +6,11 @@ + sample_job: + tasks: +- - task_key: new_task +- notebook_task: +- notebook_path: /Users/{{workspace_user_name}}/new_task +- new_cluster: +- spark_version: 13.3.x-snapshot-scala2.12 ++ - new_cluster: + node_type_id: [NODE_TYPE_ID] + num_workers: 1 ++ spark_version: 13.3.x-snapshot-scala2.12 ++ notebook_task: ++ notebook_path: '/Users/{{workspace_user_name}}/new_task' ++ task_key: new_task_2 + + targets: + +=== Rename task back to new_task remotely + +=== Sync the revert into config +Detected changes in 1 resource(s): + +Resource: resources.jobs.sample_job + tasks[task_key='new_task']: add + tasks[task_key='new_task_2']: remove + + + +>>> diff.py databricks.yml.backup databricks.yml +--- databricks.yml.backup ++++ databricks.yml +@@ -12,5 +12,5 @@ + notebook_task: + notebook_path: '/Users/{{workspace_user_name}}/new_task' +- task_key: new_task_2 ++ task_key: new_task + + targets: + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.jobs.sample_job + +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/task_rename_revert/script b/acceptance/bundle/config-remote-sync/task_rename_revert/script new file mode 100644 index 00000000000..a01c1196de6 --- /dev/null +++ b/acceptance/bundle/config-remote-sync/task_rename_revert/script @@ -0,0 +1,45 @@ +#!/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 sample_job)" + +title "Rename task to new_task_2 remotely" +echo +edit_resource.py jobs $job_id <>> [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/deploy/wal/chain-3-jobs/databricks.yml b/acceptance/bundle/deploy/wal/chain-3-jobs/databricks.yml new file mode 100644 index 00000000000..342a4516235 --- /dev/null +++ b/acceptance/bundle/deploy/wal/chain-3-jobs/databricks.yml @@ -0,0 +1,37 @@ +bundle: + name: wal-chain-test + +resources: + jobs: + # Linear chain: job_01 -> job_02 -> job_03 + # Execution order: job_01 first, job_03 last + job_01: + name: "job-01" + description: "first in chain" + tasks: + - task_key: "task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge + job_02: + name: "job-02" + description: "depends on ${resources.jobs.job_01.id}" + tasks: + - task_key: "task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge + job_03: + name: "job-03" + description: "depends on ${resources.jobs.job_02.id}" + tasks: + - task_key: "task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/chain-3-jobs/out.test.toml b/acceptance/bundle/deploy/wal/chain-3-jobs/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/chain-3-jobs/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/chain-3-jobs/output.txt b/acceptance/bundle/deploy/wal/chain-3-jobs/output.txt new file mode 100644 index 00000000000..7e04ba4dae3 --- /dev/null +++ b/acceptance/bundle/deploy/wal/chain-3-jobs/output.txt @@ -0,0 +1,110 @@ +=== First deploy (crashes on job_03) === + +>>> errcode [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default/files... +Deploying resources... +[PROCESS_KILLED] + +Exit code: [KILLED] + +=== WAL content after crash === +{ + "cli_version": "[DEV_VERSION]", + "lineage": "[UUID]", + "serial": 1, + "state_version": 2 +} +{ + "k": "resources.jobs.job_01", + "v": { + "__id__": "[JOB_01_ID]", + "state": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default/state/metadata.json" + }, + "description": "first in chain", + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "job-01", + "queue": { + "enabled": true + }, + "tasks": [ + { + "new_cluster": { + "node_type_id": "[NODE_TYPE_ID]", + "spark_version": "15.4.x-scala2.12" + }, + "spark_python_task": { + "python_file": "/Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default/files/test.py" + }, + "task_key": "task" + } + ] + } + } +} +{ + "k": "resources.jobs.job_02", + "v": { + "__id__": "[JOB_02_ID]", + "depends_on": [ + { + "label": "${resources.jobs.job_01.id}", + "node": "resources.jobs.job_01" + } + ], + "state": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default/state/metadata.json" + }, + "description": "depends on [JOB_01_ID]", + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "job-02", + "queue": { + "enabled": true + }, + "tasks": [ + { + "new_cluster": { + "node_type_id": "[NODE_TYPE_ID]", + "spark_version": "15.4.x-scala2.12" + }, + "spark_python_task": { + "python_file": "/Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default/files/test.py" + }, + "task_key": "task" + } + ] + } + } +} + +=== Number of jobs saved in WAL === +2 + +=== Bundle summary (reads from WAL) === +Name: wal-chain-test +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/wal-chain-test/default +Resources: + Jobs: + job_01: + Name: job-01 + URL: [DATABRICKS_URL]/jobs/[JOB_01_ID]?o=[NUMID] + job_02: + Name: job-02 + URL: [DATABRICKS_URL]/jobs/[JOB_02_ID]?o=[NUMID] + job_03: + Name: job-03 + URL: (not deployed) + +=== WAL after successful deploy === +WAL deleted (expected) diff --git a/acceptance/bundle/deploy/wal/chain-3-jobs/script b/acceptance/bundle/deploy/wal/chain-3-jobs/script new file mode 100644 index 00000000000..a5afc6f51d5 --- /dev/null +++ b/acceptance/bundle/deploy/wal/chain-3-jobs/script @@ -0,0 +1,24 @@ +# Linear chain: job_01 -> job_02 -> job_03 +# Let first 2 jobs/create succeed, then kill on the 3rd +kill_after.py "POST /api/2.2/jobs/create" 2 1 + +echo "=== First deploy (crashes on job_03) ===" +trace errcode $CLI bundle deploy + +echo "" +echo "=== WAL content after crash ===" +jq -S . .databricks/bundle/default/resources.json.wal 2>/dev/null || echo "No WAL file" + +echo "" +echo "=== Number of jobs saved in WAL ===" +grep -c '"k":"resources.jobs' .databricks/bundle/default/resources.json.wal 2>/dev/null || echo "0" + +echo "" +echo "=== Bundle summary (reads from WAL) ===" +$CLI bundle summary + +echo "" +echo "=== WAL after successful deploy ===" +cat .databricks/bundle/default/resources.json.wal 2>/dev/null || echo "WAL deleted (expected)" + +replace_ids.py diff --git a/acceptance/bundle/deploy/wal/chain-3-jobs/test.py b/acceptance/bundle/deploy/wal/chain-3-jobs/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/chain-3-jobs/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/databricks.yml b/acceptance/bundle/deploy/wal/corrupted-wal-entry/databricks.yml new file mode 100644 index 00000000000..a7a5cc2dfe0 --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/databricks.yml @@ -0,0 +1,23 @@ +bundle: + name: wal-corrupted-test + +resources: + jobs: + valid_job: + name: "valid-job" + tasks: + - task_key: "task-a" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge + another_valid: + name: "another-valid" + tasks: + - task_key: "task-b" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/out.test.toml b/acceptance/bundle/deploy/wal/corrupted-wal-entry/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/output.txt b/acceptance/bundle/deploy/wal/corrupted-wal-entry/output.txt new file mode 100644 index 00000000000..1aee4fe481d --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/output.txt @@ -0,0 +1,34 @@ + +>>> cat .databricks/bundle/default/resources.json.wal +{"lineage":"test-lineage-123","serial":6} +{"k":"resources.jobs.valid_job","v":{"__id__":"","state":{"name":"valid-job"}}} +{"k":"resources.jobs.another_valid","v":{"__id__":"","state":{"name":"another-valid"}}} +{"k":"resources.jobs.partial_write","v":{"__id__":"33","state":{"name":"partial- + +>>> [CLI] bundle deploy +Warn: Skipping corrupted WAL entry at [TEST_TMP_DIR]/.databricks/bundle/default/resources.json.wal:4: unexpected end of JSON input +Warn: Saved 1 corrupted WAL entries to [TEST_TMP_DIR]/.databricks/bundle/default/resources.json.wal.corrupted +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-corrupted-test/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle summary +Name: wal-corrupted-test +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/wal-corrupted-test/default +Resources: + Jobs: + another_valid: + Name: another-valid + URL: [DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] + valid_job: + Name: valid-job + URL: [DATABRICKS_URL]/jobs/[NUMID]?o=[NUMID] + +>>> cat .databricks/bundle/default/resources.json.wal.corrupted +{"k":"resources.jobs.partial_write","v":{"__id__":"33","state":{"name":"partial- +=== WAL after successful deploy === +WAL deleted (expected) diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json b/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json new file mode 100644 index 00000000000..f9f4e54d1ed --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json @@ -0,0 +1,7 @@ +{ + "state_version": 1, + "cli_version": "0.0.0", + "lineage": "test-lineage-123", + "serial": 5, + "state": {} +} diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json.wal.tmpl b/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json.wal.tmpl new file mode 100644 index 00000000000..7ef5773a4ea --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/resources.json.wal.tmpl @@ -0,0 +1,4 @@ +{"lineage":"test-lineage-123","serial":6} +{"k":"resources.jobs.valid_job","v":{"__id__":"$JOB1","state":{"name":"valid-job"}}} +{"k":"resources.jobs.another_valid","v":{"__id__":"$JOB2","state":{"name":"another-valid"}}} +{"k":"resources.jobs.partial_write","v":{"__id__":"33","state":{"name":"partial- diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/script b/acceptance/bundle/deploy/wal/corrupted-wal-entry/script new file mode 100644 index 00000000000..d6f151a29c6 --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/script @@ -0,0 +1,22 @@ +# Create pre-existing jobs in the testserver so WAL recovery triggers DoUpdate (reset) instead of DoCreate +JOB1=$($CLI jobs create --json '{"name":"valid-job"}' | jq -r '.job_id') +JOB2=$($CLI jobs create --json '{"name":"another-valid"}' | jq -r '.job_id') +echo "$JOB1:JOB1_ID" >> ACC_REPLS +echo "$JOB2:JOB2_ID" >> ACC_REPLS + +mkdir -p .databricks/bundle/default +cp resources.json .databricks/bundle/default/ + +envsubst < resources.json.wal.tmpl > .databricks/bundle/default/resources.json.wal + +trace cat .databricks/bundle/default/resources.json.wal +trace $CLI bundle deploy +trace $CLI bundle summary +trace cat .databricks/bundle/default/resources.json.wal.corrupted + +printf "\n=== WAL after successful deploy ===\n" +if [ -f ".databricks/bundle/default/resources.json.wal" ]; then + echo "WAL exists (unexpected)" +else + echo "WAL deleted (expected)" +fi diff --git a/acceptance/bundle/deploy/wal/corrupted-wal-entry/test.py b/acceptance/bundle/deploy/wal/corrupted-wal-entry/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/corrupted-wal-entry/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/crash-after-create/databricks.yml b/acceptance/bundle/deploy/wal/crash-after-create/databricks.yml new file mode 100644 index 00000000000..25b2efe2f8c --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/databricks.yml @@ -0,0 +1,25 @@ +bundle: + name: wal-crash-test + +resources: + jobs: + job_a: + name: "test-job-a" + description: "first job" + tasks: + - task_key: "task-a" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge + job_b: + name: "test-job-b" + description: "depends on ${resources.jobs.job_a.id}" + tasks: + - task_key: "task-b" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/crash-after-create/out.test.toml b/acceptance/bundle/deploy/wal/crash-after-create/out.test.toml new file mode 100644 index 00000000000..1d895a16c96 --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +EnvMatrix.COMMAND = ["plan", "deploy --force-lock", "summary"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/crash-after-create/output.txt b/acceptance/bundle/deploy/wal/crash-after-create/output.txt new file mode 100644 index 00000000000..2ab926a1dd9 --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/output.txt @@ -0,0 +1,57 @@ +=== First deploy (crashes after job_a create, before job_b) === + +>>> errcode [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-crash-test/default/files... +Deploying resources... +[PROCESS_KILLED] + +Exit code: [KILLED] + +>>> assert_exists.py .databricks/bundle/default/resources.json.wal + +>>> assert_not_exists.py .databricks/bundle/default/resources.json + +>>> cat .databricks/bundle/default/resources.json.wal +{ + "state_version": 2, + "cli_version": "[DEV_VERSION]", + "lineage": "[UUID]", + "serial": 1 +} +{ + "k": "resources.jobs.job_a", + "v": { + "__id__": "[NUMID]", + "state": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/wal-crash-test/default/state/metadata.json" + }, + "description": "first job", + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "test-job-a", + "queue": { + "enabled": true + }, + "tasks": [ + { + "new_cluster": { + "node_type_id": "[NODE_TYPE_ID]", + "spark_version": "15.4.x-scala2.12" + }, + "spark_python_task": { + "python_file": "/Workspace/Users/[USERNAME]/.bundle/wal-crash-test/default/files/test.py" + }, + "task_key": "task-a" + } + ] + } + } +} + +=== Any other command recovers state +>>> assert_exists.py .databricks/bundle/default/resources.json + +>>> assert_not_exists.py .databricks/bundle/default/resources.json.wal diff --git a/acceptance/bundle/deploy/wal/crash-after-create/script b/acceptance/bundle/deploy/wal/crash-after-create/script new file mode 100644 index 00000000000..264d84648d3 --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/script @@ -0,0 +1,17 @@ +# WAL recovery after real crash. First deploy creates job_a then crashes. +# Second deploy recovers from WAL and completes successfully. +# job_b depends on job_a, so jobs/get is called after job_a's SaveState. +kill_after.py "POST /api/2.2/jobs/create" 1 1 + +echo "=== First deploy (crashes after job_a create, before job_b) ===" +trace errcode $CLI bundle deploy + +trace assert_exists.py .databricks/bundle/default/resources.json.wal +trace assert_not_exists.py .databricks/bundle/default/resources.json +trace cat .databricks/bundle/default/resources.json.wal | jq + +title "Any other command recovers state" +$CLI bundle $COMMAND &> LOG.COMMAND.txt + +trace assert_exists.py .databricks/bundle/default/resources.json +trace assert_not_exists.py .databricks/bundle/default/resources.json.wal diff --git a/acceptance/bundle/deploy/wal/crash-after-create/test.py b/acceptance/bundle/deploy/wal/crash-after-create/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/crash-after-create/test.toml b/acceptance/bundle/deploy/wal/crash-after-create/test.toml new file mode 100644 index 00000000000..ecd87c31a8b --- /dev/null +++ b/acceptance/bundle/deploy/wal/crash-after-create/test.toml @@ -0,0 +1,2 @@ +EnvMatrix.COMMAND = ["plan", "deploy --force-lock", "summary"] +EnvRepl.COMMAND = false diff --git a/acceptance/bundle/deploy/wal/empty-wal/databricks.yml b/acceptance/bundle/deploy/wal/empty-wal/databricks.yml new file mode 100644 index 00000000000..8da92255ff1 --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: wal-empty-test + +resources: + jobs: + test_job: + name: "test-job" + tasks: + - task_key: "test-task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/empty-wal/out.test.toml b/acceptance/bundle/deploy/wal/empty-wal/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/empty-wal/output.txt b/acceptance/bundle/deploy/wal/empty-wal/output.txt new file mode 100644 index 00000000000..bba6d249fce --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/output.txt @@ -0,0 +1,6 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-empty-test/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! diff --git a/acceptance/bundle/deploy/wal/empty-wal/script b/acceptance/bundle/deploy/wal/empty-wal/script new file mode 100644 index 00000000000..ac104951c58 --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/script @@ -0,0 +1,4 @@ +mkdir -p .databricks/bundle/default +touch .databricks/bundle/default/resources.json.wal +trace $CLI bundle deploy +assert_not_exists.py .databricks/bundle/default/resources.json.wal* diff --git a/acceptance/bundle/deploy/wal/empty-wal/test.py b/acceptance/bundle/deploy/wal/empty-wal/test.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/test.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/deploy/wal/empty-wal/test.toml b/acceptance/bundle/deploy/wal/empty-wal/test.toml new file mode 100644 index 00000000000..ad64cd6e746 --- /dev/null +++ b/acceptance/bundle/deploy/wal/empty-wal/test.toml @@ -0,0 +1,13 @@ +# Empty WAL file should be moved to .wal.corrupted and deploy should proceed normally. + +[[Server]] +Pattern = "POST /api/2.2/jobs/create" +Response.Body = '{"job_id": 1001}' + +[[Server]] +Pattern = "GET /api/2.2/jobs/get" +Response.Body = '{"job_id": 1001, "settings": {"name": "test-job"}}' + +[[Repls]] +Old = '-rw[^\s]+\s+\d+\s+[^\s]+\s+[^\s]+\s+\d+\s+[A-Z][a-z]+\s+\d+\s+\d+:\d+' +New = '[FILE_INFO]' diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/databricks.yml b/acceptance/bundle/deploy/wal/future-serial-wal/databricks.yml new file mode 100644 index 00000000000..56fa1313376 --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: wal-future-serial-test + +resources: + jobs: + test_job: + name: "test-job" + tasks: + - task_key: "test-task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/out.test.toml b/acceptance/bundle/deploy/wal/future-serial-wal/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/output.txt b/acceptance/bundle/deploy/wal/future-serial-wal/output.txt new file mode 100644 index 00000000000..48c23ddf84f --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/output.txt @@ -0,0 +1,10 @@ +=== WAL content === +{"lineage":"test-lineage-123","serial":5} +{"k":"resources.jobs.test_job","v":{"__id__":"1001","state":{"name":"test-job"}}} +=== Deploy (should fail with corruption error) === + +>>> errcode [CLI] bundle deploy +Error: reading state from [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: WAL recovery failed: WAL serial (5) is ahead of expected (3), state may be corrupted + + +Exit code: 1 diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/resources.json b/acceptance/bundle/deploy/wal/future-serial-wal/resources.json new file mode 100644 index 00000000000..f2f06b34bf4 --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/resources.json @@ -0,0 +1,12 @@ +{ + "state_version": 1, + "cli_version": "0.0.0", + "lineage": "test-lineage-123", + "serial": 2, + "state": { + "resources.jobs.test_job": { + "__id__": "1001", + "state": {"name": "test-job"} + } + } +} diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/resources.json.wal b/acceptance/bundle/deploy/wal/future-serial-wal/resources.json.wal new file mode 100644 index 00000000000..98a8e48802b --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/resources.json.wal @@ -0,0 +1,2 @@ +{"lineage":"test-lineage-123","serial":5} +{"k":"resources.jobs.test_job","v":{"__id__":"1001","state":{"name":"test-job"}}} diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/script b/acceptance/bundle/deploy/wal/future-serial-wal/script new file mode 100644 index 00000000000..f7a57192255 --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/script @@ -0,0 +1,9 @@ +mkdir -p .databricks/bundle/default +cp resources.json .databricks/bundle/default/ +cp resources.json.wal .databricks/bundle/default/ + +echo "=== WAL content ===" +cat .databricks/bundle/default/resources.json.wal + +echo "=== Deploy (should fail with corruption error) ===" +trace errcode $CLI bundle deploy diff --git a/acceptance/bundle/deploy/wal/future-serial-wal/test.py b/acceptance/bundle/deploy/wal/future-serial-wal/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/future-serial-wal/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/databricks.yml b/acceptance/bundle/deploy/wal/lineage-mismatch/databricks.yml new file mode 100644 index 00000000000..32461d14676 --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: wal-lineage-mismatch-test + +resources: + jobs: + test_job: + name: "test-job" + tasks: + - task_key: "test-task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/out.test.toml b/acceptance/bundle/deploy/wal/lineage-mismatch/out.test.toml new file mode 100644 index 00000000000..9448f875df7 --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/out.test.toml @@ -0,0 +1,4 @@ +Local = true +Cloud = false +EnvMatrix.COMMAND = ["deploy", "plan", "summary"] +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/output.txt b/acceptance/bundle/deploy/wal/lineage-mismatch/output.txt new file mode 100644 index 00000000000..43f8cf6931f --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/output.txt @@ -0,0 +1,7 @@ +Any command should fail with lineage mismatch error +Error: reading state from [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: WAL recovery failed: WAL lineage ("wal-lineage-bbb") does not match state lineage ("state-lineage-aaa") + + +>>> musterr [CLI] bundle destroy --auto-approve +Error: reading state from [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: WAL recovery failed: WAL lineage ("wal-lineage-bbb") does not match state lineage ("state-lineage-aaa") + diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json b/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json new file mode 100644 index 00000000000..444a9ea888d --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json @@ -0,0 +1,12 @@ +{ + "state_version": 1, + "cli_version": "0.0.0", + "lineage": "state-lineage-aaa", + "serial": 1, + "state": { + "resources.jobs.test_job": { + "__id__": "1001", + "state": {"name": "test-job"} + } + } +} diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json.wal b/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json.wal new file mode 100644 index 00000000000..d14fb4a9713 --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/resources.json.wal @@ -0,0 +1,2 @@ +{"lineage":"wal-lineage-bbb","serial":2} +{"k":"resources.jobs.test_job","v":{"__id__":"1001","state":{"name":"test-job"}}} diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/script b/acceptance/bundle/deploy/wal/lineage-mismatch/script new file mode 100644 index 00000000000..0629a37c0f9 --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/script @@ -0,0 +1,8 @@ +mkdir -p .databricks/bundle/default +cp resources.json .databricks/bundle/default/ +cp resources.json.wal .databricks/bundle/default/ + +echo "Any command should fail with lineage mismatch error" +musterr $CLI bundle $COMMAND + +trace musterr $CLI bundle destroy --auto-approve diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/test.py b/acceptance/bundle/deploy/wal/lineage-mismatch/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/lineage-mismatch/test.toml b/acceptance/bundle/deploy/wal/lineage-mismatch/test.toml new file mode 100644 index 00000000000..0b3a9e0b7cc --- /dev/null +++ b/acceptance/bundle/deploy/wal/lineage-mismatch/test.toml @@ -0,0 +1 @@ +EnvMatrix.COMMAND = ["deploy", "plan", "summary"] diff --git a/acceptance/bundle/deploy/wal/stale-wal/databricks.yml b/acceptance/bundle/deploy/wal/stale-wal/databricks.yml new file mode 100644 index 00000000000..443283607e6 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/databricks.yml @@ -0,0 +1,14 @@ +bundle: + name: wal-stale-test + +resources: + jobs: + test_job: + name: "test-job" + tasks: + - task_key: "test-task" + spark_python_task: + python_file: ./test.py + new_cluster: + spark_version: 15.4.x-scala2.12 + node_type_id: i3.xlarge diff --git a/acceptance/bundle/deploy/wal/stale-wal/out.test.toml b/acceptance/bundle/deploy/wal/stale-wal/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/stale-wal/output.txt b/acceptance/bundle/deploy/wal/stale-wal/output.txt new file mode 100644 index 00000000000..91a7a07643d --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/output.txt @@ -0,0 +1,19 @@ +=== WAL content before deploy === +{"lineage":"stale-test-lineage","serial":1} +{"k":"resources.jobs.stale_job","v":{"__id__":"9999","state":{"name":"stale-job"}}} +=== Deploy (should ignore stale WAL) === + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-stale-test/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! +=== Checking WAL file after deploy === +Stale WAL deleted (expected) +=== State file should NOT contain stale_job === +{ + "serial": 3, + "state_keys": [ + "resources.jobs.test_job" + ] +} diff --git a/acceptance/bundle/deploy/wal/stale-wal/resources.json b/acceptance/bundle/deploy/wal/stale-wal/resources.json new file mode 100644 index 00000000000..6fd38b67ae8 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/resources.json @@ -0,0 +1,12 @@ +{ + "state_version": 1, + "cli_version": "0.0.0", + "lineage": "stale-test-lineage", + "serial": 2, + "state": { + "resources.jobs.test_job": { + "__id__": "1001", + "state": {"name": "test-job"} + } + } +} diff --git a/acceptance/bundle/deploy/wal/stale-wal/resources.json.wal b/acceptance/bundle/deploy/wal/stale-wal/resources.json.wal new file mode 100644 index 00000000000..ef5f380ed84 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/resources.json.wal @@ -0,0 +1,2 @@ +{"lineage":"stale-test-lineage","serial":1} +{"k":"resources.jobs.stale_job","v":{"__id__":"9999","state":{"name":"stale-job"}}} diff --git a/acceptance/bundle/deploy/wal/stale-wal/script b/acceptance/bundle/deploy/wal/stale-wal/script new file mode 100644 index 00000000000..4de1bc1e921 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/script @@ -0,0 +1,19 @@ +mkdir -p .databricks/bundle/default +cp resources.json .databricks/bundle/default/ +cp resources.json.wal .databricks/bundle/default/ + +echo "=== WAL content before deploy ===" +cat .databricks/bundle/default/resources.json.wal + +echo "=== Deploy (should ignore stale WAL) ===" +trace $CLI bundle deploy + +echo "=== Checking WAL file after deploy ===" +if [ -f ".databricks/bundle/default/resources.json.wal" ]; then + echo "WAL file exists (unexpected)" +else + echo "Stale WAL deleted (expected)" +fi + +echo "=== State file should NOT contain stale_job ===" +cat .databricks/bundle/default/resources.json | jq -S '{serial: .serial, state_keys: (.state | keys)}' diff --git a/acceptance/bundle/deploy/wal/stale-wal/test.py b/acceptance/bundle/deploy/wal/stale-wal/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/test.py @@ -0,0 +1 @@ +print("test") diff --git a/acceptance/bundle/deploy/wal/stale-wal/test.toml b/acceptance/bundle/deploy/wal/stale-wal/test.toml new file mode 100644 index 00000000000..934683ba6d8 --- /dev/null +++ b/acceptance/bundle/deploy/wal/stale-wal/test.toml @@ -0,0 +1,9 @@ +# Deploy with a stale WAL (old serial) - WAL should be deleted and ignored. + +[[Server]] +Pattern = "POST /api/2.2/jobs/reset" +Response.Body = '{}' + +[[Server]] +Pattern = "GET /api/2.2/jobs/get" +Response.Body = '{"job_id": 1001, "settings": {"name": "test-job"}}' diff --git a/acceptance/bundle/deploy/wal/test.toml b/acceptance/bundle/deploy/wal/test.toml new file mode 100644 index 00000000000..e60e6992455 --- /dev/null +++ b/acceptance/bundle/deploy/wal/test.toml @@ -0,0 +1,34 @@ +# WAL (Write-Ahead Log) tests verify crash recovery during bundle deployment. +# These tests simulate process crashes using KillCaller and verify state recovery. +# Only runs with direct engine since WAL is a direct-engine feature. + +Local = true +Env.DATABRICKS_CLI_TEST_PID = "1" + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = ["direct"] + +[[Repls]] +Old = 'script: line \d+:\s+\d+ Killed(: 9)?\s+"\$@"' +New = '[PROCESS_KILLED]' + +[[Repls]] +Old = '(\n>>> errcode [^\n]+\n)\nExit code:' +New = "${1}[PROCESS_KILLED]\n\nExit code:" + +[[Repls]] +Old = 'Exit code: 137' +New = 'Exit code: [KILLED]' + +# On Linux, a KillCaller kill may surface as exit code 1 rather than 137. +# Only normalise exit code 1 when it directly follows [PROCESS_KILLED] to +# avoid masking genuine error exits (lineage-mismatch, future-serial-wal). +[[Repls]] +Old = '(\[PROCESS_KILLED\]\n\nExit code: )1' +New = '${1}[KILLED]' + +# On Windows, no bash "Killed" message appears when CLI has produced output before termination. +# Match the raw exit code 1 (Windows never gets 137 or [PROCESS_KILLED] marker first). +[[Repls]] +Old = '(Deploying resources\.\.\.)\n\nExit code: 1' +New = "${1}\n[PROCESS_KILLED]\n\nExit code: [KILLED]" diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/databricks.yml b/acceptance/bundle/deploy/wal/wal-with-delete/databricks.yml new file mode 100644 index 00000000000..128bbe37f56 --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/databricks.yml @@ -0,0 +1,4 @@ +bundle: + name: wal-delete-test + +resources: {} diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/out.test.toml b/acceptance/bundle/deploy/wal/wal-with-delete/out.test.toml new file mode 100644 index 00000000000..e90b6d5d1ba --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/output.txt b/acceptance/bundle/deploy/wal/wal-with-delete/output.txt new file mode 100644 index 00000000000..4eb0fb5724a --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/output.txt @@ -0,0 +1,19 @@ +=== WAL content === +{"lineage":"delete-test-lineage","serial":2} +{"k":"resources.jobs.test_job","v":null} +=== Deploy (should recover delete from WAL) === + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/wal-delete-test/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! +=== Final state (should have no jobs) === + +>>> [CLI] bundle summary +Name: wal-delete-test +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/wal-delete-test/default +Resources: diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/resources.json b/acceptance/bundle/deploy/wal/wal-with-delete/resources.json new file mode 100644 index 00000000000..04263ec36f9 --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/resources.json @@ -0,0 +1,12 @@ +{ + "state_version": 1, + "cli_version": "0.0.0", + "lineage": "delete-test-lineage", + "serial": 1, + "state": { + "resources.jobs.test_job": { + "__id__": "1001", + "state": {"name": "test-job"} + } + } +} diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/resources.json.wal b/acceptance/bundle/deploy/wal/wal-with-delete/resources.json.wal new file mode 100644 index 00000000000..9b5c6169e3f --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/resources.json.wal @@ -0,0 +1,2 @@ +{"lineage":"delete-test-lineage","serial":2} +{"k":"resources.jobs.test_job","v":null} diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/script b/acceptance/bundle/deploy/wal/wal-with-delete/script new file mode 100644 index 00000000000..1b6708bc0ff --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/script @@ -0,0 +1,12 @@ +mkdir -p .databricks/bundle/default +cp resources.json .databricks/bundle/default/ +cp resources.json.wal .databricks/bundle/default/ + +echo "=== WAL content ===" +cat .databricks/bundle/default/resources.json.wal + +echo "=== Deploy (should recover delete from WAL) ===" +trace $CLI bundle deploy + +echo "=== Final state (should have no jobs) ===" +trace $CLI bundle summary diff --git a/acceptance/bundle/deploy/wal/wal-with-delete/test.py b/acceptance/bundle/deploy/wal/wal-with-delete/test.py new file mode 100644 index 00000000000..1ff8e07c707 --- /dev/null +++ b/acceptance/bundle/deploy/wal/wal-with-delete/test.py @@ -0,0 +1 @@ +print("test") 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 4eb65c32129..ca4b75342ea 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 @@ -28,7 +28,11 @@ "md5": null, "parent_path": "/Users/[USERNAME]", "path": "/Users/[USERNAME]/test dashboard [UNIQUE_NAME].lvdash.json", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "serialized_dashboard": "{\"pages\":[{\"displayName\":\"Untitled page\",\"name\":\"02724bf2\",\"pageType\":\"PAGE_TYPE_CANVAS\"}]}", "update_time": "[TIMESTAMP]", "warehouse_id": "[TEST_DEFAULT_WAREHOUSE_ID]" diff --git a/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt b/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt index 99c26a8ccc3..26de10b19c8 100644 --- a/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt +++ b/acceptance/bundle/deployment/bind/dashboard/recreation/output.txt @@ -18,7 +18,9 @@ Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle-[UNIQU 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: recreate resources.dashboards.dashboard1 -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. Exit code: 1 diff --git a/acceptance/bundle/deployment/bind/job/job-abort-bind/output.txt b/acceptance/bundle/deployment/bind/job/job-abort-bind/output.txt index 544448efbef..b4da76ff600 100644 --- a/acceptance/bundle/deployment/bind/job/job-abort-bind/output.txt +++ b/acceptance/bundle/deployment/bind/job/job-abort-bind/output.txt @@ -3,7 +3,11 @@ Created job with ID: [JOB_ID] === Expect binding to fail without an auto-approve flag: -Error: This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed. +Error: this bind operation requires user confirmation, but the current console does not support prompting. +To proceed, use --auto-approve after reviewing the plan above. + + +Exit code: 1 === Deploy bundle: >>> [CLI] bundle deploy --force-lock diff --git a/acceptance/bundle/deployment/bind/job/job-abort-bind/script b/acceptance/bundle/deployment/bind/job/job-abort-bind/script index bbe22b68fc4..b0aed59093a 100644 --- a/acceptance/bundle/deployment/bind/job/job-abort-bind/script +++ b/acceptance/bundle/deployment/bind/job/job-abort-bind/script @@ -36,7 +36,7 @@ trap cleanup EXIT title "Expect binding to fail without an auto-approve flag:\n" trace errcode $CLI bundle deployment bind foo $JOB_ID &> out.bind-result.txt -grep "^Error:" out.bind-result.txt +sed -n '/^Error:/,$p' out.bind-result.txt rm out.bind-result.txt title "Deploy bundle:" 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/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/pipelines/recreate/out.bind-fail.direct.txt b/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.direct.txt index c5e00b49cbf..b081c183915 100644 --- a/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.direct.txt +++ b/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.direct.txt @@ -11,5 +11,6 @@ Changes detected: ~ root_path: "/Workspace/Users/someuser@databricks.com/lakeflow_pipeline" -> null ~ storage: "/Shared/old_storage" -> "/Shared/new_storage" -Error: This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed. +Error: this bind operation requires user confirmation, but the current console does not support prompting. +To proceed, use --auto-approve after reviewing the plan above. diff --git a/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.terraform.txt b/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.terraform.txt index 1d300160a95..4e20bfd9ece 100644 --- a/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.terraform.txt +++ b/acceptance/bundle/deployment/bind/pipelines/recreate/out.bind-fail.terraform.txt @@ -49,10 +49,15 @@ Terraform will perform the following actions: - include = "/Workspace/Users/foo@databricks.com/another/**" -> null } } + + - provider_config { + - workspace_id = "[NUMID]" -> null + } } Plan: 1 to add, 0 to change, 1 to destroy. -Error: This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed. +Error: this bind operation requires user confirmation, but the current console does not support prompting. +To proceed, use --auto-approve after reviewing the plan above. diff --git a/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt b/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt index af42322deec..4da2a167b7f 100644 --- a/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt +++ b/acceptance/bundle/deployment/bind/pipelines/recreate/output.txt @@ -16,7 +16,9 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.foo -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. >>> [CLI] bundle deploy --auto-approve diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt index 7b858035299..a5e4ca80074 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.direct.txt @@ -11,5 +11,6 @@ Changes detected: ~ name: "lakeflow-pipeline" -> "test-pipeline" ~ root_path: "/Workspace/Users/[USERNAME]/lakeflow_pipeline" -> null -Error: This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed. +Error: this bind operation requires user confirmation, but the current console does not support prompting. +To proceed, use --auto-approve after reviewing the plan above. diff --git a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt index 2ff60fce2b2..deacd3b1a20 100644 --- a/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt +++ b/acceptance/bundle/deployment/bind/pipelines/update/out.bind-fail.terraform.txt @@ -36,10 +36,13 @@ Terraform will perform the following actions: - include = "/Workspace/Users/foo@databricks.com/another/**" -> null } } + + # (1 unchanged block hidden) } Plan: 0 to add, 1 to change, 0 to destroy. -Error: This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed. +Error: this bind operation requires user confirmation, but the current console does not support prompting. +To proceed, use --auto-approve after reviewing the plan above. 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/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/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/generate/app_not_yet_deployed/output.txt b/acceptance/bundle/generate/app_not_yet_deployed/output.txt index b6a68104e53..8c0e62433e5 100644 --- a/acceptance/bundle/generate/app_not_yet_deployed/output.txt +++ b/acceptance/bundle/generate/app_not_yet_deployed/output.txt @@ -2,20 +2,20 @@ >>> [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_size": "MEDIUM", "compute_status": { - "message":"App compute is stopped.", - "state":"STOPPED" + "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/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/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/pipeline_and_deploy/databricks.yml b/acceptance/bundle/generate/pipeline_and_deploy/databricks.yml new file mode 100644 index 00000000000..15b90fec1bb --- /dev/null +++ b/acceptance/bundle/generate/pipeline_and_deploy/databricks.yml @@ -0,0 +1,2 @@ +bundle: + name: pipeline_and_deploy diff --git a/acceptance/bundle/generate/pipeline_and_deploy/notebook.py b/acceptance/bundle/generate/pipeline_and_deploy/notebook.py new file mode 100644 index 00000000000..1a5691c338e --- /dev/null +++ b/acceptance/bundle/generate/pipeline_and_deploy/notebook.py @@ -0,0 +1,2 @@ +# Databricks notebook source +print("Hello world!") diff --git a/acceptance/bundle/generate/pipeline_and_deploy/out.test.toml b/acceptance/bundle/generate/pipeline_and_deploy/out.test.toml new file mode 100644 index 00000000000..bbc7fcfd1bd --- /dev/null +++ b/acceptance/bundle/generate/pipeline_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/pipeline_and_deploy/output.txt b/acceptance/bundle/generate/pipeline_and_deploy/output.txt new file mode 100644 index 00000000000..25943c80bcf --- /dev/null +++ b/acceptance/bundle/generate/pipeline_and_deploy/output.txt @@ -0,0 +1,32 @@ + +=== Upload files to workspace +>>> [CLI] workspace import /Workspace/Users/[USERNAME]/notebook.py --file notebook.py --format AUTO --overwrite + +>>> [CLI] workspace import /Workspace/Users/[USERNAME]/test.py --file test.py --format AUTO --overwrite + +=== Create a pipeline that references the filesCreated pipeline + +=== Generate bundle config from the pipelineFile successfully saved to src/notebook.py +File successfully saved to src/test.py +Pipeline configuration successfully saved to resources/out.pipeline.yml + +=== Verify generated yaml has expected fields +=== Deploy the generated bundle +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/pipeline_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/pipeline_and_deploy/default + +Deleting files... +Destroy complete! + +=== Cleanup: delete the original pipeline and files +>>> errcode [CLI] pipelines delete [PIPELINE_ID] + +>>> errcode [CLI] workspace delete /Workspace/Users/[USERNAME]/notebook + +>>> errcode [CLI] workspace delete /Workspace/Users/[USERNAME]/test.py diff --git a/acceptance/bundle/generate/pipeline_and_deploy/script b/acceptance/bundle/generate/pipeline_and_deploy/script new file mode 100644 index 00000000000..db8e519574c --- /dev/null +++ b/acceptance/bundle/generate/pipeline_and_deploy/script @@ -0,0 +1,42 @@ +title "Upload files to workspace" +trace $CLI workspace import "/Workspace/Users/${CURRENT_USER_NAME}/notebook.py" --file notebook.py --format AUTO --overwrite +trace $CLI workspace import "/Workspace/Users/${CURRENT_USER_NAME}/test.py" --file test.py --format AUTO --overwrite + +title "Create a pipeline that references the files" +PIPELINE_ID=$($CLI pipelines create --json '{ + "name": "test-pipeline-${UNIQUE_NAME}", + "libraries": [ + { + "notebook": { + "path": "/Workspace/Users/'${CURRENT_USER_NAME}'/notebook" + } + }, + { + "file": { + "path": "/Workspace/Users/'${CURRENT_USER_NAME}'/test.py" + } + } + ] +}' | jq -r '.pipeline_id') +echo "Created pipeline" +env -u MSYS_NO_PATHCONV add_repl.py "$PIPELINE_ID" PIPELINE_ID + +cleanup() { + title "Cleanup: delete the original pipeline and files" + trace errcode $CLI pipelines delete "$PIPELINE_ID" + trace errcode $CLI workspace delete "/Workspace/Users/${CURRENT_USER_NAME}/notebook" + trace errcode $CLI workspace delete "/Workspace/Users/${CURRENT_USER_NAME}/test.py" +} +trap cleanup EXIT + +title "Generate bundle config from the pipeline" +$CLI bundle generate pipeline --existing-pipeline-id "$PIPELINE_ID" --key out --config-dir resources --source-dir src --force 2>&1 | sort + +title "Verify generated yaml has expected fields" +cat resources/out.pipeline.yml | env -u MSYS_NO_PATHCONV contains.py "libraries:" "- notebook:" "path: ../src/notebook.py" "- file:" "path: ../src/test.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/pipeline_and_deploy/test.py b/acceptance/bundle/generate/pipeline_and_deploy/test.py new file mode 100644 index 00000000000..693eaecb2b1 --- /dev/null +++ b/acceptance/bundle/generate/pipeline_and_deploy/test.py @@ -0,0 +1 @@ +print("Hello!") diff --git a/acceptance/bundle/generate/pipeline_and_deploy/test.toml b/acceptance/bundle/generate/pipeline_and_deploy/test.toml new file mode 100644 index 00000000000..3eba85b404a --- /dev/null +++ b/acceptance/bundle/generate/pipeline_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/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..1ae0e2142f2 --- /dev/null +++ b/acceptance/bundle/generate/python_job_and_deploy/script @@ -0,0 +1,45 @@ +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" + }, + "new_cluster": { + "spark_version": "'${DEFAULT_SPARK_VERSION}'", + "node_type_id": "'${NODE_TYPE_ID}'", + "num_workers": 1 + } + } + ] +}' | 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/integration_whl/wrapper/out.test.toml b/acceptance/bundle/integration_whl/wrapper/out.test.toml index f82a2ac4481..44a1a2186a1 100644 --- a/acceptance/bundle/integration_whl/wrapper/out.test.toml +++ b/acceptance/bundle/integration_whl/wrapper/out.test.toml @@ -1,5 +1,6 @@ Local = true Cloud = true CloudSlow = true +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 f82a2ac4481..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,5 +1,6 @@ Local = true Cloud = true CloudSlow = true +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/postgres_catalog.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_catalog.yml.tmpl new file mode 100644 index 00000000000..2ae10852504 --- /dev/null +++ b/acceptance/bundle/invariant/configs/postgres_catalog.yml.tmpl @@ -0,0 +1,15 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + postgres_projects: + project: + project_id: test-pg-project-$UNIQUE_NAME + display_name: Test Postgres Project + + postgres_catalogs: + catalog: + catalog_id: test_pg_catalog_$UNIQUE_NAME + branch: ${resources.postgres_projects.project.name}/branches/production + postgres_database: appdb + create_database_if_missing: true diff --git a/acceptance/bundle/invariant/configs/postgres_synced_table.yml.tmpl b/acceptance/bundle/invariant/configs/postgres_synced_table.yml.tmpl new file mode 100644 index 00000000000..7a548a02756 --- /dev/null +++ b/acceptance/bundle/invariant/configs/postgres_synced_table.yml.tmpl @@ -0,0 +1,16 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + postgres_synced_tables: + foo: + synced_table_id: lakebase_$UNIQUE_NAME.public.trips_synced + source_table_full_name: main.raw.trips + primary_key_columns: [id] + scheduling_policy: SNAPSHOT + postgres_database: appdb + branch: projects/test-pg-project-$UNIQUE_NAME/branches/production + create_database_objects_if_missing: true + new_pipeline_spec: + storage_catalog: main + storage_schema: pipelines diff --git a/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl b/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl index 1befb4e157e..cea1a4d026c 100644 --- a/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl +++ b/acceptance/bundle/invariant/configs/vector_search_endpoint.yml.tmpl @@ -12,4 +12,4 @@ resources: endpoint_type: STANDARD permissions: - level: CAN_USE - user_name: viewer@example.com + group_name: users diff --git a/acceptance/bundle/invariant/continue_293/out.test.toml b/acceptance/bundle/invariant/continue_293/out.test.toml index 11aaf584918..0b5736c7f00 100644 --- a/acceptance/bundle/invariant/continue_293/out.test.toml +++ b/acceptance/bundle/invariant/continue_293/out.test.toml @@ -26,8 +26,10 @@ EnvMatrix.INPUT_CONFIG = [ "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", + "postgres_catalog.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", + "postgres_synced_table.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", diff --git a/acceptance/bundle/invariant/continue_293/test.toml b/acceptance/bundle/invariant/continue_293/test.toml index 91c45e0dd76..2afbcbf5e31 100644 --- a/acceptance/bundle/invariant/continue_293/test.toml +++ b/acceptance/bundle/invariant/continue_293/test.toml @@ -11,3 +11,7 @@ EnvMatrixExclude.no_vector_search_endpoint = ["INPUT_CONFIG=vector_search_endpoi # 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 11aaf584918..0b5736c7f00 100644 --- a/acceptance/bundle/invariant/migrate/out.test.toml +++ b/acceptance/bundle/invariant/migrate/out.test.toml @@ -26,8 +26,10 @@ EnvMatrix.INPUT_CONFIG = [ "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", + "postgres_catalog.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", + "postgres_synced_table.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", diff --git a/acceptance/bundle/invariant/migrate/test.toml b/acceptance/bundle/invariant/migrate/test.toml index adc49c2992e..240a32d5d4c 100644 --- a/acceptance/bundle/invariant/migrate/test.toml +++ b/acceptance/bundle/invariant/migrate/test.toml @@ -19,3 +19,7 @@ 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 11aaf584918..0b5736c7f00 100644 --- a/acceptance/bundle/invariant/no_drift/out.test.toml +++ b/acceptance/bundle/invariant/no_drift/out.test.toml @@ -26,8 +26,10 @@ EnvMatrix.INPUT_CONFIG = [ "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", + "postgres_catalog.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", + "postgres_synced_table.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", diff --git a/acceptance/bundle/invariant/test.toml b/acceptance/bundle/invariant/test.toml index beabef5ef1e..021beef6348 100644 --- a/acceptance/bundle/invariant/test.toml +++ b/acceptance/bundle/invariant/test.toml @@ -44,8 +44,10 @@ EnvMatrix.INPUT_CONFIG = [ "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", + "postgres_catalog.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", + "postgres_synced_table.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", @@ -67,6 +69,8 @@ no_alert_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=alert.yml.tmpl"] no_postgres_project_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_project.yml.tmpl"] no_postgres_branch_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_branch.yml.tmpl"] no_postgres_endpoint_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_endpoint.yml.tmpl"] +no_postgres_catalog_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_catalog.yml.tmpl"] +no_postgres_synced_table_on_cloud = ["CONFIG_Cloud=true", "INPUT_CONFIG=postgres_synced_table.yml.tmpl"] # External locations require actual storage credentials with cloud IAM setup # which are environment-specific, so we only test locally with the mock server @@ -81,5 +85,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/migrate/basic/out.original_state.json b/acceptance/bundle/migrate/basic/out.original_state.json index 65e976ff461..1deac23037a 100644 --- a/acceptance/bundle/migrate/basic/out.original_state.json +++ b/acceptance/bundle/migrate/basic/out.original_state.json @@ -55,7 +55,11 @@ "parameter": [], "performance_target": null, "pipeline_task": [], - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "python_wheel_task": [], "queue": [ { @@ -208,7 +212,11 @@ "name": "Test Migration Pipeline", "notification": [], "photon": false, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "restart_window": [], "root_path": null, "run_as": [], @@ -254,7 +262,11 @@ "id": "mycat.myschema.myvol", "name": "myvol", "owner": "[USERNAME]", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "schema_name": "myschema", "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", "volume_path": "/Volumes/mycat/myschema/myvol", diff --git a/acceptance/bundle/migrate/basic/out.plan_update.json b/acceptance/bundle/migrate/basic/out.plan_update.json index 44ba986a2f6..99e22ec08b7 100644 --- a/acceptance/bundle/migrate/basic/out.plan_update.json +++ b/acceptance/bundle/migrate/basic/out.plan_update.json @@ -2,7 +2,7 @@ "plan_version": 2, "cli_version": "[DEV_VERSION]", "lineage": "[UUID]", - "serial": 8, + "serial": 6, "plan": { "resources.jobs.test_job": { "action": "update", diff --git a/acceptance/bundle/migrate/basic/output.txt b/acceptance/bundle/migrate/basic/output.txt index 0d31bbd682f..dafa3a4086e 100644 --- a/acceptance/bundle/migrate/basic/output.txt +++ b/acceptance/bundle/migrate/basic/output.txt @@ -39,7 +39,7 @@ Deployment complete! === Should show that it's already migrated >>> musterr [CLI] bundle deployment migrate Error: already using direct engine -Details: [TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=7 lineage="[UUID]" +Details: [TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=6 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE=direct [CLI] bundle plan Plan: 0 to add, 0 to change, 0 to delete, 3 unchanged @@ -86,14 +86,14 @@ Deployment complete! === Should show that it's already migrated >>> musterr [CLI] bundle deployment migrate Error: already using direct engine -Details: [TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=8 lineage="[UUID]" +Details: [TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=6 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states -[TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=8 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=6 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states --force-pull -resources.json: remote direct state serial=8 lineage="[UUID]" -[TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=8 lineage="[UUID]" +resources.json: remote direct state serial=6 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/dev/resources.json: local direct state serial=6 lineage="[UUID]" === Extra plan: should have no drift >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle plan diff --git a/acceptance/bundle/migrate/dashboards/out.original_state.json b/acceptance/bundle/migrate/dashboards/out.original_state.json index fd7f588ea60..588895fb3c5 100644 --- a/acceptance/bundle/migrate/dashboards/out.original_state.json +++ b/acceptance/bundle/migrate/dashboards/out.original_state.json @@ -28,7 +28,11 @@ "md5": "b4ab6aee48485b0b5e980761d3efe936", "parent_path": "/Users/[USERNAME]", "path": "/Users/[USERNAME]/my dashboard.lvdash.json", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "serialized_dashboard": "{\"pages\":[{\"displayName\":\"Dashboard test bundle-deploy-dashboard\",\"name\":\"02724bf2\",\"pageType\":\"PAGE_TYPE_CANVAS\"}]}\n", "update_time": "[TIMESTAMP]", "warehouse_id": "123456" diff --git a/acceptance/bundle/migrate/dashboards/out.plan_after_migrate.json b/acceptance/bundle/migrate/dashboards/out.plan_after_migrate.json index 0f73ce72be1..6b55f64bd8d 100644 --- a/acceptance/bundle/migrate/dashboards/out.plan_after_migrate.json +++ b/acceptance/bundle/migrate/dashboards/out.plan_after_migrate.json @@ -2,7 +2,7 @@ "plan_version": 2, "cli_version": "[DEV_VERSION]", "lineage": "[UUID]", - "serial": 4, + "serial": 3, "plan": { "resources.dashboards.dashboard1": { "action": "skip", diff --git a/acceptance/bundle/migrate/dashboards/output.txt b/acceptance/bundle/migrate/dashboards/output.txt index 7cbd91a2f68..19a4f1c7bb5 100644 --- a/acceptance/bundle/migrate/dashboards/output.txt +++ b/acceptance/bundle/migrate/dashboards/output.txt @@ -47,11 +47,11 @@ Deployment complete! === Should show that it's already migrated >>> musterr [CLI] bundle deployment migrate Error: already using direct engine -Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=5 lineage="[UUID]" +Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=3 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=5 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=3 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states --force-pull -resources.json: remote direct state serial=5 lineage="[UUID]" -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=5 lineage="[UUID]" +resources.json: remote direct state serial=3 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=3 lineage="[UUID]" diff --git a/acceptance/bundle/migrate/default-python/out.state_original.json b/acceptance/bundle/migrate/default-python/out.state_original.json index a91be18e2d2..cab881e6d51 100644 --- a/acceptance/bundle/migrate/default-python/out.state_original.json +++ b/acceptance/bundle/migrate/default-python/out.state_original.json @@ -116,7 +116,11 @@ ], "performance_target": null, "pipeline_task": [], - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "python_wheel_task": [], "queue": [ { @@ -441,7 +445,11 @@ "name": "[dev [USERNAME]] my_default_python_etl", "notification": [], "photon": false, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "restart_window": [], "root_path": "/Workspace/Users/[USERNAME]/.bundle/my_default_python/dev/files/src/my_default_python_etl", "run_as": [], diff --git a/acceptance/bundle/migrate/grants/out.original_state.json b/acceptance/bundle/migrate/grants/out.original_state.json index a410fff585a..550f69cfe70 100644 --- a/acceptance/bundle/migrate/grants/out.original_state.json +++ b/acceptance/bundle/migrate/grants/out.original_state.json @@ -31,7 +31,11 @@ "metastore": null, "model": null, "pipeline": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "recipient": null, "schema": null, "share": null, @@ -75,7 +79,11 @@ "metastore": null, "model": null, "pipeline": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "recipient": null, "schema": "main.schema_grants", "share": null, @@ -118,7 +126,11 @@ "metastore": null, "model": null, "pipeline": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "recipient": null, "schema": null, "share": null, @@ -155,7 +167,11 @@ "metastore_id": "[UUID]", "name": "mymodel", "owner": "[USERNAME]", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "schema_name": "schema_grants", "storage_location": null, "updated_at": [UNIX_TIME_MILLIS][0], @@ -187,7 +203,11 @@ "name": "schema_grants", "owner": "[USERNAME]", "properties": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "schema_id": "[UUID]", "storage_root": null }, @@ -210,7 +230,11 @@ "id": "main.schema_grants.volume_name", "name": "volume_name", "owner": "[USERNAME]", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "schema_name": "schema_grants", "storage_location": "s3://[METASTORE_NAME]/metastore/[UUID]/volumes/[UUID]", "volume_path": "/Volumes/main/schema_grants/volume_name", diff --git a/acceptance/bundle/migrate/grants/output.txt b/acceptance/bundle/migrate/grants/output.txt index 44ec67fb48a..146787d549a 100644 --- a/acceptance/bundle/migrate/grants/output.txt +++ b/acceptance/bundle/migrate/grants/output.txt @@ -45,11 +45,11 @@ Deployment complete! === Should show that it's already migrated >>> musterr [CLI] bundle deployment migrate Error: already using direct engine -Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=11 lineage="[UUID]" +Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=9 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=11 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=9 lineage="[UUID]" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states --force-pull -resources.json: remote direct state serial=11 lineage="[UUID]" -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=11 lineage="[UUID]" +resources.json: remote direct state serial=9 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=9 lineage="[UUID]" diff --git a/acceptance/bundle/migrate/permissions/out.original_state.json b/acceptance/bundle/migrate/permissions/out.original_state.json index 866dadca8ac..dffac5aa8ca 100644 --- a/acceptance/bundle/migrate/permissions/out.original_state.json +++ b/acceptance/bundle/migrate/permissions/out.original_state.json @@ -55,7 +55,11 @@ "parameter": [], "performance_target": null, "pipeline_task": [], - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "python_wheel_task": [], "queue": [ { @@ -195,7 +199,11 @@ "notebook_path": null, "object_type": "job", "pipeline_id": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "registered_model_id": null, "repo_id": null, "repo_path": null, @@ -257,7 +265,11 @@ "notebook_path": null, "object_type": "pipelines", "pipeline_id": "[UUID]", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "registered_model_id": null, "repo_id": null, "repo_path": null, @@ -332,7 +344,11 @@ "name": "Test Migration Pipeline", "notification": [], "photon": false, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "restart_window": [], "root_path": null, "run_as": [], diff --git a/acceptance/bundle/migrate/permissions/output.txt b/acceptance/bundle/migrate/permissions/output.txt index 953a4bae979..f85c8d7bdbf 100644 --- a/acceptance/bundle/migrate/permissions/output.txt +++ b/acceptance/bundle/migrate/permissions/output.txt @@ -62,11 +62,11 @@ Deployment complete! === Should show that it's already migrated >>> musterr [CLI] bundle deployment migrate Error: already using direct engine -Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=8 lineage="[UUID]" +Details: [TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=7 lineage="[UUID]" >>> [CLI] bundle debug states -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=8 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=7 lineage="[UUID]" >>> [CLI] bundle debug states --force-pull -resources.json: remote direct state serial=8 lineage="[UUID]" -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=8 lineage="[UUID]" +resources.json: remote direct state serial=7 lineage="[UUID]" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=7 lineage="[UUID]" diff --git a/acceptance/bundle/migrate/runas/out.create_requests.json b/acceptance/bundle/migrate/runas/out.create_requests.json index 65cdc895abc..65dd4044d9e 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.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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.old_state.json b/acceptance/bundle/migrate/runas/out.old_state.json index 2d0fba02fd9..49b51c2a30d 100644 --- a/acceptance/bundle/migrate/runas/out.old_state.json +++ b/acceptance/bundle/migrate/runas/out.old_state.json @@ -46,7 +46,11 @@ "notebook_path": null, "object_type": "pipelines", "pipeline_id": "[UUID]", - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "registered_model_id": null, "repo_id": null, "repo_path": null, @@ -121,7 +125,11 @@ "name": "DABs Revenue Pipeline", "notification": [], "photon": false, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "restart_window": [], "root_path": null, "run_as": [ 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/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 519954aedc9..f784a183258 100644 --- a/acceptance/bundle/open/out.test.toml +++ b/acceptance/bundle/open/out.test.toml @@ -1,5 +1,3 @@ Local = true Cloud = false -GOOS.linux = false -GOOS.windows = false 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/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/refschema/out.fields.txt b/acceptance/bundle/refschema/out.fields.txt index 5a55ba006ee..f433389916f 100644 --- a/acceptance/bundle/refschema/out.fields.txt +++ b/acceptance/bundle/refschema/out.fields.txt @@ -210,6 +210,7 @@ resources.apps.*.telemetry_export_destinations[*].unity_catalog *apps.UnityCatal resources.apps.*.telemetry_export_destinations[*].unity_catalog.logs_table string ALL resources.apps.*.telemetry_export_destinations[*].unity_catalog.metrics_table string ALL resources.apps.*.telemetry_export_destinations[*].unity_catalog.traces_table string ALL +resources.apps.*.thumbnail_url string ALL resources.apps.*.update_time string ALL resources.apps.*.updater string ALL resources.apps.*.url string ALL @@ -353,6 +354,7 @@ resources.clusters.*.executors[*].start_timestamp int64 REMOTE resources.clusters.*.gcp_attributes *compute.GcpAttributes ALL resources.clusters.*.gcp_attributes.availability compute.GcpAvailability ALL resources.clusters.*.gcp_attributes.boot_disk_size int ALL +resources.clusters.*.gcp_attributes.confidential_compute_type compute.ConfidentialComputeType ALL resources.clusters.*.gcp_attributes.first_on_demand int ALL resources.clusters.*.gcp_attributes.google_service_account string ALL resources.clusters.*.gcp_attributes.local_ssd_count int ALL @@ -458,6 +460,7 @@ resources.clusters.*.spec.enable_local_disk_encryption bool REMOTE resources.clusters.*.spec.gcp_attributes *compute.GcpAttributes REMOTE resources.clusters.*.spec.gcp_attributes.availability compute.GcpAvailability REMOTE resources.clusters.*.spec.gcp_attributes.boot_disk_size int REMOTE +resources.clusters.*.spec.gcp_attributes.confidential_compute_type compute.ConfidentialComputeType REMOTE resources.clusters.*.spec.gcp_attributes.first_on_demand int REMOTE resources.clusters.*.spec.gcp_attributes.google_service_account string REMOTE resources.clusters.*.spec.gcp_attributes.local_ssd_count int REMOTE @@ -844,6 +847,7 @@ resources.jobs.*.job_clusters[*].new_cluster.enable_local_disk_encryption bool A resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes *compute.GcpAttributes ALL resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.availability compute.GcpAvailability ALL resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.boot_disk_size int ALL +resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type compute.ConfidentialComputeType ALL resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.first_on_demand int ALL resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.google_service_account string ALL resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.local_ssd_count int ALL @@ -1163,6 +1167,7 @@ resources.jobs.*.tasks[*].for_each_task.task.new_cluster.enable_local_disk_encry resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes *compute.GcpAttributes ALL resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability compute.GcpAvailability ALL resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.boot_disk_size int ALL +resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type compute.ConfidentialComputeType ALL resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.first_on_demand int ALL resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.google_service_account string ALL resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.local_ssd_count int ALL @@ -1227,7 +1232,15 @@ resources.jobs.*.tasks[*].for_each_task.task.notification_settings.no_alert_for_ resources.jobs.*.tasks[*].for_each_task.task.notification_settings.no_alert_for_skipped_runs bool ALL resources.jobs.*.tasks[*].for_each_task.task.pipeline_task *jobs.PipelineTask ALL resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.full_refresh bool ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.full_refresh_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.full_refresh_selection[*] string ALL resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.pipeline_id string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.refresh_flow_selection[*] string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.refresh_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.refresh_selection[*] string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.pipeline_task.reset_checkpoint_selection[*] string ALL resources.jobs.*.tasks[*].for_each_task.task.power_bi_task *jobs.PowerBiTask ALL resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.connection_resource_name string ALL resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.power_bi_model *jobs.PowerBiModel ALL @@ -1244,6 +1257,12 @@ resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.tables[*].name string resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.tables[*].schema string ALL resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode jobs.StorageMode ALL resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.warehouse_id string ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task *jobs.PythonOperatorTask ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task.main string ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task.parameters []jobs.PythonOperatorTaskParameter ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task.parameters[*] jobs.PythonOperatorTaskParameter ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task.parameters[*].name string ALL +resources.jobs.*.tasks[*].for_each_task.task.python_operator_task.parameters[*].value string ALL resources.jobs.*.tasks[*].for_each_task.task.python_wheel_task *jobs.PythonWheelTask ALL resources.jobs.*.tasks[*].for_each_task.task.python_wheel_task.entry_point string ALL resources.jobs.*.tasks[*].for_each_task.task.python_wheel_task.named_parameters map[string]string ALL @@ -1265,6 +1284,14 @@ resources.jobs.*.tasks[*].for_each_task.task.run_job_task.notebook_params map[st resources.jobs.*.tasks[*].for_each_task.task.run_job_task.notebook_params.* string ALL resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params *jobs.PipelineParams ALL resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh bool ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.full_refresh_selection[*] string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_flow_selection[*] string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.refresh_selection[*] string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection []string ALL +resources.jobs.*.tasks[*].for_each_task.task.run_job_task.pipeline_params.reset_checkpoint_selection[*] string ALL resources.jobs.*.tasks[*].for_each_task.task.run_job_task.python_named_params map[string]string ALL resources.jobs.*.tasks[*].for_each_task.task.run_job_task.python_named_params.* string ALL resources.jobs.*.tasks[*].for_each_task.task.run_job_task.python_params []string ALL @@ -1423,6 +1450,7 @@ resources.jobs.*.tasks[*].new_cluster.enable_local_disk_encryption bool ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes *compute.GcpAttributes ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes.availability compute.GcpAvailability ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes.boot_disk_size int ALL +resources.jobs.*.tasks[*].new_cluster.gcp_attributes.confidential_compute_type compute.ConfidentialComputeType ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes.first_on_demand int ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes.google_service_account string ALL resources.jobs.*.tasks[*].new_cluster.gcp_attributes.local_ssd_count int ALL @@ -1487,7 +1515,15 @@ resources.jobs.*.tasks[*].notification_settings.no_alert_for_canceled_runs bool resources.jobs.*.tasks[*].notification_settings.no_alert_for_skipped_runs bool ALL resources.jobs.*.tasks[*].pipeline_task *jobs.PipelineTask ALL resources.jobs.*.tasks[*].pipeline_task.full_refresh bool ALL +resources.jobs.*.tasks[*].pipeline_task.full_refresh_selection []string ALL +resources.jobs.*.tasks[*].pipeline_task.full_refresh_selection[*] string ALL resources.jobs.*.tasks[*].pipeline_task.pipeline_id string ALL +resources.jobs.*.tasks[*].pipeline_task.refresh_flow_selection []string ALL +resources.jobs.*.tasks[*].pipeline_task.refresh_flow_selection[*] string ALL +resources.jobs.*.tasks[*].pipeline_task.refresh_selection []string ALL +resources.jobs.*.tasks[*].pipeline_task.refresh_selection[*] string ALL +resources.jobs.*.tasks[*].pipeline_task.reset_checkpoint_selection []string ALL +resources.jobs.*.tasks[*].pipeline_task.reset_checkpoint_selection[*] string ALL resources.jobs.*.tasks[*].power_bi_task *jobs.PowerBiTask ALL resources.jobs.*.tasks[*].power_bi_task.connection_resource_name string ALL resources.jobs.*.tasks[*].power_bi_task.power_bi_model *jobs.PowerBiModel ALL @@ -1504,6 +1540,12 @@ resources.jobs.*.tasks[*].power_bi_task.tables[*].name string ALL resources.jobs.*.tasks[*].power_bi_task.tables[*].schema string ALL resources.jobs.*.tasks[*].power_bi_task.tables[*].storage_mode jobs.StorageMode ALL resources.jobs.*.tasks[*].power_bi_task.warehouse_id string ALL +resources.jobs.*.tasks[*].python_operator_task *jobs.PythonOperatorTask ALL +resources.jobs.*.tasks[*].python_operator_task.main string ALL +resources.jobs.*.tasks[*].python_operator_task.parameters []jobs.PythonOperatorTaskParameter ALL +resources.jobs.*.tasks[*].python_operator_task.parameters[*] jobs.PythonOperatorTaskParameter ALL +resources.jobs.*.tasks[*].python_operator_task.parameters[*].name string ALL +resources.jobs.*.tasks[*].python_operator_task.parameters[*].value string ALL resources.jobs.*.tasks[*].python_wheel_task *jobs.PythonWheelTask ALL resources.jobs.*.tasks[*].python_wheel_task.entry_point string ALL resources.jobs.*.tasks[*].python_wheel_task.named_parameters map[string]string ALL @@ -1525,6 +1567,14 @@ resources.jobs.*.tasks[*].run_job_task.notebook_params map[string]string ALL resources.jobs.*.tasks[*].run_job_task.notebook_params.* string ALL resources.jobs.*.tasks[*].run_job_task.pipeline_params *jobs.PipelineParams ALL resources.jobs.*.tasks[*].run_job_task.pipeline_params.full_refresh bool ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.full_refresh_selection []string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.full_refresh_selection[*] string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.refresh_flow_selection []string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.refresh_flow_selection[*] string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.refresh_selection []string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.refresh_selection[*] string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection []string ALL +resources.jobs.*.tasks[*].run_job_task.pipeline_params.reset_checkpoint_selection[*] string ALL resources.jobs.*.tasks[*].run_job_task.python_named_params map[string]string ALL resources.jobs.*.tasks[*].run_job_task.python_named_params.* string ALL resources.jobs.*.tasks[*].run_job_task.python_params []string ALL @@ -2205,6 +2255,7 @@ resources.pipelines.*.clusters[*].enable_local_disk_encryption bool ALL resources.pipelines.*.clusters[*].gcp_attributes *compute.GcpAttributes ALL resources.pipelines.*.clusters[*].gcp_attributes.availability compute.GcpAvailability ALL resources.pipelines.*.clusters[*].gcp_attributes.boot_disk_size int ALL +resources.pipelines.*.clusters[*].gcp_attributes.confidential_compute_type compute.ConfidentialComputeType ALL resources.pipelines.*.clusters[*].gcp_attributes.first_on_demand int ALL resources.pipelines.*.clusters[*].gcp_attributes.google_service_account string ALL resources.pipelines.*.clusters[*].gcp_attributes.local_ssd_count int ALL @@ -2330,6 +2381,9 @@ resources.pipelines.*.ingestion_definition.objects[*].report.table_configuration 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.confluence_options *pipelines.ConfluenceConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.confluence_options.include_confluence_spaces[*] string 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 @@ -2354,6 +2408,63 @@ resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.g 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.jira_options *pipelines.JiraConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.jira_options.include_jira_spaces[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options *pipelines.KafkaOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.client_config.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer *pipelines.Transformer ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.format pipelines.TransformerFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options *pipelines.JsonTransformerOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.as_variant bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_file_path string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.max_offsets_per_trigger int64 ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.starting_offset string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.topic_pattern string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.topics []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.topics[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer *pipelines.Transformer ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.format pipelines.TransformerFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options *pipelines.JsonTransformerOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.as_variant bool ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_file_path string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options *pipelines.MetaMarketingOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_attribution_windows[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_breakdowns[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.action_report_time string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.breakdowns[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.custom_insights_lookback_window int ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.level string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.meta_ads_options.time_increment string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options *pipelines.OutlookOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.attachment_mode pipelines.OutlookAttachmentMode ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format pipelines.OutlookBodyFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.folder_filter[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_folders[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_mailboxes[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_senders[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.include_subjects[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.sender_filter[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.subject_filter[*] 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 @@ -2374,6 +2485,8 @@ resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.s 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.smartsheet_options *pipelines.SmartsheetOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.smartsheet_options.enforce_schema bool 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 @@ -2384,6 +2497,8 @@ resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.t 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.connector_options.zendesk_support_options *pipelines.ZendeskSupportOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.zendesk_support_options.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 @@ -2418,6 +2533,9 @@ resources.pipelines.*.ingestion_definition.objects[*].schema.table_configuration 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.confluence_options *pipelines.ConfluenceConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.confluence_options.include_confluence_spaces[*] string 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 @@ -2442,6 +2560,63 @@ resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.go 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.jira_options *pipelines.JiraConnectorOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.jira_options.include_jira_spaces[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options *pipelines.KafkaOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.client_config map[string]string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.client_config.* string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer *pipelines.Transformer ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.format pipelines.TransformerFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options *pipelines.JsonTransformerOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.as_variant bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_file_path string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.max_offsets_per_trigger int64 ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.starting_offset string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.topic_pattern string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.topics []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.topics[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer *pipelines.Transformer ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.format pipelines.TransformerFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options *pipelines.JsonTransformerOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.as_variant bool ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_evolution_mode pipelines.FileIngestionOptionsSchemaEvolutionMode ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_file_path string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_options.schema_hints string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options *pipelines.MetaMarketingOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_attribution_windows[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_breakdowns[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.action_report_time string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.breakdowns[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.custom_insights_lookback_window int ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.level string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.meta_ads_options.time_increment string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options *pipelines.OutlookOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.attachment_mode pipelines.OutlookAttachmentMode ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.body_format pipelines.OutlookBodyFormat ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.folder_filter[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_folders[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_mailboxes[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_senders[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.include_subjects[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.sender_filter[*] string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.start_date string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter []string ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.subject_filter[*] 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 @@ -2462,6 +2637,8 @@ resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.sh 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.smartsheet_options *pipelines.SmartsheetOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.smartsheet_options.enforce_schema bool 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 @@ -2472,6 +2649,8 @@ resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.ti 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.connector_options.zendesk_support_options *pipelines.ZendeskSupportOptions ALL +resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.zendesk_support_options.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 @@ -2514,6 +2693,8 @@ resources.pipelines.*.ingestion_definition.source_configurations[*].catalog.post resources.pipelines.*.ingestion_definition.source_configurations[*].catalog.postgres.slot_config.publication_name string ALL resources.pipelines.*.ingestion_definition.source_configurations[*].catalog.postgres.slot_config.slot_name string ALL resources.pipelines.*.ingestion_definition.source_configurations[*].catalog.source_catalog string ALL +resources.pipelines.*.ingestion_definition.source_configurations[*].google_ads_config *pipelines.GoogleAdsConfig ALL +resources.pipelines.*.ingestion_definition.source_configurations[*].google_ads_config.manager_account_id string ALL resources.pipelines.*.ingestion_definition.source_type pipelines.IngestionSourceType ALL resources.pipelines.*.ingestion_definition.table_configuration *pipelines.TableSpecificConfig ALL resources.pipelines.*.ingestion_definition.table_configuration.auto_full_refresh_policy *pipelines.AutoFullRefreshPolicy ALL @@ -2606,82 +2787,84 @@ resources.pipelines.*.permissions[*].group_name string ALL resources.pipelines.*.permissions[*].level iam.PermissionLevel ALL resources.pipelines.*.permissions[*].service_principal_name string ALL resources.pipelines.*.permissions[*].user_name string ALL -resources.postgres_branches.*.branch_id string INPUT STATE +resources.postgres_branches.*.branch_id string ALL resources.postgres_branches.*.create_time *time.Time REMOTE -resources.postgres_branches.*.expire_time *time.Time INPUT STATE +resources.postgres_branches.*.expire_time *time.Time ALL resources.postgres_branches.*.id string INPUT -resources.postgres_branches.*.is_protected bool INPUT STATE +resources.postgres_branches.*.is_protected bool ALL resources.postgres_branches.*.lifecycle resources.Lifecycle INPUT resources.postgres_branches.*.lifecycle.prevent_destroy bool INPUT resources.postgres_branches.*.modified_status string INPUT resources.postgres_branches.*.name string REMOTE -resources.postgres_branches.*.no_expiry bool INPUT STATE +resources.postgres_branches.*.no_expiry bool ALL resources.postgres_branches.*.parent string ALL -resources.postgres_branches.*.source_branch string INPUT STATE -resources.postgres_branches.*.source_branch_lsn string INPUT STATE -resources.postgres_branches.*.source_branch_time *time.Time INPUT STATE -resources.postgres_branches.*.spec *postgres.BranchSpec REMOTE -resources.postgres_branches.*.spec.expire_time *time.Time REMOTE -resources.postgres_branches.*.spec.is_protected bool REMOTE -resources.postgres_branches.*.spec.no_expiry bool REMOTE -resources.postgres_branches.*.spec.source_branch string REMOTE -resources.postgres_branches.*.spec.source_branch_lsn string REMOTE -resources.postgres_branches.*.spec.source_branch_time *time.Time REMOTE -resources.postgres_branches.*.spec.ttl *duration.Duration REMOTE +resources.postgres_branches.*.replace_existing bool INPUT STATE +resources.postgres_branches.*.source_branch string ALL +resources.postgres_branches.*.source_branch_lsn string ALL +resources.postgres_branches.*.source_branch_time *time.Time ALL resources.postgres_branches.*.status *postgres.BranchStatus REMOTE +resources.postgres_branches.*.status.branch_id string REMOTE resources.postgres_branches.*.status.current_state postgres.BranchStatusState REMOTE resources.postgres_branches.*.status.default bool REMOTE +resources.postgres_branches.*.status.delete_time *time.Time REMOTE resources.postgres_branches.*.status.expire_time *time.Time REMOTE resources.postgres_branches.*.status.is_protected bool REMOTE resources.postgres_branches.*.status.logical_size_bytes int64 REMOTE resources.postgres_branches.*.status.pending_state postgres.BranchStatusState REMOTE +resources.postgres_branches.*.status.purge_time *time.Time REMOTE resources.postgres_branches.*.status.source_branch string REMOTE resources.postgres_branches.*.status.source_branch_lsn string REMOTE resources.postgres_branches.*.status.source_branch_time *time.Time REMOTE resources.postgres_branches.*.status.state_change_time *time.Time REMOTE -resources.postgres_branches.*.ttl *duration.Duration INPUT STATE +resources.postgres_branches.*.ttl *duration.Duration ALL resources.postgres_branches.*.uid string REMOTE resources.postgres_branches.*.update_time *time.Time REMOTE resources.postgres_branches.*.url string INPUT -resources.postgres_endpoints.*.autoscaling_limit_max_cu float64 INPUT STATE -resources.postgres_endpoints.*.autoscaling_limit_min_cu float64 INPUT STATE +resources.postgres_catalogs.*.branch string ALL +resources.postgres_catalogs.*.catalog_id string ALL +resources.postgres_catalogs.*.create_database_if_missing bool ALL +resources.postgres_catalogs.*.create_time *time.Time REMOTE +resources.postgres_catalogs.*.id string INPUT +resources.postgres_catalogs.*.lifecycle resources.Lifecycle INPUT +resources.postgres_catalogs.*.lifecycle.prevent_destroy bool INPUT +resources.postgres_catalogs.*.modified_status string INPUT +resources.postgres_catalogs.*.name string REMOTE +resources.postgres_catalogs.*.postgres_database string ALL +resources.postgres_catalogs.*.status *postgres.CatalogCatalogStatus REMOTE +resources.postgres_catalogs.*.status.branch string REMOTE +resources.postgres_catalogs.*.status.catalog_id string REMOTE +resources.postgres_catalogs.*.status.postgres_database string REMOTE +resources.postgres_catalogs.*.status.project string REMOTE +resources.postgres_catalogs.*.uid string REMOTE +resources.postgres_catalogs.*.update_time *time.Time REMOTE +resources.postgres_catalogs.*.url string INPUT +resources.postgres_endpoints.*.autoscaling_limit_max_cu float64 ALL +resources.postgres_endpoints.*.autoscaling_limit_min_cu float64 ALL resources.postgres_endpoints.*.create_time *time.Time REMOTE -resources.postgres_endpoints.*.disabled bool INPUT STATE -resources.postgres_endpoints.*.endpoint_id string INPUT STATE -resources.postgres_endpoints.*.endpoint_type postgres.EndpointType INPUT STATE -resources.postgres_endpoints.*.group *postgres.EndpointGroupSpec INPUT STATE -resources.postgres_endpoints.*.group.enable_readable_secondaries bool INPUT STATE -resources.postgres_endpoints.*.group.max int INPUT STATE -resources.postgres_endpoints.*.group.min int INPUT STATE +resources.postgres_endpoints.*.disabled bool ALL +resources.postgres_endpoints.*.endpoint_id string ALL +resources.postgres_endpoints.*.endpoint_type postgres.EndpointType ALL +resources.postgres_endpoints.*.group *postgres.EndpointGroupSpec ALL +resources.postgres_endpoints.*.group.enable_readable_secondaries bool ALL +resources.postgres_endpoints.*.group.max int ALL +resources.postgres_endpoints.*.group.min int ALL resources.postgres_endpoints.*.id string INPUT resources.postgres_endpoints.*.lifecycle resources.Lifecycle INPUT resources.postgres_endpoints.*.lifecycle.prevent_destroy bool INPUT resources.postgres_endpoints.*.modified_status string INPUT resources.postgres_endpoints.*.name string REMOTE -resources.postgres_endpoints.*.no_suspension bool INPUT STATE +resources.postgres_endpoints.*.no_suspension bool ALL resources.postgres_endpoints.*.parent string ALL -resources.postgres_endpoints.*.settings *postgres.EndpointSettings INPUT STATE -resources.postgres_endpoints.*.settings.pg_settings map[string]string INPUT STATE -resources.postgres_endpoints.*.settings.pg_settings.* string INPUT STATE -resources.postgres_endpoints.*.spec *postgres.EndpointSpec REMOTE -resources.postgres_endpoints.*.spec.autoscaling_limit_max_cu float64 REMOTE -resources.postgres_endpoints.*.spec.autoscaling_limit_min_cu float64 REMOTE -resources.postgres_endpoints.*.spec.disabled bool REMOTE -resources.postgres_endpoints.*.spec.endpoint_type postgres.EndpointType REMOTE -resources.postgres_endpoints.*.spec.group *postgres.EndpointGroupSpec REMOTE -resources.postgres_endpoints.*.spec.group.enable_readable_secondaries bool REMOTE -resources.postgres_endpoints.*.spec.group.max int REMOTE -resources.postgres_endpoints.*.spec.group.min int REMOTE -resources.postgres_endpoints.*.spec.no_suspension bool REMOTE -resources.postgres_endpoints.*.spec.settings *postgres.EndpointSettings REMOTE -resources.postgres_endpoints.*.spec.settings.pg_settings map[string]string REMOTE -resources.postgres_endpoints.*.spec.settings.pg_settings.* string REMOTE -resources.postgres_endpoints.*.spec.suspend_timeout_duration *duration.Duration REMOTE +resources.postgres_endpoints.*.replace_existing bool INPUT STATE +resources.postgres_endpoints.*.settings *postgres.EndpointSettings ALL +resources.postgres_endpoints.*.settings.pg_settings map[string]string ALL +resources.postgres_endpoints.*.settings.pg_settings.* string ALL resources.postgres_endpoints.*.status *postgres.EndpointStatus REMOTE resources.postgres_endpoints.*.status.autoscaling_limit_max_cu float64 REMOTE resources.postgres_endpoints.*.status.autoscaling_limit_min_cu float64 REMOTE resources.postgres_endpoints.*.status.current_state postgres.EndpointStatusState REMOTE resources.postgres_endpoints.*.status.disabled bool REMOTE +resources.postgres_endpoints.*.status.endpoint_id string REMOTE resources.postgres_endpoints.*.status.endpoint_type postgres.EndpointType REMOTE resources.postgres_endpoints.*.status.group *postgres.EndpointGroupStatus REMOTE resources.postgres_endpoints.*.status.group.enable_readable_secondaries bool REMOTE @@ -2695,27 +2878,28 @@ resources.postgres_endpoints.*.status.settings *postgres.EndpointSettings REMOTE resources.postgres_endpoints.*.status.settings.pg_settings map[string]string REMOTE resources.postgres_endpoints.*.status.settings.pg_settings.* string REMOTE resources.postgres_endpoints.*.status.suspend_timeout_duration *duration.Duration REMOTE -resources.postgres_endpoints.*.suspend_timeout_duration *duration.Duration INPUT STATE +resources.postgres_endpoints.*.suspend_timeout_duration *duration.Duration ALL resources.postgres_endpoints.*.uid string REMOTE resources.postgres_endpoints.*.update_time *time.Time REMOTE resources.postgres_endpoints.*.url string INPUT -resources.postgres_projects.*.budget_policy_id string INPUT STATE +resources.postgres_projects.*.budget_policy_id string ALL resources.postgres_projects.*.create_time *time.Time REMOTE -resources.postgres_projects.*.custom_tags []postgres.ProjectCustomTag INPUT STATE -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 -resources.postgres_projects.*.default_endpoint_settings.no_suspension bool INPUT STATE -resources.postgres_projects.*.default_endpoint_settings.pg_settings map[string]string INPUT STATE -resources.postgres_projects.*.default_endpoint_settings.pg_settings.* string INPUT STATE -resources.postgres_projects.*.default_endpoint_settings.suspend_timeout_duration *duration.Duration INPUT STATE -resources.postgres_projects.*.display_name string INPUT STATE -resources.postgres_projects.*.enable_pg_native_login bool INPUT STATE -resources.postgres_projects.*.history_retention_duration *duration.Duration INPUT STATE +resources.postgres_projects.*.custom_tags []postgres.ProjectCustomTag ALL +resources.postgres_projects.*.custom_tags[*] postgres.ProjectCustomTag ALL +resources.postgres_projects.*.custom_tags[*].key string ALL +resources.postgres_projects.*.custom_tags[*].value string ALL +resources.postgres_projects.*.default_branch string ALL +resources.postgres_projects.*.default_endpoint_settings *postgres.ProjectDefaultEndpointSettings ALL +resources.postgres_projects.*.default_endpoint_settings.autoscaling_limit_max_cu float64 ALL +resources.postgres_projects.*.default_endpoint_settings.autoscaling_limit_min_cu float64 ALL +resources.postgres_projects.*.default_endpoint_settings.no_suspension bool ALL +resources.postgres_projects.*.default_endpoint_settings.pg_settings map[string]string ALL +resources.postgres_projects.*.default_endpoint_settings.pg_settings.* string ALL +resources.postgres_projects.*.default_endpoint_settings.suspend_timeout_duration *duration.Duration ALL +resources.postgres_projects.*.delete_time *time.Time REMOTE +resources.postgres_projects.*.display_name string ALL +resources.postgres_projects.*.enable_pg_native_login bool ALL +resources.postgres_projects.*.history_retention_duration *duration.Duration ALL resources.postgres_projects.*.id string INPUT resources.postgres_projects.*.initial_endpoint_spec *postgres.InitialEndpointSpec REMOTE resources.postgres_projects.*.initial_endpoint_spec.group *postgres.EndpointGroupSpec REMOTE @@ -2726,26 +2910,9 @@ resources.postgres_projects.*.lifecycle resources.Lifecycle INPUT resources.postgres_projects.*.lifecycle.prevent_destroy bool INPUT resources.postgres_projects.*.modified_status string INPUT resources.postgres_projects.*.name string REMOTE -resources.postgres_projects.*.pg_version int INPUT STATE -resources.postgres_projects.*.project_id string INPUT STATE -resources.postgres_projects.*.spec *postgres.ProjectSpec REMOTE -resources.postgres_projects.*.spec.budget_policy_id string REMOTE -resources.postgres_projects.*.spec.custom_tags []postgres.ProjectCustomTag REMOTE -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 -resources.postgres_projects.*.spec.default_endpoint_settings.no_suspension bool REMOTE -resources.postgres_projects.*.spec.default_endpoint_settings.pg_settings map[string]string REMOTE -resources.postgres_projects.*.spec.default_endpoint_settings.pg_settings.* string REMOTE -resources.postgres_projects.*.spec.default_endpoint_settings.suspend_timeout_duration *duration.Duration REMOTE -resources.postgres_projects.*.spec.display_name string REMOTE -resources.postgres_projects.*.spec.enable_pg_native_login bool REMOTE -resources.postgres_projects.*.spec.history_retention_duration *duration.Duration REMOTE -resources.postgres_projects.*.spec.pg_version int REMOTE +resources.postgres_projects.*.pg_version int ALL +resources.postgres_projects.*.project_id string ALL +resources.postgres_projects.*.purge_time *time.Time REMOTE resources.postgres_projects.*.status *postgres.ProjectStatus REMOTE resources.postgres_projects.*.status.branch_logical_size_limit_bytes int64 REMOTE resources.postgres_projects.*.status.budget_policy_id string REMOTE @@ -2766,6 +2933,7 @@ resources.postgres_projects.*.status.enable_pg_native_login bool REMOTE resources.postgres_projects.*.status.history_retention_duration *duration.Duration REMOTE resources.postgres_projects.*.status.owner string REMOTE resources.postgres_projects.*.status.pg_version int REMOTE +resources.postgres_projects.*.status.project_id string REMOTE resources.postgres_projects.*.status.synthetic_storage_size_bytes int64 REMOTE resources.postgres_projects.*.uid string REMOTE resources.postgres_projects.*.update_time *time.Time REMOTE @@ -2776,6 +2944,49 @@ resources.postgres_projects.*.permissions[*].group_name string ALL resources.postgres_projects.*.permissions[*].level iam.PermissionLevel ALL resources.postgres_projects.*.permissions[*].service_principal_name string ALL resources.postgres_projects.*.permissions[*].user_name string ALL +resources.postgres_synced_tables.*.branch string ALL +resources.postgres_synced_tables.*.create_database_objects_if_missing bool ALL +resources.postgres_synced_tables.*.create_time *time.Time REMOTE +resources.postgres_synced_tables.*.existing_pipeline_id string ALL +resources.postgres_synced_tables.*.id string INPUT +resources.postgres_synced_tables.*.lifecycle resources.Lifecycle INPUT +resources.postgres_synced_tables.*.lifecycle.prevent_destroy bool INPUT +resources.postgres_synced_tables.*.modified_status string INPUT +resources.postgres_synced_tables.*.name string REMOTE +resources.postgres_synced_tables.*.new_pipeline_spec *postgres.NewPipelineSpec ALL +resources.postgres_synced_tables.*.new_pipeline_spec.budget_policy_id string ALL +resources.postgres_synced_tables.*.new_pipeline_spec.storage_catalog string ALL +resources.postgres_synced_tables.*.new_pipeline_spec.storage_schema string ALL +resources.postgres_synced_tables.*.postgres_database string ALL +resources.postgres_synced_tables.*.primary_key_columns []string ALL +resources.postgres_synced_tables.*.primary_key_columns[*] string ALL +resources.postgres_synced_tables.*.scheduling_policy postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy ALL +resources.postgres_synced_tables.*.source_table_full_name string ALL +resources.postgres_synced_tables.*.status *postgres.SyncedTableSyncedTableStatus REMOTE +resources.postgres_synced_tables.*.status.detailed_state postgres.SyncedTableState REMOTE +resources.postgres_synced_tables.*.status.last_processed_commit_version int64 REMOTE +resources.postgres_synced_tables.*.status.last_sync *postgres.SyncedTablePosition REMOTE +resources.postgres_synced_tables.*.status.last_sync.delta_table_sync_info *postgres.DeltaTableSyncInfo REMOTE +resources.postgres_synced_tables.*.status.last_sync.delta_table_sync_info.delta_commit_time *time.Time REMOTE +resources.postgres_synced_tables.*.status.last_sync.delta_table_sync_info.delta_commit_version int64 REMOTE +resources.postgres_synced_tables.*.status.last_sync.sync_end_time *time.Time REMOTE +resources.postgres_synced_tables.*.status.last_sync.sync_start_time *time.Time REMOTE +resources.postgres_synced_tables.*.status.last_sync_time *time.Time REMOTE +resources.postgres_synced_tables.*.status.message string REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress *postgres.SyncedTablePipelineProgress REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress.estimated_completion_time_seconds float64 REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress.latest_version_currently_processing int64 REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress.sync_progress_completion float64 REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress.synced_row_count int64 REMOTE +resources.postgres_synced_tables.*.status.ongoing_sync_progress.total_row_count int64 REMOTE +resources.postgres_synced_tables.*.status.pipeline_id string REMOTE +resources.postgres_synced_tables.*.status.project string REMOTE +resources.postgres_synced_tables.*.status.provisioning_phase postgres.ProvisioningPhase REMOTE +resources.postgres_synced_tables.*.status.unity_catalog_provisioning_state postgres.ProvisioningInfoState REMOTE +resources.postgres_synced_tables.*.synced_table_id string ALL +resources.postgres_synced_tables.*.timeseries_key string ALL +resources.postgres_synced_tables.*.uid string REMOTE +resources.postgres_synced_tables.*.url string INPUT resources.quality_monitors.*.assets_dir string ALL resources.quality_monitors.*.baseline_table_name string ALL resources.quality_monitors.*.custom_metrics []catalog.MonitorMetric ALL @@ -3042,19 +3253,19 @@ resources.vector_search_endpoints.*.endpoint_status *vectorsearch.EndpointStatus 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 STATE +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.requested_target_qps int64 REMOTE resources.vector_search_endpoints.*.scaling_info.state vectorsearch.ScalingChangeState REMOTE +resources.vector_search_endpoints.*.target_qps int64 INPUT STATE 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 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/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/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/resources/apps/create_already_exists/output.txt b/acceptance/bundle/resources/apps/create_already_exists/output.txt index bac47c04f92..82deb4ab43a 100644 --- a/acceptance/bundle/resources/apps/create_already_exists/output.txt +++ b/acceptance/bundle/resources/apps/create_already_exists/output.txt @@ -2,29 +2,29 @@ >>> [CLI] apps create test-app-already-exists { "active_deployment": { - "deployment_id":"deploy-[NUMID]", - "source_code_path":"/Workspace/Users/[USERNAME]/test-app-already-exists", + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-app-already-exists", "status": { - "message":"Deployment succeeded", - "state":"SUCCEEDED" + "message": "Deployment succeeded", + "state": "SUCCEEDED" } }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, - "compute_size":"MEDIUM", + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "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" + "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 @@ -37,9 +37,8 @@ HTTP Status: 409 Conflict API error_code: RESOURCE_ALREADY_EXISTS API message: An app with the same name already exists: test-app-already-exists -Updating deployment state... >>> [CLI] apps delete test-app-already-exists { - "name":"" + "name": "" } 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/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/dashboards/detect-change/output.txt b/acceptance/bundle/resources/dashboards/detect-change/output.txt index 53179be5169..8e06bc10e80 100644 --- a/acceptance/bundle/resources/dashboards/detect-change/output.txt +++ b/acceptance/bundle/resources/dashboards/detect-change/output.txt @@ -60,7 +60,8 @@ These modifications are untracked and will be overwritten on deploy. Make sure that the local dashboard definition matches what you intend to deploy before proceeding with the deployment. -Run `databricks bundle deploy --force` to bypass this error. +To overwrite the remote changes with your local version, use --force. +The remote modifications will be lost. update dashboards.file_reference @@ -77,7 +78,8 @@ These modifications are untracked and will be overwritten on deploy. Make sure that the local dashboard definition matches what you intend to deploy before proceeding with the deployment. -Run `databricks bundle deploy --force` to bypass this error. +To overwrite the remote changes with your local version, use --force. +The remote modifications will be lost. >>> errcode [CLI] bundle deploy @@ -91,7 +93,8 @@ These modifications are untracked and will be overwritten on deploy. Make sure that the local dashboard definition matches what you intend to deploy before proceeding with the deployment. -Run `databricks bundle deploy --force` to bypass this error. +To overwrite the remote changes with your local version, use --force. +The remote modifications will be lost. Exit code: 1 diff --git a/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.deploy.direct.txt b/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.deploy.direct.txt index 84918b848bf..705bd09cb32 100644 --- a/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.deploy.direct.txt +++ b/acceptance/bundle/resources/dashboards/publish-failure-cleans-up-dashboard/out.deploy.direct.txt @@ -9,6 +9,5 @@ HTTP Status: 400 Bad Request API error_code: RESOURCE_DOES_NOT_EXIST API message: Warehouse doesnotexist does not exist -Updating deployment state... Exit code: 1 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/jobs/create-error/output.txt b/acceptance/bundle/resources/jobs/create-error/output.txt index 0fcd944efd2..4211f239d91 100644 --- a/acceptance/bundle/resources/jobs/create-error/output.txt +++ b/acceptance/bundle/resources/jobs/create-error/output.txt @@ -9,4 +9,3 @@ HTTP Status: 400 Bad Request API error_code: INVALID_PARAMETER_VALUE API message: Shared job cluster feature is only supported in multi-task jobs. -Updating deployment state... diff --git a/acceptance/bundle/resources/jobs/no-git-provider/out.deploy.direct.txt b/acceptance/bundle/resources/jobs/no-git-provider/out.deploy.direct.txt index 41b7e6e0f61..eab8c861805 100644 --- a/acceptance/bundle/resources/jobs/no-git-provider/out.deploy.direct.txt +++ b/acceptance/bundle/resources/jobs/no-git-provider/out.deploy.direct.txt @@ -13,6 +13,5 @@ HTTP Status: 400 Bad Request API error_code: INVALID_PARAMETER_VALUE API message: git_source.git_provider must be one of: github,gitlab,bitbucketcloud,gitlabenterpriseedition,bitbucketserver,azuredevopsservices,githubenterprise,awscodecommit -Updating deployment state... Exit code: 1 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 e5a4c7283a6..5ad0addb75e 100644 --- a/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml +++ b/acceptance/bundle/resources/jobs/no-git-provider/out.test.toml @@ -1,4 +1,4 @@ -Local = false +Local = true Cloud = true RunsOnDbr = true EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/no-git-provider/test.toml b/acceptance/bundle/resources/jobs/no-git-provider/test.toml index ca29b496685..2dbb113fb51 100644 --- a/acceptance/bundle/resources/jobs/no-git-provider/test.toml +++ b/acceptance/bundle/resources/jobs/no-git-provider/test.toml @@ -1,4 +1,4 @@ -Local = false +Local = true Cloud = true RecordRequests = false diff --git a/acceptance/bundle/resources/jobs/update/out.plan_update.direct.json b/acceptance/bundle/resources/jobs/update/out.plan_update.direct.json index bdb8e9f5e99..7bf628435bb 100644 --- a/acceptance/bundle/resources/jobs/update/out.plan_update.direct.json +++ b/acceptance/bundle/resources/jobs/update/out.plan_update.direct.json @@ -2,7 +2,7 @@ "plan_version": 2, "cli_version": "[DEV_VERSION]", "lineage": "[UUID]", - "serial": 2, + "serial": 1, "plan": { "resources.jobs.foo": { "action": "update", diff --git a/acceptance/bundle/resources/jobs/update/out.state.terraform.json b/acceptance/bundle/resources/jobs/update/out.state.terraform.json index 2c50ca82cbb..520c7641bcd 100644 --- a/acceptance/bundle/resources/jobs/update/out.state.terraform.json +++ b/acceptance/bundle/resources/jobs/update/out.state.terraform.json @@ -102,7 +102,11 @@ "parameter": [], "performance_target": null, "pipeline_task": [], - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "python_wheel_task": [], "queue": [ { 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/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/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/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/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/pipelines/auto-approve/output.txt b/acceptance/bundle/resources/pipelines/auto-approve/output.txt index 5154a1206a3..3016a4c248d 100644 --- a/acceptance/bundle/resources/pipelines/auto-approve/output.txt +++ b/acceptance/bundle/resources/pipelines/auto-approve/output.txt @@ -50,7 +50,9 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: delete resources.pipelines.bar -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. Exit code: 1 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/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/output.txt b/acceptance/bundle/resources/pipelines/recreate/output.txt index 8550395ff2a..bc68df40d25 100644 --- a/acceptance/bundle/resources/pipelines/recreate/output.txt +++ b/acceptance/bundle/resources/pipelines/recreate/output.txt @@ -49,7 +49,9 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: recreate resources.pipelines.foo -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. Exit code: 1 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/output.txt b/acceptance/bundle/resources/postgres_branches/basic/output.txt index 9da42b6da2a..5cd8cb990b5 100644 --- a/acceptance/bundle/resources/postgres_branches/basic/output.txt +++ b/acceptance/bundle/resources/postgres_branches/basic/output.txt @@ -35,6 +35,7 @@ Deployment complete! "name": "projects/test-pg-proj-[UNIQUE_NAME]/branches/main", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "main", "current_state": "READY", "default": false, "is_protected": false, diff --git a/acceptance/bundle/resources/postgres_branches/basic/test.toml b/acceptance/bundle/resources/postgres_branches/basic/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_branches/basic/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_branches/recreate/out.get_branch.txt b/acceptance/bundle/resources/postgres_branches/recreate/out.get_branch.txt index 46df9881704..5a361bd3421 100644 --- a/acceptance/bundle/resources/postgres_branches/recreate/out.get_branch.txt +++ b/acceptance/bundle/resources/postgres_branches/recreate/out.get_branch.txt @@ -2,6 +2,7 @@ "name": "[MAIN_ID_2]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "new-branch-[UNIQUE_NAME]", "current_state": "READY", "default": false, "is_protected": false, diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_branches/replace_existing/databricks.yml.tmpl new file mode 100644 index 00000000000..d55c43cb741 --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/databricks.yml.tmpl @@ -0,0 +1,24 @@ +bundle: + name: deploy-postgres-replace-branch-$UNIQUE_NAME + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Replace existing branch test" + pg_version: 16 + history_retention_duration: "604800s" + + # Take over the implicitly-created production branch with replace_existing + # and apply a non-default spec (no_expiry: true). The field is input-only + # and not surfaced in get-branch, but it is visible in the recorded + # request body so the diff confirms it was sent. + postgres_branches: + production: + parent: ${resources.postgres_projects.my_project.id} + branch_id: production + replace_existing: true + no_expiry: true diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.direct.txt b/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.direct.txt new file mode 100644 index 00000000000..03109f9364d --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.direct.txt @@ -0,0 +1,16 @@ +The following resources will be deleted: + delete resources.postgres_branches.production + 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.production + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-branch-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.terraform.txt b/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.terraform.txt new file mode 100644 index 00000000000..03109f9364d --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.destroy.terraform.txt @@ -0,0 +1,16 @@ +The following resources will be deleted: + delete resources.postgres_branches.production + 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.production + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-branch-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.direct.json b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.direct.json new file mode 100644 index 00000000000..781311e084c --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.direct.json @@ -0,0 +1,31 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Replace existing branch test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "production", + "replace_existing": "true" + }, + "body": { + "spec": { + "no_expiry": true + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.terraform.json b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.terraform.json new file mode 100644 index 00000000000..f70714acb2b --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.deploy.terraform.json @@ -0,0 +1,32 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Replace existing branch test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "production", + "replace_existing": "true" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]", + "spec": { + "no_expiry": true + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.direct.json b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.direct.json new file mode 100644 index 00000000000..1ddf631fc65 --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.direct.json @@ -0,0 +1,47 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Replace existing branch test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "production", + "replace_existing": "true" + }, + "body": { + "spec": { + "no_expiry": true + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.terraform.json b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.terraform.json new file mode 100644 index 00000000000..ad6e8580927 --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.requests.destroy.terraform.json @@ -0,0 +1,48 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Replace existing branch test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "production", + "replace_existing": "true" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]", + "spec": { + "no_expiry": true + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/production" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/out.test.toml b/acceptance/bundle/resources/postgres_branches/replace_existing/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/output.txt b/acceptance/bundle/resources/postgres_branches/replace_existing/output.txt new file mode 100644 index 00000000000..3f1092385aa --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/output.txt @@ -0,0 +1,34 @@ + +>>> [CLI] bundle validate +Name: deploy-postgres-replace-branch-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-branch-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-branch-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] postgres get-branch projects/test-pg-proj-[UNIQUE_NAME]/branches/production +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", + "parent": "projects/test-pg-proj-[UNIQUE_NAME]", + "status": { + "branch_id": "production", + "current_state": "READY", + "default": true, + "is_protected": false, + "state_change_time": "[TIMESTAMP]" + }, + "uid": "[BRANCH_UID]" +} + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +=== bundle destroy +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ diff --git a/acceptance/bundle/resources/postgres_branches/replace_existing/script b/acceptance/bundle/resources/postgres_branches/replace_existing/script new file mode 100644 index 00000000000..3c3cc7e9300 --- /dev/null +++ b/acceptance/bundle/resources/postgres_branches/replace_existing/script @@ -0,0 +1,28 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + # Belt-and-braces in case bundle destroy was skipped or partially failed. + $CLI postgres delete-project "projects/test-pg-proj-${UNIQUE_NAME}" 2>/dev/null || true + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle validate + +rm -f out.requests.txt +trace $CLI bundle deploy + +# Confirm the implicit production branch is now under management. +trace $CLI postgres get-branch "projects/test-pg-proj-${UNIQUE_NAME}/branches/production" | jq 'del(.create_time, .update_time, .status.logical_size_bytes)' + +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.deploy.$DATABRICKS_BUNDLE_ENGINE.json + +# bundle destroy: the backend rejects independent deletion of the root +# branch with a MANAGED_BY_PARENT marker; both engines disregard that +# error and let the project delete cascade-clean the branch. Per-engine +# output is captured so regressions in either engine's destroy flow show +# up in the diff. +title "bundle destroy" +$CLI bundle destroy --auto-approve > out.destroy.$DATABRICKS_BUNDLE_ENGINE.txt 2>&1 || true + +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.destroy.$DATABRICKS_BUNDLE_ENGINE.json diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.no_change.direct.json b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.no_change.direct.json index 32120ec36ed..ff7df005d4e 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.no_change.direct.json +++ b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.no_change.direct.json @@ -7,10 +7,12 @@ ], "action": "skip", "remote_state": { + "branch_id": "dev-branch", "create_time": "[TIMESTAMP]", "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": false, diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.restore.direct.json b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.restore.direct.json index 110fb1aea0f..64acdc88a68 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.restore.direct.json +++ b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.restore.direct.json @@ -15,10 +15,12 @@ } }, "remote_state": { + "branch_id": "dev-branch", "create_time": "[TIMESTAMP]", "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": true, diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.update.direct.json b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.update.direct.json index 58169e75a32..6cc645dede6 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.update.direct.json +++ b/acceptance/bundle/resources/postgres_branches/update_protected/out.plan.update.direct.json @@ -15,10 +15,12 @@ } }, "remote_state": { + "branch_id": "dev-branch", "create_time": "[TIMESTAMP]", "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": false, diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/output.txt b/acceptance/bundle/resources/postgres_branches/update_protected/output.txt index 7380da0adce..f9984dd8d20 100644 --- a/acceptance/bundle/resources/postgres_branches/update_protected/output.txt +++ b/acceptance/bundle/resources/postgres_branches/update_protected/output.txt @@ -30,6 +30,7 @@ Deployment complete! "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": false, @@ -104,6 +105,7 @@ Deployment complete! "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": true, @@ -138,6 +140,7 @@ Deployment complete! "name": "[DEV_BRANCH_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]", "status": { + "branch_id": "dev-branch", "current_state": "READY", "default": false, "is_protected": false, diff --git a/acceptance/bundle/resources/postgres_branches/update_protected/test.toml b/acceptance/bundle/resources/postgres_branches/update_protected/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_branches/update_protected/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_branches/without_branch_id/test.toml b/acceptance/bundle/resources/postgres_branches/without_branch_id/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_branches/without_branch_id/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_catalogs/basic/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_catalogs/basic/databricks.yml.tmpl new file mode 100644 index 00000000000..b339f4c1354 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/basic/databricks.yml.tmpl @@ -0,0 +1,19 @@ +bundle: + name: deploy-postgres-catalog-$UNIQUE_NAME + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Test Project for Catalog" + pg_version: 16 + + postgres_catalogs: + my_catalog: + catalog_id: lakebase_test_$UNIQUE_NAME + branch: ${resources.postgres_projects.my_project.id}/branches/production + postgres_database: appdb + create_database_if_missing: true diff --git a/acceptance/bundle/resources/postgres_catalogs/basic/out.requests.json b/acceptance/bundle/resources/postgres_catalogs/basic/out.requests.json new file mode 100644 index 00000000000..891733f4150 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/basic/out.requests.json @@ -0,0 +1,31 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Test Project for Catalog", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/catalogs", + "q": { + "catalog_id": "lakebase_test_[UNIQUE_NAME]" + }, + "body": { + "spec": { + "branch": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", + "create_database_if_missing": true, + "postgres_database": "appdb" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/catalogs/lakebase_test_[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_catalogs/basic/out.test.toml b/acceptance/bundle/resources/postgres_catalogs/basic/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/basic/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_catalogs/basic/output.txt b/acceptance/bundle/resources/postgres_catalogs/basic/output.txt new file mode 100644 index 00000000000..7bf45891cb3 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/basic/output.txt @@ -0,0 +1,74 @@ + +>>> [CLI] bundle validate +Name: deploy-postgres-catalog-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-catalog-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle summary +Name: deploy-postgres-catalog-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-catalog-[UNIQUE_NAME]/default +Resources: + Postgres catalogs: + my_catalog: + Name: lakebase_test_[UNIQUE_NAME] + URL: [DATABRICKS_URL]/explore/data/lakebase_test_[UNIQUE_NAME] + Postgres projects: + my_project: + Name: Test Project for Catalog + URL: (not deployed) + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-catalog-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] postgres get-catalog catalogs/lakebase_test_[UNIQUE_NAME] +{ + "name": "catalogs/lakebase_test_[UNIQUE_NAME]", + "status": { + "branch": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", + "catalog_id": "lakebase_test_[UNIQUE_NAME]", + "postgres_database": "appdb", + "project": "projects/test-pg-proj-[UNIQUE_NAME]" + } +} + +>>> [CLI] bundle summary +Name: deploy-postgres-catalog-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-catalog-[UNIQUE_NAME]/default +Resources: + Postgres catalogs: + my_catalog: + Name: lakebase_test_[UNIQUE_NAME] + URL: [DATABRICKS_URL]/explore/data/lakebase_test_[UNIQUE_NAME] + Postgres projects: + my_project: + Name: Test Project for Catalog + URL: (not deployed) + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.postgres_catalogs.my_catalog + 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-catalog-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_catalogs/basic/script b/acceptance/bundle/resources/postgres_catalogs/basic/script new file mode 100644 index 00000000000..ba6240838c8 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/basic/script @@ -0,0 +1,23 @@ +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 catalog details. Hide volatile fields so cloud and local match. +catalog_name="catalogs/lakebase_test_${UNIQUE_NAME}" +trace $CLI postgres get-catalog "${catalog_name}" | jq 'del(.create_time, .update_time, .uid)' + +trace $CLI bundle summary + +# Filter requests to only show postgres operations (exclude workspace, telemetry, and operation polling). +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.json diff --git a/acceptance/bundle/resources/postgres_catalogs/recreate/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_catalogs/recreate/databricks.yml.tmpl new file mode 100644 index 00000000000..bf3c2b56576 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/recreate/databricks.yml.tmpl @@ -0,0 +1,19 @@ +bundle: + name: recreate-postgres-catalog-$UNIQUE_NAME + +sync: + paths: [] + +resources: + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Test Project for Catalog Recreate" + pg_version: 16 + + postgres_catalogs: + my_catalog: + catalog_id: lakebase_test_$UNIQUE_NAME + branch: ${resources.postgres_projects.my_project.id}/branches/production + postgres_database: $POSTGRES_DATABASE + create_database_if_missing: true diff --git a/acceptance/bundle/resources/postgres_catalogs/recreate/out.test.toml b/acceptance/bundle/resources/postgres_catalogs/recreate/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/recreate/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_catalogs/recreate/output.txt b/acceptance/bundle/resources/postgres_catalogs/recreate/output.txt new file mode 100644 index 00000000000..1f6bb621987 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/recreate/output.txt @@ -0,0 +1,31 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-catalog-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle plan +recreate postgres_catalogs.my_catalog + +Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-catalog-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.postgres_catalogs.my_catalog + 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/recreate-postgres-catalog-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_catalogs/recreate/script b/acceptance/bundle/resources/postgres_catalogs/recreate/script new file mode 100644 index 00000000000..4d9de784299 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/recreate/script @@ -0,0 +1,16 @@ +cleanup() { + trace $CLI bundle destroy --auto-approve + rm -f out.requests.txt +} +trap cleanup EXIT + +export POSTGRES_DATABASE=appdb +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle deploy + +# Toggle a recreate-on-change field; plan must show delete + create. +export POSTGRES_DATABASE=otherdb +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle plan | contains.py "Plan: 1 to add, 0 to change, 1 to delete, 1 unchanged" + +trace $CLI bundle deploy diff --git a/acceptance/bundle/resources/postgres_catalogs/test.toml b/acceptance/bundle/resources/postgres_catalogs/test.toml new file mode 100644 index 00000000000..66f0811b343 --- /dev/null +++ b/acceptance/bundle/resources/postgres_catalogs/test.toml @@ -0,0 +1,26 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +# Lakebase v2 (postgres) is only available in AWS as of January 2026 +CloudEnvs.gcp = false +CloudEnvs.azure = false + +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] + +Ignore = [ + "databricks.yml", + ".databricks", +] + +[[Repls]] +# Clean up ?o= suffix after URL since not all workspaces have that +Old = '\?o=\[(NUMID|ALPHANUMID)\]' +New = '' +Order = 1000 + +[[Repls]] +# Normalize postgres operation IDs (unique per operation). +Old = '/operations/[A-Za-z0-9+/=-]+' +New = '/operations/[OPERATION_ID]' +Order = 2000 diff --git a/acceptance/bundle/resources/postgres_endpoints/basic/output.txt b/acceptance/bundle/resources/postgres_endpoints/basic/output.txt index 898cb54c465..1c0c49f2475 100644 --- a/acceptance/bundle/resources/postgres_endpoints/basic/output.txt +++ b/acceptance/bundle/resources/postgres_endpoints/basic/output.txt @@ -43,6 +43,7 @@ Deployment complete! "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "custom", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, diff --git a/acceptance/bundle/resources/postgres_endpoints/basic/test.toml b/acceptance/bundle/resources/postgres_endpoints/basic/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_endpoints/basic/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_endpoints/replace_existing/databricks.yml.tmpl new file mode 100644 index 00000000000..220d7b0077e --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/databricks.yml.tmpl @@ -0,0 +1,43 @@ +bundle: + name: deploy-postgres-replace-endpoint-$UNIQUE_NAME + +sync: + paths: [] + +resources: + # The project's default_endpoint_settings are inherited by every implicit + # primary endpoint. We set suspend_timeout_duration to 300s here so the + # primary endpoint of the "develop" branch below would otherwise come up + # with 300s. + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Replace existing endpoint test" + pg_version: 16 + history_retention_duration: "604800s" + default_endpoint_settings: + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + suspend_timeout_duration: "300s" + + # Non-default branch managed normally — the server auto-provisions its + # primary read-write endpoint with the project defaults above. + postgres_branches: + develop: + parent: ${resources.postgres_projects.my_project.id} + branch_id: develop + no_expiry: true + + # Take over the implicitly-created primary endpoint of the develop branch + # and override the inherited suspend_timeout_duration (300s) with 600s. A + # successful deploy with 600s reflected in get-endpoint proves + # replace_existing applied the spec. + postgres_endpoints: + primary: + parent: ${resources.postgres_branches.develop.id} + endpoint_id: primary + replace_existing: true + endpoint_type: ENDPOINT_TYPE_READ_WRITE + autoscaling_limit_min_cu: 0.5 + autoscaling_limit_max_cu: 4 + suspend_timeout_duration: "600s" diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.direct.txt b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.direct.txt new file mode 100644 index 00000000000..2ac6d613c05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.direct.txt @@ -0,0 +1,17 @@ +The following resources will be deleted: + delete resources.postgres_branches.develop + delete resources.postgres_endpoints.primary + 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.develop + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-endpoint-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.terraform.txt b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.terraform.txt new file mode 100644 index 00000000000..2ac6d613c05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.destroy.terraform.txt @@ -0,0 +1,17 @@ +The following resources will be deleted: + delete resources.postgres_branches.develop + delete resources.postgres_endpoints.primary + 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.develop + +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-endpoint-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.direct.json b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.direct.json new file mode 100644 index 00000000000..28e0d2433a9 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.direct.json @@ -0,0 +1,55 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "Replace existing endpoint test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "develop" + }, + "body": { + "spec": { + "no_expiry": true + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints", + "q": { + "endpoint_id": "primary", + "replace_existing": "true" + }, + "body": { + "spec": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "endpoint_type": "ENDPOINT_TYPE_READ_WRITE", + "suspend_timeout_duration": "600s" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.terraform.json b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.terraform.json new file mode 100644 index 00000000000..6ab68fc5dd9 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.deploy.terraform.json @@ -0,0 +1,53 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "Replace existing endpoint test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "develop" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]", + "spec": { + "no_expiry": true + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints", + "q": { + "endpoint_id": "primary", + "replace_existing": "true" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/develop", + "spec": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "endpoint_type": "ENDPOINT_TYPE_READ_WRITE", + "suspend_timeout_duration": "600s" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.direct.json b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.direct.json new file mode 100644 index 00000000000..51e48eb6b96 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.direct.json @@ -0,0 +1,79 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "Replace existing endpoint test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "develop" + }, + "body": { + "spec": { + "no_expiry": true + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints", + "q": { + "endpoint_id": "primary", + "replace_existing": "true" + }, + "body": { + "spec": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "endpoint_type": "ENDPOINT_TYPE_READ_WRITE", + "suspend_timeout_duration": "600s" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.terraform.json b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.terraform.json new file mode 100644 index 00000000000..1bebfa32251 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.requests.destroy.terraform.json @@ -0,0 +1,77 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "default_endpoint_settings": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "suspend_timeout_duration": "300s" + }, + "display_name": "Replace existing endpoint test", + "history_retention_duration": "604800s", + "pg_version": 16 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches", + "q": { + "branch_id": "develop" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]", + "spec": { + "no_expiry": true + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints", + "q": { + "endpoint_id": "primary", + "replace_existing": "true" + }, + "body": { + "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/develop", + "spec": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "endpoint_type": "ENDPOINT_TYPE_READ_WRITE", + "suspend_timeout_duration": "600s" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop" +} +{ + "method": "GET", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]/branches/develop" +} +{ + "method": "DELETE", + "path": "/api/2.0/postgres/projects/test-pg-proj-[UNIQUE_NAME]" +} diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.test.toml b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/output.txt b/acceptance/bundle/resources/postgres_endpoints/replace_existing/output.txt new file mode 100644 index 00000000000..c1823ef73c0 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/output.txt @@ -0,0 +1,45 @@ + +>>> [CLI] bundle validate +Name: deploy-postgres-replace-endpoint-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-endpoint-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-replace-endpoint-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] postgres get-endpoint projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary +{ + "name": "projects/test-pg-proj-[UNIQUE_NAME]/branches/develop/endpoints/primary", + "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/develop", + "status": { + "autoscaling_limit_max_cu": 4, + "autoscaling_limit_min_cu": 0.5, + "current_state": "ACTIVE", + "disabled": false, + "endpoint_id": "primary", + "endpoint_type": "ENDPOINT_TYPE_READ_WRITE", + "group": { + "enable_readable_secondaries": false, + "max": 1, + "min": 1 + }, + "hosts": { + "host": "[ENDPOINT_UID].database.us-east-1.cloud.databricks.com" + }, + "settings": {}, + "suspend_timeout_duration": "600s" + }, + "uid": "[ENDPOINT_UID]" +} + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +=== bundle destroy +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ diff --git a/acceptance/bundle/resources/postgres_endpoints/replace_existing/script b/acceptance/bundle/resources/postgres_endpoints/replace_existing/script new file mode 100644 index 00000000000..b88120d5f31 --- /dev/null +++ b/acceptance/bundle/resources/postgres_endpoints/replace_existing/script @@ -0,0 +1,30 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +cleanup() { + # Belt-and-braces in case bundle destroy was skipped or partially failed. + $CLI postgres delete-project "projects/test-pg-proj-${UNIQUE_NAME}" 2>/dev/null || true + rm -f out.requests.txt +} +trap cleanup EXIT + +trace $CLI bundle validate + +rm -f out.requests.txt +trace $CLI bundle deploy + +# The implicit primary endpoint inherits suspend_timeout_duration from the +# project default (300s). After deploy with replace_existing=true, the +# bundle's spec must be applied: 600s. +trace $CLI postgres get-endpoint "projects/test-pg-proj-${UNIQUE_NAME}/branches/develop/endpoints/primary" | jq 'del(.create_time, .update_time)' + +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.deploy.$DATABRICKS_BUNDLE_ENGINE.json + +# bundle destroy: the backend rejects independent deletion of the primary +# read-write endpoint with a MANAGED_BY_PARENT marker; both engines +# disregard that error and let the parent branch delete cascade-clean the +# endpoint. Per-engine output is captured so regressions in either +# engine's destroy flow show up in the diff. +title "bundle destroy" +$CLI bundle destroy --auto-approve > out.destroy.$DATABRICKS_BUNDLE_ENGINE.txt 2>&1 || true + +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.destroy.$DATABRICKS_BUNDLE_ENGINE.json diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.no_change.direct.json b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.no_change.direct.json index 98cc8f8b22c..09deef4c5cb 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.no_change.direct.json +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.no_change.direct.json @@ -8,6 +8,8 @@ "action": "skip", "remote_state": { "create_time": "[TIMESTAMP]", + "endpoint_id": "my-endpoint", + "endpoint_type": "", "name": "[MY_ENDPOINT_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/main", "status": { @@ -15,6 +17,7 @@ "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.restore.direct.json b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.restore.direct.json index 86fbbaa7a6b..747dfb2b1cd 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.restore.direct.json +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.restore.direct.json @@ -18,6 +18,8 @@ }, "remote_state": { "create_time": "[TIMESTAMP]", + "endpoint_id": "my-endpoint", + "endpoint_type": "", "name": "[MY_ENDPOINT_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/main", "status": { @@ -25,6 +27,7 @@ "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.update.direct.json b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.update.direct.json index 1ff9863d78f..1fc1d86a101 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.update.direct.json +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/out.plan.update.direct.json @@ -18,6 +18,8 @@ }, "remote_state": { "create_time": "[TIMESTAMP]", + "endpoint_id": "my-endpoint", + "endpoint_type": "", "name": "[MY_ENDPOINT_ID]", "parent": "projects/test-pg-proj-[UNIQUE_NAME]/branches/main", "status": { @@ -25,6 +27,7 @@ "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt index 9192423fa68..5d8512b0857 100644 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt +++ b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/output.txt @@ -35,6 +35,7 @@ Deployment complete! "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, @@ -122,6 +123,7 @@ Deployment complete! "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, @@ -165,6 +167,7 @@ Deployment complete! "autoscaling_limit_min_cu": 0.5, "current_state": "ACTIVE", "disabled": false, + "endpoint_id": "my-endpoint", "endpoint_type": "ENDPOINT_TYPE_READ_ONLY", "group": { "enable_readable_secondaries": true, diff --git a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/test.toml b/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_endpoints/update_autoscaling/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/test.toml b/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_endpoints/without_endpoint_id/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_projects/basic/output.txt b/acceptance/bundle/resources/postgres_projects/basic/output.txt index 3fb4b9ee8a9..6b4c966c277 100644 --- a/acceptance/bundle/resources/postgres_projects/basic/output.txt +++ b/acceptance/bundle/resources/postgres_projects/basic/output.txt @@ -31,16 +31,18 @@ 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, "suspend_timeout_duration": "300s" }, "display_name": "Test Postgres Project", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]" diff --git a/acceptance/bundle/resources/postgres_projects/basic/test.toml b/acceptance/bundle/resources/postgres_projects/basic/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_projects/basic/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml 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..8b469394b10 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,22 @@ { - "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": false, + "history_retention_duration": "604800s", + "owner": "[USERNAME]", + "pg_version": 16, + "project_id": "test-pg-new-[UNIQUE_NAME]", + "synthetic_storage_size_bytes": 0 }, - "uid":"[UUID]", - "update_time":"[TIMESTAMP]" + "uid": "[UUID]", + "update_time": "[TIMESTAMP]" } 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..02eada6bb5c 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 @@ -3,18 +3,21 @@ "remote_state": { "create_time": "[TIMESTAMP]", "name": "[MY_PROJECT_ID]", + "project_id": "test-pg-proj-[UNIQUE_NAME]", "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, "suspend_timeout_duration": "300s" }, "display_name": "Original Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]", 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..de9277d15f0 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 @@ -16,18 +16,21 @@ "remote_state": { "create_time": "[TIMESTAMP]", "name": "[MY_PROJECT_ID]", + "project_id": "test-pg-proj-[UNIQUE_NAME]", "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, "suspend_timeout_duration": "300s" }, "display_name": "Updated Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]", 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..d6307318bc8 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 @@ -16,18 +16,21 @@ "remote_state": { "create_time": "[TIMESTAMP]", "name": "[MY_PROJECT_ID]", + "project_id": "test-pg-proj-[UNIQUE_NAME]", "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, "suspend_timeout_duration": "300s" }, "display_name": "Original Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]", 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 aabcba8c34e..85a5668aa31 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": "spec" + "update_mask": "initial_endpoint_spec,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 d68d893ad28..daa3b4c7d93 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": "spec" + "update_mask": "initial_endpoint_spec,spec" }, "body": { "name": "[MY_PROJECT_ID]", 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 e709f77c8a9..7a5001785b0 100644 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt +++ b/acceptance/bundle/resources/postgres_projects/update_display_name/output.txt @@ -29,16 +29,18 @@ 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, "suspend_timeout_duration": "300s" }, "display_name": "Original Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]" @@ -100,16 +102,18 @@ 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, "suspend_timeout_duration": "300s" }, "display_name": "Updated Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]" @@ -138,16 +142,18 @@ 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, "suspend_timeout_duration": "300s" }, "display_name": "Original Name", - "enable_pg_native_login": true, + "enable_pg_native_login": false, "history_retention_duration": "604800s", "owner": "[USERNAME]", "pg_version": 16, + "project_id": "test-pg-proj-[UNIQUE_NAME]", "synthetic_storage_size_bytes": 0 }, "uid": "[UUID]" diff --git a/acceptance/bundle/resources/postgres_projects/update_display_name/test.toml b/acceptance/bundle/resources/postgres_projects/update_display_name/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/postgres_projects/update_display_name/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml diff --git a/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.direct.txt b/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.direct.txt index 79d1f7200e1..8103b944c46 100644 --- a/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.direct.txt +++ b/acceptance/bundle/resources/postgres_projects/without_project_id/out.deploy.direct.txt @@ -11,4 +11,3 @@ HTTP Status: 400 Bad Request API error_code: INVALID_PARAMETER_VALUE API message: Field 'project_id' is required, expected non-default value (not "")! -Updating deployment state... diff --git a/acceptance/bundle/resources/postgres_synced_tables/basic/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_synced_tables/basic/databricks.yml.tmpl new file mode 100644 index 00000000000..9d14a96d86e --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/basic/databricks.yml.tmpl @@ -0,0 +1,38 @@ +bundle: + name: deploy-postgres-synced-table-$UNIQUE_NAME + +sync: + paths: [] + +resources: + schemas: + pipeline_storage: + name: pipeline_storage_$UNIQUE_NAME + catalog_name: main + comment: "Pipeline storage for the synced-table test" + + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Test Project for Synced Table" + pg_version: 17 + + postgres_catalogs: + my_catalog: + catalog_id: lakebase_test_$UNIQUE_NAME + branch: ${resources.postgres_projects.my_project.id}/branches/production + postgres_database: appdb + create_database_if_missing: true + + postgres_synced_tables: + my_table: + synced_table_id: ${resources.postgres_catalogs.my_catalog.catalog_id}.public.trips_synced + source_table_full_name: main.source_$UNIQUE_NAME.trips_source + primary_key_columns: ["tpep_pickup_datetime"] + scheduling_policy: SNAPSHOT + postgres_database: appdb + branch: ${resources.postgres_projects.my_project.id}/branches/production + create_database_objects_if_missing: true + new_pipeline_spec: + storage_catalog: ${resources.schemas.pipeline_storage.catalog_name} + storage_schema: ${resources.schemas.pipeline_storage.name} diff --git a/acceptance/bundle/resources/postgres_synced_tables/basic/out.requests.json b/acceptance/bundle/resources/postgres_synced_tables/basic/out.requests.json new file mode 100644 index 00000000000..4e85c53cd99 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/basic/out.requests.json @@ -0,0 +1,54 @@ +{ + "method": "POST", + "path": "/api/2.0/postgres/projects", + "q": { + "project_id": "test-pg-proj-[UNIQUE_NAME]" + }, + "body": { + "spec": { + "display_name": "Test Project for Synced Table", + "pg_version": 17 + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/catalogs", + "q": { + "catalog_id": "lakebase_test_[UNIQUE_NAME]" + }, + "body": { + "spec": { + "branch": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", + "create_database_if_missing": true, + "postgres_database": "appdb" + } + } +} +{ + "method": "POST", + "path": "/api/2.0/postgres/synced_tables", + "q": { + "synced_table_id": "lakebase_test_[UNIQUE_NAME].public.trips_synced" + }, + "body": { + "spec": { + "branch": "projects/test-pg-proj-[UNIQUE_NAME]/branches/production", + "create_database_objects_if_missing": true, + "new_pipeline_spec": { + "storage_catalog": "main", + "storage_schema": "pipeline_storage_[UNIQUE_NAME]" + }, + "postgres_database": "appdb", + "primary_key_columns": [ + "tpep_pickup_datetime" + ], + "scheduling_policy": "SNAPSHOT", + "source_table_full_name": "main.source_[UNIQUE_NAME].trips_source" + } + } +} +{ + "method": "GET", + "path": "/api/2.0/postgres/synced_tables/lakebase_test_[UNIQUE_NAME].public.trips_synced" +} diff --git a/acceptance/bundle/resources/postgres_synced_tables/basic/out.test.toml b/acceptance/bundle/resources/postgres_synced_tables/basic/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/basic/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_synced_tables/basic/output.txt b/acceptance/bundle/resources/postgres_synced_tables/basic/output.txt new file mode 100644 index 00000000000..d355b3d15f4 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/basic/output.txt @@ -0,0 +1,95 @@ +Creating temporary source table: main.source_[UNIQUE_NAME].trips_source +{ + "full_name": "main.source_[UNIQUE_NAME]" +} + +>>> [CLI] bundle validate +Name: deploy-postgres-synced-table-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-synced-table-[UNIQUE_NAME]/default + +Validation OK! + +>>> [CLI] bundle summary +Name: deploy-postgres-synced-table-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-synced-table-[UNIQUE_NAME]/default +Resources: + Postgres catalogs: + my_catalog: + Name: lakebase_test_[UNIQUE_NAME] + URL: [DATABRICKS_URL]/explore/data/lakebase_test_[UNIQUE_NAME] + Postgres projects: + my_project: + Name: Test Project for Synced Table + URL: (not deployed) + Postgres synced tables: + my_table: + Name: ${resources.postgres_catalogs.my_catalog.catalog_id}.public.trips_synced + URL: [DATABRICKS_URL]/explore/data/$%7Bresources/postgres_catalogs/my_catalog.catalog_id%7D.public.trips_synced + Schemas: + pipeline_storage: + Name: pipeline_storage_[UNIQUE_NAME] + URL: (not deployed) + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-synced-table-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] postgres get-synced-table synced_tables/lakebase_test_[UNIQUE_NAME].public.trips_synced +{ + "name": "synced_tables/lakebase_test_[UNIQUE_NAME].public.trips_synced", + "unity_catalog_provisioning_state": "ACTIVE" +} + +>>> [CLI] bundle summary +Name: deploy-postgres-synced-table-[UNIQUE_NAME] +Target: default +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/deploy-postgres-synced-table-[UNIQUE_NAME]/default +Resources: + Postgres catalogs: + my_catalog: + Name: lakebase_test_[UNIQUE_NAME] + URL: [DATABRICKS_URL]/explore/data/lakebase_test_[UNIQUE_NAME] + Postgres projects: + my_project: + Name: Test Project for Synced Table + URL: (not deployed) + Postgres synced tables: + my_table: + Name: lakebase_test_[UNIQUE_NAME].public.trips_synced + URL: [DATABRICKS_URL]/explore/data/lakebase_test_[UNIQUE_NAME]/public/trips_synced + Schemas: + pipeline_storage: + Name: pipeline_storage_[UNIQUE_NAME] + URL: [DATABRICKS_URL]/explore/data/main/pipeline_storage_[UNIQUE_NAME] + +>>> print_requests.py --keep --get //postgres ^//workspace-files/ ^//workspace/ ^//telemetry-ext ^//operations/ + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.postgres_catalogs.my_catalog + delete resources.postgres_projects.my_project + delete resources.postgres_synced_tables.my_table + delete resources.schemas.pipeline_storage + +This action will result in the deletion of the following UC schemas. Any underlying data may be lost: + delete resources.schemas.pipeline_storage + +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-synced-table-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! +Cleaning up temporary source table diff --git a/acceptance/bundle/resources/postgres_synced_tables/basic/script b/acceptance/bundle/resources/postgres_synced_tables/basic/script new file mode 100644 index 00000000000..be21d010aea --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/basic/script @@ -0,0 +1,39 @@ +envsubst < databricks.yml.tmpl > databricks.yml + +# Create a per-test source schema + table. We can't read samples.nyctaxi.trips +# directly because shared workspaces hit the "20 synced tables per source table" +# limit (same reason synced_database_tables takes this approach). +echo "Creating temporary source table: main.source_$UNIQUE_NAME.trips_source" +$CLI schemas create source_$UNIQUE_NAME main -o json | jq '{full_name}' +MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/sql/statements/" --json "{ + \"warehouse_id\": \"$TEST_DEFAULT_WAREHOUSE_ID\", + \"statement\": \"CREATE TABLE main.source_$UNIQUE_NAME.trips_source AS SELECT * FROM samples.nyctaxi.trips LIMIT 10\", + \"wait_timeout\": \"45s\" + }" > /dev/null + +cleanup() { + trace $CLI bundle destroy --auto-approve + echo "Cleaning up temporary source table" + $CLI tables delete main.source_$UNIQUE_NAME.trips_source || true + $CLI schemas delete main.source_$UNIQUE_NAME || true + 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 + +# Keep only the deterministic identity + provisioning-state. detailed_state +# varies with pipeline-provisioning timing on cloud, ongoing_sync_progress and +# project only appear there, and create_time/update_time/uid/pipeline_id are +# random per run. +trace $CLI postgres get-synced-table "synced_tables/lakebase_test_${UNIQUE_NAME}.public.trips_synced" | jq '{name, unity_catalog_provisioning_state: .status.unity_catalog_provisioning_state}' + +trace $CLI bundle summary + +# Filter requests to only show postgres operations (exclude workspace, telemetry, and operation polling). +trace print_requests.py --keep --get '//postgres' '^//workspace-files/' '^//workspace/' '^//telemetry-ext' '^//operations/' > out.requests.json diff --git a/acceptance/bundle/resources/postgres_synced_tables/recreate/databricks.yml.tmpl b/acceptance/bundle/resources/postgres_synced_tables/recreate/databricks.yml.tmpl new file mode 100644 index 00000000000..40a350d53fa --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/recreate/databricks.yml.tmpl @@ -0,0 +1,39 @@ +bundle: + name: recreate-postgres-synced-table-$UNIQUE_NAME + +sync: + paths: [] + +resources: + schemas: + pipeline_storage: + name: pipeline_storage_$UNIQUE_NAME + catalog_name: main + comment: "Pipeline storage for the synced-table recreate test" + + postgres_projects: + my_project: + project_id: test-pg-proj-$UNIQUE_NAME + display_name: "Test Project for Synced Table Recreate" + pg_version: 17 + + postgres_catalogs: + my_catalog: + catalog_id: lakebase_test_$UNIQUE_NAME + branch: ${resources.postgres_projects.my_project.id}/branches/production + postgres_database: appdb + create_database_if_missing: true + + postgres_synced_tables: + my_table: + synced_table_id: ${resources.postgres_catalogs.my_catalog.catalog_id}.public.trips_synced + source_table_full_name: main.source_$UNIQUE_NAME.trips_source + primary_key_columns: ["tpep_pickup_datetime"] + scheduling_policy: SNAPSHOT + postgres_database: appdb + branch: ${resources.postgres_projects.my_project.id}/branches/production + create_database_objects_if_missing: true + timeseries_key: $TIMESERIES_KEY + new_pipeline_spec: + storage_catalog: ${resources.schemas.pipeline_storage.catalog_name} + storage_schema: ${resources.schemas.pipeline_storage.name} diff --git a/acceptance/bundle/resources/postgres_synced_tables/recreate/out.test.toml b/acceptance/bundle/resources/postgres_synced_tables/recreate/out.test.toml new file mode 100644 index 00000000000..110f841fa05 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/recreate/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true +CloudEnvs.azure = false +CloudEnvs.gcp = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] diff --git a/acceptance/bundle/resources/postgres_synced_tables/recreate/output.txt b/acceptance/bundle/resources/postgres_synced_tables/recreate/output.txt new file mode 100644 index 00000000000..7e2924062df --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/recreate/output.txt @@ -0,0 +1,41 @@ +Creating temporary source table: main.source_[UNIQUE_NAME].trips_source +{ + "full_name": "main.source_[UNIQUE_NAME]" +} + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-synced-table-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle plan +recreate postgres_synced_tables.my_table + +Plan: 1 to add, 0 to change, 1 to delete, 3 unchanged + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/recreate-postgres-synced-table-[UNIQUE_NAME]/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle destroy --auto-approve +The following resources will be deleted: + delete resources.postgres_catalogs.my_catalog + delete resources.postgres_projects.my_project + delete resources.postgres_synced_tables.my_table + delete resources.schemas.pipeline_storage + +This action will result in the deletion of the following UC schemas. Any underlying data may be lost: + delete resources.schemas.pipeline_storage + +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/recreate-postgres-synced-table-[UNIQUE_NAME]/default + +Deleting files... +Destroy complete! +Cleaning up temporary source table diff --git a/acceptance/bundle/resources/postgres_synced_tables/recreate/script b/acceptance/bundle/resources/postgres_synced_tables/recreate/script new file mode 100644 index 00000000000..de437c0c151 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/recreate/script @@ -0,0 +1,30 @@ +# Per-test source table to avoid the 20-synced-tables-per-source-table limit. +echo "Creating temporary source table: main.source_$UNIQUE_NAME.trips_source" +$CLI schemas create source_$UNIQUE_NAME main -o json | jq '{full_name}' +MSYS_NO_PATHCONV=1 $CLI api post "/api/2.0/sql/statements/" --json "{ + \"warehouse_id\": \"$TEST_DEFAULT_WAREHOUSE_ID\", + \"statement\": \"CREATE TABLE main.source_$UNIQUE_NAME.trips_source AS SELECT * FROM samples.nyctaxi.trips LIMIT 10\", + \"wait_timeout\": \"45s\" + }" > /dev/null + +cleanup() { + trace $CLI bundle destroy --auto-approve + echo "Cleaning up temporary source table" + $CLI tables delete main.source_$UNIQUE_NAME.trips_source || true + $CLI schemas delete main.source_$UNIQUE_NAME || true + rm -f out.requests.txt +} +trap cleanup EXIT + +export TIMESERIES_KEY=tpep_pickup_datetime +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle deploy + +# Toggle a recreate-on-change field; plan must show delete + create. +# We toggle timeseries_key (not scheduling_policy) so we don't need CDF +# on the source. +export TIMESERIES_KEY=tpep_dropoff_datetime +envsubst < databricks.yml.tmpl > databricks.yml +trace $CLI bundle plan | contains.py "Plan: 1 to add, 0 to change, 1 to delete, 3 unchanged" + +trace $CLI bundle deploy diff --git a/acceptance/bundle/resources/postgres_synced_tables/test.toml b/acceptance/bundle/resources/postgres_synced_tables/test.toml new file mode 100644 index 00000000000..bcde950f372 --- /dev/null +++ b/acceptance/bundle/resources/postgres_synced_tables/test.toml @@ -0,0 +1,29 @@ +Local = true +Cloud = true +RequiresUnityCatalog = true + +# Lakebase v2 (postgres) is only available in AWS as of January 2026 +CloudEnvs.gcp = false +CloudEnvs.azure = false + +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct", "terraform"] + +Ignore = [ + "databricks.yml", + ".databricks", +] + +[[Repls]] +# Clean up ?o= suffix after URL since not all workspaces have that +Old = '\?o=\[(NUMID|ALPHANUMID)\]' +New = '' +Order = 1000 + +# Fake SQL endpoint for the per-test source table create in script. +[[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/{full_name}" +Response.Body = '{"status": "OK"}' 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/schemas/auto-approve/output.txt b/acceptance/bundle/resources/schemas/auto-approve/output.txt index 6a773f60cae..bf8e7771eeb 100644 --- a/acceptance/bundle/resources/schemas/auto-approve/output.txt +++ b/acceptance/bundle/resources/schemas/auto-approve/output.txt @@ -57,7 +57,9 @@ Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/[UNIQUE_NAME]/file This action will result in the deletion or recreation of the following UC schemas. Any underlying data may be lost: delete resources.schemas.bar -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. === Test cleanup 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/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/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/test.toml b/acceptance/bundle/resources/vector_search_endpoints/basic/test.toml deleted file mode 100644 index f8b3bbe49dd..00000000000 --- a/acceptance/bundle/resources/vector_search_endpoints/basic/test.toml +++ /dev/null @@ -1 +0,0 @@ -# All configuration inherited from parent test.toml 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 index 02f23f3f9a3..45555a83ff1 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/budget_policy/output.txt @@ -9,7 +9,7 @@ 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" + "effective_budget_policy_id": "remote-policy" } === Plan detects drift and proposes update 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 index 08afd3157e1..dece842119f 100644 --- 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 @@ -13,9 +13,6 @@ Deployment complete! "endpoint_type": "STANDARD" } ->>> print_state.py -"/vector-search-endpoints/[ORIGINAL_ENDPOINT_UUID]" - === Delete and recreate remotely with the same name >>> [CLI] vector-search-endpoints delete-endpoint vs-endpoint-[UNIQUE_NAME] @@ -35,14 +32,12 @@ Deployment complete! Original endpoint UUID: [ORIGINAL_ENDPOINT_UUID] Remote recreated endpoint UUID: [REMOTE_RECREATED_ENDPOINT_UUID] -=== Plan detects the UUID change and proposes recreate +=== Plan after out-of-band recreate >>> [CLI] bundle plan -recreate vector_search_endpoints.my_endpoint -update vector_search_endpoints.my_endpoint.permissions +create vector_search_endpoints.my_endpoint.permissions -Plan: 1 to add, 1 to change, 1 to delete, 0 unchanged +Plan: 1 to add, 0 to change, 0 to delete, 1 unchanged -=== Deploy recreates the endpoint and rebinds permissions to the new UUID >>> [CLI] bundle deploy Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-recreated-same-name-[UNIQUE_NAME]/default/files... Deploying resources... @@ -51,12 +46,14 @@ 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" } ->>> print_state.py -"/vector-search-endpoints/[UUID]" +=== 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: 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 index 0a17aa3152a..dbef9250f28 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/recreated_same_name/script @@ -14,7 +14,6 @@ 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}' -trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id' title "Delete and recreate remotely with the same name" trace $CLI vector-search-endpoints delete-endpoint "${endpoint_name}" @@ -32,10 +31,11 @@ if [ "$original_endpoint_uuid" = "$remote_recreated_endpoint_uuid" ]; then exit 1 fi -title "Plan detects the UUID change and proposes recreate" -trace $CLI bundle plan | contains.py "recreate vector_search_endpoints.my_endpoint" "update vector_search_endpoints.my_endpoint.permissions" +title "Plan after out-of-band recreate" +trace $CLI bundle plan -title "Deploy recreates the endpoint and rebinds permissions to the new UUID" trace $CLI bundle deploy -trace $CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq '{name, endpoint_type}' -trace print_state.py | jq '.state."resources.vector_search_endpoints.my_endpoint.permissions".state.object_id' +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/min_qps/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/databricks.yml.tmpl similarity index 68% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/databricks.yml.tmpl rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/databricks.yml.tmpl index 7936e98b23d..3999d01fe3b 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/databricks.yml.tmpl +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/databricks.yml.tmpl @@ -1,5 +1,5 @@ bundle: - name: drift-vs-endpoint-min-qps-$UNIQUE_NAME + name: drift-vs-endpoint-target-qps-$UNIQUE_NAME sync: paths: [] @@ -9,4 +9,4 @@ resources: my_endpoint: name: vs-endpoint-$UNIQUE_NAME endpoint_type: STANDARD - min_qps: 1 + target_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/target_qps/out.plan.direct.json similarity index 54% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/out.plan.direct.json index dbd1364b122..0d942ffd26d 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.plan.direct.json +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/out.plan.direct.json @@ -3,13 +3,7 @@ "resources.vector_search_endpoints.my_endpoint": { "action": "update", "changes": { - "endpoint_uuid": { - "action": "skip", - "reason": "custom", - "old": "[MY_ENDPOINT_UUID]", - "remote": "[MY_ENDPOINT_UUID]" - }, - "min_qps": { + "target_qps": { "action": "update", "old": 1, "new": 1, diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/out.test.toml similarity index 100% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/out.test.toml rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/out.test.toml diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/output.txt similarity index 64% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/output.txt index 9f8c49adba5..61502f2d894 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/output.txt @@ -1,26 +1,26 @@ === Initial deployment >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-target-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 +=== Simulate remote drift: change target_qps to 5 outside the bundle +>>> [CLI] vector-search-endpoints patch-endpoint vs-endpoint-[UNIQUE_NAME] --target-qps 5 { - "creation_timestamp":[UNIX_TIME_MILLIS][0], - "creator":"[USERNAME]", + "creation_timestamp": [UNIX_TIME_MILLIS][0], + "creator": "[USERNAME]", "endpoint_status": { - "state":"ONLINE" + "state": "ONLINE" }, - "endpoint_type":"STANDARD", - "id":"[MY_ENDPOINT_UUID]", - "last_updated_timestamp":[UNIX_TIME_MILLIS][1], - "last_updated_user":"[USERNAME]", - "name":"vs-endpoint-[UNIQUE_NAME]", + "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 + "requested_target_qps": 5 } } @@ -30,9 +30,9 @@ update vector_search_endpoints.my_endpoint Plan: 0 to add, 1 to change, 0 to delete, 0 unchanged -=== Deploy restores min_qps to 1 +=== Deploy restores target_qps to 1 >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-target-qps-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! @@ -42,7 +42,7 @@ Deployment complete! "method": "PATCH", "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]", "body": { - "min_qps": 1 + "target_qps": 1 } } @@ -56,7 +56,7 @@ Deployment complete! 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 +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/drift-vs-endpoint-target-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/target_qps/script similarity index 65% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/script index 3c2062e4747..58ce3a2f5ad 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/script +++ b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/script @@ -11,19 +11,14 @@ trace $CLI bundle deploy endpoint_name="vs-endpoint-${UNIQUE_NAME}" -# Register a stable label for the endpoint UUID so the plan output shows the -# same token for both saved (old) and remote, confirming they match. -endpoint_uuid=$($CLI vector-search-endpoints get-endpoint "${endpoint_name}" | jq -r '.id') -add_repl.py "$endpoint_uuid" "MY_ENDPOINT_UUID" - -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 "Simulate remote drift: change target_qps to 5 outside the bundle" +trace $CLI vector-search-endpoints patch-endpoint "${endpoint_name}" --target-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" +title "Deploy restores target_qps to 1" rm -f out.requests.txt trace $CLI bundle deploy trace print_requests.py '//vector-search/endpoints' diff --git a/acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/test.toml b/acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/test.toml similarity index 100% rename from acceptance/bundle/resources/vector_search_endpoints/drift/min_qps/test.toml rename to acceptance/bundle/resources/vector_search_endpoints/drift/target_qps/test.toml 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/update/min_qps/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/out.test.toml similarity index 100% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.test.toml rename to acceptance/bundle/resources/vector_search_endpoints/recreate/create-fails/out.test.toml 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/update/min_qps/databricks.yml.tmpl b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/databricks.yml.tmpl similarity index 75% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/databricks.yml.tmpl rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/databricks.yml.tmpl index 7c326b69d51..71272f0325a 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/databricks.yml.tmpl +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/databricks.yml.tmpl @@ -1,5 +1,5 @@ bundle: - name: update-vs-endpoint-min-qps-$UNIQUE_NAME + name: update-vs-endpoint-target-qps-$UNIQUE_NAME sync: paths: [] @@ -9,7 +9,7 @@ resources: my_endpoint: name: vs-endpoint-$UNIQUE_NAME endpoint_type: STANDARD - min_qps: 1 + target_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/target_qps/out.requests.create.direct.json similarity index 87% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.create.direct.json rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.requests.create.direct.json index 0a1d51a3512..7e7209e51e5 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.create.direct.json +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.requests.create.direct.json @@ -3,8 +3,8 @@ "path": "/api/2.0/vector-search/endpoints", "body": { "endpoint_type": "STANDARD", - "min_qps": 1, - "name": "vs-endpoint-[UNIQUE_NAME]" + "name": "vs-endpoint-[UNIQUE_NAME]", + "target_qps": 1 } } { 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/target_qps/out.requests.update.direct.json similarity index 95% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.update.direct.json rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.requests.update.direct.json index 24876c67bed..b361b6e4357 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/out.requests.update.direct.json +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.requests.update.direct.json @@ -2,7 +2,7 @@ "method": "PATCH", "path": "/api/2.0/vector-search/endpoints/vs-endpoint-[UNIQUE_NAME]", "body": { - "min_qps": 2 + "target_qps": 2 } } { diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/out.test.toml new file mode 100644 index 00000000000..88423408186 --- /dev/null +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_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/target_qps/output.txt similarity index 80% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/output.txt rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/output.txt index 8dec120bc4a..5402cefc3af 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/output.txt +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/output.txt @@ -1,7 +1,7 @@ === Initial deployment >>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-min-qps-[UNIQUE_NAME]/default/files... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-target-qps-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! @@ -14,8 +14,8 @@ Deployment complete! "endpoint_type": "STANDARD" } -=== Update min_qps ->>> update_file.py databricks.yml min_qps: 1 min_qps: 2 +=== Update target_qps +>>> update_file.py databricks.yml target_qps: 1 target_qps: 2 >>> [CLI] bundle plan update vector_search_endpoints.my_endpoint @@ -24,7 +24,7 @@ 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... +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-target-qps-[UNIQUE_NAME]/default/files... Deploying resources... Updating deployment state... Deployment complete! @@ -41,7 +41,7 @@ Deployment complete! 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 +All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/update-vs-endpoint-target-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/target_qps/script similarity index 88% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/script rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/script index c21d64b9d97..c17c8adfedc 100644 --- a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/script +++ b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/script @@ -20,8 +20,8 @@ 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" +title "Update target_qps" +trace update_file.py databricks.yml "target_qps: 1" "target_qps: 2" trace $CLI bundle plan rm -f out.requests.txt diff --git a/acceptance/bundle/resources/vector_search_endpoints/update/min_qps/test.toml b/acceptance/bundle/resources/vector_search_endpoints/update/target_qps/test.toml similarity index 100% rename from acceptance/bundle/resources/vector_search_endpoints/update/min_qps/test.toml rename to acceptance/bundle/resources/vector_search_endpoints/update/target_qps/test.toml 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/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/recreate/output.txt b/acceptance/bundle/resources/volumes/recreate/output.txt index 494e9ff538d..5714559a602 100644 --- a/acceptance/bundle/resources/volumes/recreate/output.txt +++ b/acceptance/bundle/resources/volumes/recreate/output.txt @@ -28,7 +28,7 @@ Deployment complete! { "privilege_assignments": [ { - "principal":"account users", + "principal": "account users", "privileges": [ "WRITE_VOLUME" ] @@ -45,7 +45,9 @@ 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: recreate resources.volumes.foo -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. Exit code: 1 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/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/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/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/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/state_present/output.txt b/acceptance/bundle/state/state_present/output.txt index 706b54a67a0..3c81760a391 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.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-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.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-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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 @@ -91,14 +91,14 @@ Deployment complete! >>> print_state.py 3 -15 +13 contains error: '12' not found in the output. >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states [TEST_TMP_DIR]/.databricks/bundle/default/terraform/terraform.tfstate: local terraform state serial=3 lineage="test-lineage" -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=15 lineage="test-lineage" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=13 lineage="test-lineage" >>> DATABRICKS_BUNDLE_ENGINE= [CLI] bundle debug states --force-pull [TEST_TMP_DIR]/.databricks/bundle/default/terraform/terraform.tfstate: local terraform state serial=3 lineage="test-lineage" -resources.json: remote direct state serial=15 lineage="test-lineage" -[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=15 lineage="test-lineage" +resources.json: remote direct state serial=13 lineage="test-lineage" +[TEST_TMP_DIR]/.databricks/bundle/default/resources.json: local direct state serial=13 lineage="test-lineage" diff --git a/acceptance/bundle/summary/show-full-config/databricks.yml b/acceptance/bundle/summary/show-full-config/databricks.yml deleted file mode 100644 index 08ac0519c7c..00000000000 --- a/acceptance/bundle/summary/show-full-config/databricks.yml +++ /dev/null @@ -1,19 +0,0 @@ -bundle: - name: test-bundle - -include: - - targets/*.yml - - resources/*.yml - -variables: - mode: - default: development - default: - default: false - -targets: - also_default: - mode: development - default: true - workspace: - host: https://example.com diff --git a/acceptance/bundle/summary/show-full-config/output.txt b/acceptance/bundle/summary/show-full-config/output.txt deleted file mode 100644 index 81654d82306..00000000000 --- a/acceptance/bundle/summary/show-full-config/output.txt +++ /dev/null @@ -1,135 +0,0 @@ - ->>> [CLI] bundle summary --show-full-config --include-locations -{ - "__locations": { - "files": [ - "databricks.yml", - "resources/my_dashboard.dashboard.yml", - "targets/default.yml", - "targets/not_default.yml", - "targets/variable_default.yml", - "targets/variable_mode.yml" - ], - "locations": { - "bundle": [ - [ - 0, - 2, - 3 - ] - ], - "include": [ - [ - 0, - 5, - 3 - ] - ], - "resources": [ - [ - 1, - 2, - 3 - ] - ], - "resources.dashboards": [ - [ - 1, - 3, - 5 - ] - ], - "resources.dashboards.my_dashboard": [ - [ - 1, - 4, - 7 - ] - ], - "targets": [ - [ - 0, - 15, - 3 - ], - [ - 2, - 2, - 3 - ], - [ - 3, - 2, - 3 - ], - [ - 4, - 2, - 3 - ], - [ - 5, - 2, - 3 - ] - ], - "variables": [ - [ - 0, - 9, - 3 - ] - ] - }, - "version": 1 - }, - "bundle": { - "name": "test-bundle" - }, - "include": [ - "targets/default.yml", - "targets/not_default.yml", - "targets/variable_default.yml", - "targets/variable_mode.yml", - "resources/my_dashboard.dashboard.yml" - ], - "resources": { - "dashboards": { - "my_dashboard": { - "display_name": "My Dashboard", - "file_path": "../src/my_dashboard.lvdash.json", - "warehouse_id": "[NUMID]abcdef" - } - } - }, - "targets": { - "also_default": { - "default": true, - "mode": "development", - "workspace": { - "host": "https://example.com" - } - }, - "default": { - "default": true, - "mode": "development" - }, - "not_default": { - "mode": "production" - }, - "variable_default": { - "default": "${var.default}" - }, - "variable_mode": { - "mode": "${var.mode}" - } - }, - "variables": { - "default": { - "default": false - }, - "mode": { - "default": "development" - } - } -} diff --git a/acceptance/bundle/summary/show-full-config/resources/my_dashboard.dashboard.yml b/acceptance/bundle/summary/show-full-config/resources/my_dashboard.dashboard.yml deleted file mode 100644 index 07ff8d8f75e..00000000000 --- a/acceptance/bundle/summary/show-full-config/resources/my_dashboard.dashboard.yml +++ /dev/null @@ -1,6 +0,0 @@ -resources: - dashboards: - my_dashboard: - display_name: My Dashboard - file_path: ../src/my_dashboard.lvdash.json - warehouse_id: 1234567890abcdef diff --git a/acceptance/bundle/summary/show-full-config/script b/acceptance/bundle/summary/show-full-config/script deleted file mode 100644 index eccfb0544cb..00000000000 --- a/acceptance/bundle/summary/show-full-config/script +++ /dev/null @@ -1 +0,0 @@ -trace $CLI bundle summary --show-full-config --include-locations diff --git a/acceptance/bundle/summary/show-full-config/src/my_dashboard.lvdash.json b/acceptance/bundle/summary/show-full-config/src/my_dashboard.lvdash.json deleted file mode 100644 index 0967ef424bc..00000000000 --- a/acceptance/bundle/summary/show-full-config/src/my_dashboard.lvdash.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/acceptance/bundle/summary/show-full-config/targets/default.yml b/acceptance/bundle/summary/show-full-config/targets/default.yml deleted file mode 100644 index 7dd22abd892..00000000000 --- a/acceptance/bundle/summary/show-full-config/targets/default.yml +++ /dev/null @@ -1,4 +0,0 @@ -targets: - default: - mode: development - default: true diff --git a/acceptance/bundle/summary/show-full-config/targets/not_default.yml b/acceptance/bundle/summary/show-full-config/targets/not_default.yml deleted file mode 100644 index 44f4a59fabc..00000000000 --- a/acceptance/bundle/summary/show-full-config/targets/not_default.yml +++ /dev/null @@ -1,3 +0,0 @@ -targets: - not_default: - mode: production diff --git a/acceptance/bundle/summary/show-full-config/targets/variable_default.yml b/acceptance/bundle/summary/show-full-config/targets/variable_default.yml deleted file mode 100644 index 3b086d8bfb1..00000000000 --- a/acceptance/bundle/summary/show-full-config/targets/variable_default.yml +++ /dev/null @@ -1,3 +0,0 @@ -targets: - variable_default: - default: ${var.default} diff --git a/acceptance/bundle/summary/show-full-config/targets/variable_mode.yml b/acceptance/bundle/summary/show-full-config/targets/variable_mode.yml deleted file mode 100644 index 2d0339c4b11..00000000000 --- a/acceptance/bundle/summary/show-full-config/targets/variable_mode.yml +++ /dev/null @@ -1,3 +0,0 @@ -targets: - variable_mode: - mode: ${var.mode} diff --git a/acceptance/bundle/summary/show-full-config/test.toml b/acceptance/bundle/summary/show-full-config/test.toml deleted file mode 100644 index 9cf9b33ecab..00000000000 --- a/acceptance/bundle/summary/show-full-config/test.toml +++ /dev/null @@ -1,3 +0,0 @@ -[[Repls]] -Old = '\\\\' -New = '/' 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/test.toml b/acceptance/bundle/telemetry/deploy/test.toml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance/bundle/templates/default-scala/output/my_default_scala/build.sbt b/acceptance/bundle/templates/default-scala/output/my_default_scala/build.sbt index 7d4a7215439..7adfb0ba1b1 100644 --- a/acceptance/bundle/templates/default-scala/output/my_default_scala/build.sbt +++ b/acceptance/bundle/templates/default-scala/output/my_default_scala/build.sbt @@ -6,7 +6,7 @@ name := "my_default_scala" organization := "com.examples" version := "0.1" -libraryDependencies += "com.databricks" %% "databricks-connect" % "17.0.+" +libraryDependencies += "com.databricks" %% "databricks-connect" % "17.3.+" libraryDependencies += "org.slf4j" % "slf4j-simple" % "2.0.16" libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % Test diff --git a/acceptance/bundle/templates/default-scala/output/my_default_scala/resources/my_default_scala.job.yml b/acceptance/bundle/templates/default-scala/output/my_default_scala/resources/my_default_scala.job.yml index 3e40cd1629c..f3c9332d16f 100644 --- a/acceptance/bundle/templates/default-scala/output/my_default_scala/resources/my_default_scala.job.yml +++ b/acceptance/bundle/templates/default-scala/output/my_default_scala/resources/my_default_scala.job.yml @@ -22,6 +22,6 @@ resources: environments: - environment_key: default spec: - environment_version: "4-scala-preview" + environment_version: "4" java_dependencies: - ${workspace.artifact_path}/.internal/my_default_scala-assembly-0.1.jar diff --git a/acceptance/bundle/templates/pydabs/test.toml b/acceptance/bundle/templates/pydabs/test.toml index 51537c52ecc..fc66c79de7c 100644 --- a/acceptance/bundle/templates/pydabs/test.toml +++ b/acceptance/bundle/templates/pydabs/test.toml @@ -5,5 +5,5 @@ Local = true Cloud = false [[Repls]] -Old = '"databricks-bundles==0.\d+.\d+"' +Old = '"databricks-bundles==\d+\.\d+\.\d+"' New = '"databricks-bundles==x.y.z"' diff --git a/acceptance/bundle/user_agent/output.txt b/acceptance/bundle/user_agent/output.txt index bf128624271..5c6e383d1ff 100644 --- a/acceptance/bundle/user_agent/output.txt +++ b/acceptance/bundle/user_agent/output.txt @@ -37,12 +37,14 @@ 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.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 deploy.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat' +MISS deploy.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat' +MISS deploy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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' @@ -67,15 +69,17 @@ 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.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 destroy.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' +MISS destroy.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS destroy.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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' @@ -86,8 +90,9 @@ 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 plan.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat' +MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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' @@ -103,9 +108,10 @@ 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.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 plan2.terraform /api/2.0/preview/scim/v2/Me 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5' +MISS plan2.terraform /.well-known/databricks-config 'databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 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' @@ -119,10 +125,12 @@ 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 435b188af3b..d9230f0c8b3 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.deploy.terraform.json @@ -246,7 +246,11 @@ "name": "myschema", "owner": "[USERNAME]", "properties": null, - "provider_config": [], + "provider_config": [ + { + "workspace_id": "[NUMID]" + } + ], "schema_id": "[UUID]", "storage_root": null }, @@ -308,7 +312,25 @@ { "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 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -317,7 +339,7 @@ { "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 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "POST", @@ -330,7 +352,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -339,7 +361,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -348,7 +370,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -357,7 +379,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", 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 f8ab210ec72..216a2f38dac 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.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-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "DELETE", @@ -148,7 +148,25 @@ { "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 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -157,7 +175,7 @@ { "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 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -166,7 +184,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -175,7 +193,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -184,7 +202,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -193,7 +211,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -202,7 +220,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -211,7 +229,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", 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 11daf62e9ed..698b816f170 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan.terraform.json @@ -58,7 +58,16 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -67,7 +76,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", 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 75f4620ef48..2b6d39fc59e 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json @@ -76,7 +76,16 @@ { "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 sdk/sdkv2 resource/schema auth/pat" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" + ] + }, + "method": "GET", + "path": "/api/2.0/preview/scim/v2/Me" +} +{ + "headers": { + "User-Agent": [ + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat" ] }, "method": "GET", @@ -85,7 +94,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", @@ -94,7 +103,7 @@ { "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" + "databricks-tf-provider/1.115.0 databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[DEV_VERSION] terraform/1.5.5" ] }, "method": "GET", 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 3a3e2db9e9a..c3017391f2f 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.direct.json @@ -33,6 +33,15 @@ "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 3a3e2db9e9a..bf160a9744d 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.summary.terraform.json @@ -33,6 +33,15 @@ "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/validate/catalog_requires_direct_mode/output.txt b/acceptance/bundle/validate/catalog_requires_direct_mode/output.txt index 2bddbc154b0..c110f0aa7f9 100644 --- a/acceptance/bundle/validate/catalog_requires_direct_mode/output.txt +++ b/acceptance/bundle/validate/catalog_requires_direct_mode/output.txt @@ -1,7 +1,7 @@ Error: Catalog resources are only supported with direct deployment mode in databricks.yml:6:5 -Catalog resources require direct deployment mode. Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' to use catalog resources. +Catalog resources require direct deployment mode. Please set the DATABRICKS_BUNDLE_ENGINE environment variable to 'direct' or set 'bundle.engine: direct' in your databricks.yml to use catalog resources. Learn more at https://docs.databricks.com/dev-tools/bundles/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/variables/test.toml b/acceptance/bundle/variables/test.toml deleted file mode 100644 index f5945bd6205..00000000000 --- a/acceptance/bundle/variables/test.toml +++ /dev/null @@ -1,2 +0,0 @@ -# The tests here intend to test variable interpolation via "bundle validate". -# Even though "bundle validate" does a few API calls, that's not the focus there. 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/cmd/account/account-help/output.txt b/acceptance/cmd/account/account-help/output.txt index b39d4395521..1687f96d466 100644 --- a/acceptance/cmd/account/account-help/output.txt +++ b/acceptance/cmd/account/account-help/output.txt @@ -6,10 +6,10 @@ Usage: databricks account [command] Identity and Access Management - access-control These APIs manage access rules on resources in an account. + access-control *Public Preview* These APIs manage access rules on resources in an account. groups Groups simplify identity management, making it easier to assign access to Databricks account, data, and other securable objects. groups-v2 Groups simplify identity management, making it easier to assign access to Databricks account, data, and other securable objects. - iam-v2 These APIs are used to manage identities and the workspace access of these identities in . + iam-v2 *Beta* These APIs are used to manage identities and the workspace access of these identities in . service-principals Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. service-principals-v2 Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. users User identities recognized by Databricks and represented by email addresses. @@ -26,13 +26,13 @@ Settings network-connectivity These APIs provide configurations for the network connectivity of your workspaces for serverless compute resources. network-policies These APIs manage network policies for this account. settings Accounts Settings API allows users to manage settings at the account level. - settings-v2 APIs to manage account level settings. + settings-v2 *Public Preview* APIs to manage account level settings. workspace-network-configuration These APIs allow configuration of network settings for Databricks workspaces by selecting which network policy to associate with the workspace. Provisioning credentials These APIs manage credential configurations for this workspace. encryption-keys These APIs manage encryption key configurations for this workspace (optional). - endpoints These APIs manage endpoint configurations for this account. + endpoints *Public Preview* These APIs manage endpoint configurations for this account. networks These APIs manage network configurations for customer-managed VPCs (optional). private-access These APIs manage private access settings for this account. storage These APIs manage storage configurations for this workspace. @@ -41,10 +41,10 @@ Provisioning Billing billable-usage This API allows you to download billable usage logs for the specified account and date range. - budget-policy A service serves REST API about Budget policies. - budgets These APIs manage budget configurations for this account. + budget-policy *Public Preview* A service serves REST API about Budget policies. + budgets *Public Preview* These APIs manage budget configurations for this account. log-delivery These APIs manage log delivery configurations for this account. - usage-dashboards These APIs manage usage dashboards for this account. + usage-dashboards *Public Preview* These APIs manage usage dashboards for this account. OAuth custom-app-integration These APIs enable administrators to manage custom OAuth app integrations, which is required for adding/using Custom OAuth App Integration like Tableau Cloud for Databricks in AWS cloud. 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/default-profile/out.test.toml b/acceptance/cmd/api/default-profile/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/api/default-profile/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/api/default-profile/output.txt b/acceptance/cmd/api/default-profile/output.txt new file mode 100644 index 00000000000..2d22f0db2bc --- /dev/null +++ b/acceptance/cmd/api/default-profile/output.txt @@ -0,0 +1,28 @@ + +=== default_profile is used when no --profile flag and no DATABRICKS_CONFIG_PROFILE + +>>> [CLI] api get /api/2.0/clusters/list +{} + +>>> 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" +} + +=== --profile overrides default_profile (negative case) +Warn: [hostmetadata] failed to fetch host metadata for https://other.test, will skip for 1m0s +Error: Get "https://other.test/api/2.0/clusters/list": (redacted) + +Exit code: 1 diff --git a/acceptance/cmd/api/default-profile/script b/acceptance/cmd/api/default-profile/script new file mode 100644 index 00000000000..0a926912fa0 --- /dev/null +++ b/acceptance/cmd/api/default-profile/script @@ -0,0 +1,28 @@ +sethome "./home" + +# Two profiles plus an explicit default_profile pointing at the test server. +# The 'other' profile points at an RFC 2606 reserved host so we can assert +# that --profile overrides default_profile without making a real request. +cat > "./home/.databrickscfg" <&1 | contains.py "other.test" diff --git a/acceptance/cmd/api/default-profile/test.toml b/acceptance/cmd/api/default-profile/test.toml new file mode 100644 index 00000000000..02f926882c8 --- /dev/null +++ b/acceptance/cmd/api/default-profile/test.toml @@ -0,0 +1,10 @@ +Ignore = [ + "home", +] + +# Redact the OS- and network-dependent suffix on the failed lookup so the +# negative case (--profile overrides default_profile) is stable across +# runners. We still assert the requested host appears in output. +[[Repls]] +Old = 'Get "https://other.test/api/2.0/clusters/list": .*' +New = 'Get "https://other.test/api/2.0/clusters/list": (redacted)' 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/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..bd5e6108735 --- /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": "secure", + "location": "OS keyring (service: databricks-cli)", + "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: no cached credentials; run `databricks auth login` to sign in +Token storage: plaintext, ~/.databricks/token-cache.json (from auth_storage in [__settings__] section of [TEST_TMP_DIR]/home/.databrickscfg) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from [TEST_TMP_DIR]/home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from [TEST_TMP_DIR]/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: no cached credentials; run `databricks auth login` to sign in +Token storage: plaintext, ~/.databricks/token-cache.json (from DATABRICKS_AUTH_STORAGE environment variable) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from [TEST_TMP_DIR]/home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from [TEST_TMP_DIR]/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 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: [KEYRING_LOOKUP_ERROR] +Token storage: secure, OS keyring (service: databricks-cli) (from default) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from [TEST_TMP_DIR]/home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from [TEST_TMP_DIR]/home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-secure-default/script b/acceptance/cmd/auth/describe/u2m-secure-default/script new file mode 100644 index 00000000000..d0b1ce40020 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-secure-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" <>> musterr [CLI] auth token --profile logfood -Error: cache: databricks OAuth is not configured for this host. Try logging in again with `databricks auth login --profile logfood` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new +Error: cache: databricks OAuth is not configured for this host. no cached credentials; run `databricks auth login` to sign in diff --git a/acceptance/cmd/auth/profiles/output.txt b/acceptance/cmd/auth/profiles/output.txt index 207e2d54716..c2d5f743f43 100644 --- a/acceptance/cmd/auth/profiles/output.txt +++ b/acceptance/cmd/auth/profiles/output.txt @@ -4,28 +4,28 @@ Warn: [hostmetadata] failed to fetch host metadata for https://test.cloud.databr { "profiles": [ { - "name":"workspace-profile", - "host":"https://test.cloud.databricks.com", - "cloud":"aws", - "auth_type":"", - "valid":false + "name": "workspace-profile", + "host": "https://test.cloud.databricks.com", + "cloud": "aws", + "auth_type": "", + "valid": false }, { - "name":"account-profile", - "host":"https://accounts.cloud.databricks.com", - "account_id":"test-account-123", - "cloud":"aws", - "auth_type":"", - "valid":false + "name": "account-profile", + "host": "https://accounts.cloud.databricks.com", + "account_id": "test-account-123", + "cloud": "aws", + "auth_type": "", + "valid": false }, { - "name":"unified-profile", - "host":"https://unified.databricks.com", - "account_id":"unified-account-456", - "workspace_id":"[NUMID]", - "cloud":"aws", - "auth_type":"", - "valid":false + "name": "unified-profile", + "host": "https://unified.databricks.com", + "account_id": "unified-account-456", + "workspace_id": "[NUMID]", + "cloud": "aws", + "auth_type": "", + "valid": false } ] } diff --git a/acceptance/cmd/auth/profiles/spog-account/output.txt b/acceptance/cmd/auth/profiles/spog-account/output.txt index f5ce0ac53cd..7ec701d700e 100644 --- a/acceptance/cmd/auth/profiles/spog-account/output.txt +++ b/acceptance/cmd/auth/profiles/spog-account/output.txt @@ -4,13 +4,13 @@ { "profiles": [ { - "name":"spog-account", - "host":"[DATABRICKS_URL]", - "account_id":"spog-acct-123", - "workspace_id":"none", - "cloud":"aws", - "auth_type":"pat", - "valid":true + "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/token/default-profile/out.test.toml b/acceptance/cmd/auth/token/default-profile/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/auth/token/default-profile/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/token/default-profile/output.txt b/acceptance/cmd/auth/token/default-profile/output.txt new file mode 100644 index 00000000000..4b386f69e94 --- /dev/null +++ b/acceptance/cmd/auth/token/default-profile/output.txt @@ -0,0 +1,15 @@ + +=== default_profile is honored when no args, --profile, or env var + +>>> errcode [CLI] auth token +Warn: [hostmetadata] failed to fetch host metadata for https://myworkspace.test, will skip for 1m0s +Error: cache: databricks OAuth is not configured for this host. no cached credentials; run `databricks auth login` to sign in + +Exit code: 1 + +=== default_profile pointing at a missing profile falls through to picker + +>>> errcode [CLI] auth token +Error: no profile specified. Use --profile to specify which profile to use + +Exit code: 1 diff --git a/acceptance/cmd/auth/token/default-profile/script b/acceptance/cmd/auth/token/default-profile/script new file mode 100644 index 00000000000..f5de6231901 --- /dev/null +++ b/acceptance/cmd/auth/token/default-profile/script @@ -0,0 +1,34 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE + +# default_profile points at "myprofile". Without it, `auth token` would fall +# through to the non-interactive error "no profile specified". +cat > "./home/.databrickscfg" <<'ENDCFG' +[myprofile] +host = https://myworkspace.test +auth_type = databricks-cli + +[other] +host = https://other.test +auth_type = databricks-cli + +[__settings__] +default_profile = myprofile +ENDCFG + +title "default_profile is honored when no args, --profile, or env var\n" +trace errcode $CLI auth token + +title "default_profile pointing at a missing profile falls through to picker\n" +cat > "./home/.databrickscfg" <<'ENDCFG' +[myprofile] +host = https://myworkspace.test +auth_type = databricks-cli + +[__settings__] +default_profile = does-not-exist +ENDCFG +trace errcode $CLI auth token diff --git a/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt b/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt index a5fb2467341..bd35319a5a8 100644 --- a/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt +++ b/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt @@ -1,3 +1,3 @@ -Error: cache: databricks OAuth is not configured for this host. Try logging in again with `databricks auth login --profile test-profile` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new +Error: cache: databricks OAuth is not configured for this host. no cached credentials; run `databricks auth login` to sign in Exit code: 1 diff --git a/acceptance/cmd/auth/token/output.txt b/acceptance/cmd/auth/token/output.txt index 9299dfe871b..67bf1a3654e 100644 --- a/acceptance/cmd/auth/token/output.txt +++ b/acceptance/cmd/auth/token/output.txt @@ -1,5 +1,5 @@ >>> [CLI] auth token --host [DATABRICKS_URL] -Error: cache: databricks OAuth is not configured for this host. Try logging in again with `databricks auth login --host [DATABRICKS_URL]` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new +Error: cache: databricks OAuth is not configured for this host. no cached credentials; run `databricks auth login` to sign in Exit code: 1 diff --git a/acceptance/cmd/completion/output.txt b/acceptance/cmd/completion/output.txt index 17ed0f61eb0..d9e58987459 100644 --- a/acceptance/cmd/completion/output.txt +++ b/acceptance/cmd/completion/output.txt @@ -1,34 +1,34 @@ >>> [CLI] completion install --shell zsh --auto-approve Databricks CLI completions installed for zsh. -Restart your shell or run 'source home/.zshrc' to activate. +Restart your shell or run 'source [HOME]/.zshrc' to activate. Warning: zsh completions require the completion system to be initialized. -Add the following to your home/.zshrc: +Add the following to your [HOME]/.zshrc: autoload -U compinit && compinit >>> [CLI] completion status --shell zsh Shell: zsh -File: home/.zshrc +File: [HOME]/.zshrc Status: installed Warning: zsh completions require the completion system to be initialized. -Add the following to your home/.zshrc: +Add the following to your [HOME]/.zshrc: autoload -U compinit && compinit >>> [CLI] completion install --shell zsh --auto-approve -Databricks CLI completions are already installed for zsh in home/.zshrc. +Databricks CLI completions are already installed for zsh in [HOME]/.zshrc. Warning: zsh completions require the completion system to be initialized. -Add the following to your home/.zshrc: +Add the following to your [HOME]/.zshrc: autoload -U compinit && compinit >>> [CLI] completion uninstall --shell zsh --auto-approve -Databricks CLI completions removed for zsh from home/.zshrc. +Databricks CLI completions removed for zsh from [HOME]/.zshrc. >>> [CLI] completion status --shell zsh Shell: zsh -File: home/.zshrc +File: [HOME]/.zshrc Status: not installed # bash completion V2 for databricks -*- shell-script -*- #compdef databricks diff --git a/acceptance/cmd/workspace/apps/output.txt b/acceptance/cmd/workspace/apps/output.txt index e722afbe860..8d6fd58c574 100644 --- a/acceptance/cmd/workspace/apps/output.txt +++ b/acceptance/cmd/workspace/apps/output.txt @@ -3,82 +3,82 @@ >>> [CLI] apps create --json @input.json { "active_deployment": { - "deployment_id":"deploy-[NUMID]", - "source_code_path":"/Workspace/Users/[USERNAME]/test-name", + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-name", "status": { - "message":"Deployment succeeded", - "state":"SUCCEEDED" + "message": "Deployment succeeded", + "state": "SUCCEEDED" } }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, - "compute_size":"MEDIUM", + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "default_source_code_path":"/Workspace/Users/[USERNAME]/test-name", - "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", + "deployment_id": "deploy-[NUMID]", + "source_code_path": "/Workspace/Users/[USERNAME]/test-name", "status": { - "message":"Deployment succeeded", - "state":"SUCCEEDED" + "message": "Deployment succeeded", + "state": "SUCCEEDED" } }, "app_status": { - "message":"Application is running.", - "state":"RUNNING" + "message": "Application is running.", + "state": "RUNNING" }, - "compute_size":"MEDIUM", + "compute_size": "MEDIUM", "compute_status": { - "message":"App compute is active.", - "state":"ACTIVE" + "message": "App compute is active.", + "state": "ACTIVE" }, - "default_source_code_path":"/Workspace/Users/[USERNAME]/test-name", - "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/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/dbr_test.go b/acceptance/dbr_test.go index 2fe498698a1..4a264eecd53 100644 --- a/acceptance/dbr_test.go +++ b/acceptance/dbr_test.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/google/uuid" @@ -27,7 +28,7 @@ func workspaceTmpDir(ctx context.Context, t *testing.T) string { w, err := databricks.NewWorkspaceClient() require.NoError(t, err) - currentUser, err := w.CurrentUser.Me(ctx) + currentUser, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) timestamp := time.Now().Format("2006-01-02T15:04:05Z") @@ -79,7 +80,7 @@ func setupDbrTestDir(ctx context.Context, t *testing.T, uniqueID string) (*datab w, err := databricks.NewWorkspaceClient() require.NoError(t, err) - currentUser, err := w.CurrentUser.Me(ctx) + currentUser, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) // API path (without /Workspace prefix) for workspace API calls. @@ -180,7 +181,7 @@ func runDbrTests(ctx context.Context, t *testing.T, w *databricks.WorkspaceClien t.Fatal("CLOUD_ENV is not set. Please run DBR tests from a CI environment with deco env run.") } - currentUser, err := w.CurrentUser.Me(ctx) + currentUser, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) // Create debug logs directory diff --git a/acceptance/experimental/aitools/skills/install-experimental-empty/out.test.toml b/acceptance/experimental/aitools/skills/install-experimental-empty/out.test.toml new file mode 100644 index 00000000000..d6187dcb046 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-experimental-empty/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/experimental/aitools/skills/install-experimental-empty/output.txt b/acceptance/experimental/aitools/skills/install-experimental-empty/output.txt new file mode 100644 index 00000000000..78a0d503215 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-experimental-empty/output.txt @@ -0,0 +1,9 @@ + +=== --experimental against a manifest with no experimental skills logs a nudge +>>> [CLI] experimental aitools install --global --experimental +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Warn: --experimental was set but the manifest at test-ref exposes no experimental skills. Set DATABRICKS_SKILLS_REF to a release that includes them (or =main for the latest). +Installed 1 skill. diff --git a/acceptance/experimental/aitools/skills/install-experimental-empty/script b/acceptance/experimental/aitools/skills/install-experimental-empty/script new file mode 100644 index 00000000000..cf27462115c --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-experimental-empty/script @@ -0,0 +1,5 @@ +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +title "--experimental against a manifest with no experimental skills logs a nudge" +trace $CLI experimental aitools install --global --experimental diff --git a/acceptance/experimental/aitools/skills/install-experimental-empty/test.toml b/acceptance/experimental/aitools/skills/install-experimental-empty/test.toml new file mode 100644 index 00000000000..0e77cc5247d --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-experimental-empty/test.toml @@ -0,0 +1,31 @@ +Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" +Env.DATABRICKS_SKILLS_REF = "test-ref" + +Ignore = [ + ".databricks/aitools/skills/.state.json", +] + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = [] + +# Stable-only manifest (no repo_dir=experimental). +[[Server]] +Pattern = "GET /test-ref/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-stable": {"version": "1.0.0", "files": ["SKILL.md"]} + } +} +''' + +[[Server]] +Pattern = "GET /test-ref/skills/test-stable/SKILL.md" +Response.Body = '''--- +name: test-stable +--- + +# Stable +''' diff --git a/acceptance/experimental/aitools/skills/install-specific/out.test.toml b/acceptance/experimental/aitools/skills/install-specific/out.test.toml new file mode 100644 index 00000000000..d6187dcb046 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-specific/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/experimental/aitools/skills/install-specific/output.txt b/acceptance/experimental/aitools/skills/install-specific/output.txt new file mode 100644 index 00000000000..f6222311b1b --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-specific/output.txt @@ -0,0 +1,26 @@ + +=== install only one specific stable skill via --skills +>>> [CLI] experimental aitools install --global --skills test-stable-a +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Installed 1 skill. + +=== install a specific experimental skill +>>> [CLI] experimental aitools install --global --skills test-exp --experimental +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Installed 1 skill. + +=== asking for an experimental skill without --experimental flag errors out +>>> [CLI] experimental aitools install --global --skills test-exp +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Error: skill "test-exp" is experimental; use --experimental to install + +Exit code: 1 diff --git a/acceptance/experimental/aitools/skills/install-specific/script b/acceptance/experimental/aitools/skills/install-specific/script new file mode 100644 index 00000000000..f87aa4fbd24 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-specific/script @@ -0,0 +1,11 @@ +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +title "install only one specific stable skill via --skills" +trace $CLI experimental aitools install --global --skills test-stable-a + +title "install a specific experimental skill" +trace $CLI experimental aitools install --global --skills test-exp --experimental + +title "asking for an experimental skill without --experimental flag errors out" +errcode trace $CLI experimental aitools install --global --skills test-exp diff --git a/acceptance/experimental/aitools/skills/install-specific/test.toml b/acceptance/experimental/aitools/skills/install-specific/test.toml new file mode 100644 index 00000000000..9be89c8b92a --- /dev/null +++ b/acceptance/experimental/aitools/skills/install-specific/test.toml @@ -0,0 +1,50 @@ +Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" +Env.DATABRICKS_SKILLS_REF = "test-ref" + +Ignore = [ + ".databricks/aitools/skills/.state.json", +] + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = [] + +[[Server]] +Pattern = "GET /test-ref/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-stable-a": {"version": "1.0.0", "files": ["SKILL.md"], "repo_dir": "skills"}, + "test-stable-b": {"version": "1.0.0", "files": ["SKILL.md"], "repo_dir": "skills"}, + "test-exp": {"version": "0.0.1", "files": ["SKILL.md"], "repo_dir": "experimental"} + } +} +''' + +[[Server]] +Pattern = "GET /test-ref/skills/test-stable-a/SKILL.md" +Response.Body = '''--- +name: test-stable-a +--- + +# A +''' + +[[Server]] +Pattern = "GET /test-ref/skills/test-stable-b/SKILL.md" +Response.Body = '''--- +name: test-stable-b +--- + +# B +''' + +[[Server]] +Pattern = "GET /test-ref/experimental/test-exp/SKILL.md" +Response.Body = '''--- +name: test-exp +--- + +# Exp +''' diff --git a/acceptance/experimental/aitools/skills/install/out.test.toml b/acceptance/experimental/aitools/skills/install/out.test.toml new file mode 100644 index 00000000000..d6187dcb046 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = [] diff --git a/acceptance/experimental/aitools/skills/install/output.txt b/acceptance/experimental/aitools/skills/install/output.txt new file mode 100644 index 00000000000..f659c9e2998 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install/output.txt @@ -0,0 +1,24 @@ + +=== stable-only install (no --experimental flag) installs 1 skill +>>> [CLI] experimental aitools install --global +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Installed 1 skill. + +=== re-run with --experimental installs the experimental one too +>>> [CLI] experimental aitools install --global --experimental +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Installed 2 skills. + +=== no-op re-run is idempotent (no new fetches, no errors) +>>> [CLI] experimental aitools install --global --experimental +Command "install" is deprecated, use "databricks aitools install" instead. +Flag --global has been deprecated, use --scope=global +Installing Databricks AI skills for Claude Code... +Using skills version test-ref +Installed 2 skills. diff --git a/acceptance/experimental/aitools/skills/install/script b/acceptance/experimental/aitools/skills/install/script new file mode 100644 index 00000000000..cc64fb54951 --- /dev/null +++ b/acceptance/experimental/aitools/skills/install/script @@ -0,0 +1,11 @@ +# Agent detection needs ~/.claude; prefer USERPROFILE on Windows. +mkdir -p "${USERPROFILE:-$HOME}/.claude" + +title "stable-only install (no --experimental flag) installs 1 skill" +trace $CLI experimental aitools install --global + +title "re-run with --experimental installs the experimental one too" +trace $CLI experimental aitools install --global --experimental + +title "no-op re-run is idempotent (no new fetches, no errors)" +trace $CLI experimental aitools install --global --experimental diff --git a/acceptance/experimental/aitools/skills/install/test.toml b/acceptance/experimental/aitools/skills/install/test.toml new file mode 100644 index 00000000000..f21efd9189a --- /dev/null +++ b/acceptance/experimental/aitools/skills/install/test.toml @@ -0,0 +1,52 @@ +# Mock server replaces raw.githubusercontent.com for manifest + skill files. +Env.DATABRICKS_SKILLS_BASE_URL = "$DATABRICKS_HOST" +Env.DATABRICKS_SKILLS_REF = "test-ref" + +Ignore = [ + ".databricks/aitools/skills/.state.json", +] + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = [] + +# Stable + experimental skills via repo_dir. +[[Server]] +Pattern = "GET /test-ref/manifest.json" +Response.Body = ''' +{ + "version": "2", + "updated_at": "2026-01-01T00:00:00Z", + "skills": { + "test-stable": { + "version": "1.0.0", + "description": "Stable test skill", + "files": ["SKILL.md"], + "repo_dir": "skills" + }, + "test-exp": { + "version": "0.0.1", + "description": "Experimental test skill", + "files": ["SKILL.md"], + "repo_dir": "experimental" + } + } +} +''' + +[[Server]] +Pattern = "GET /test-ref/skills/test-stable/SKILL.md" +Response.Body = '''--- +name: test-stable +--- + +# Test stable skill +''' + +[[Server]] +Pattern = "GET /test-ref/experimental/test-exp/SKILL.md" +Response.Body = '''--- +name: test-exp +--- + +# Test experimental skill +''' diff --git a/acceptance/help/output.txt b/acceptance/help/output.txt index d9f379f5bbf..453ec28b84b 100644 --- a/acceptance/help/output.txt +++ b/acceptance/help/output.txt @@ -36,24 +36,24 @@ Apps apps Apps run directly on a customer's Databricks instance, integrate with their data, use and extend Databricks services, and enable users to interact through single sign-on. Vector Search - vector-search-endpoints **Endpoint**: Represents the compute resources to host vector search indexes. + vector-search-endpoints **Endpoint**: Represents the compute resources to host AI Search indexes. vector-search-indexes **Index**: An efficient representation of your embedding vectors that supports real-time and efficient approximate nearest neighbor (ANN) search queries. Identity and Access Management - current-user This API allows retrieving information about currently authenticated user or service principal. + current-user *Public Preview* This API allows retrieving information about currently authenticated user or service principal. groups Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects. - groups-v2 Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects. + groups-v2 *Public Preview* Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects. permissions Permissions API are used to create read, write, edit, update and manage access for various users on different objects and endpoints. service-principals Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. - service-principals-v2 Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. + service-principals-v2 *Public Preview* Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. users User identities recognized by Databricks and represented by email addresses. - users-v2 User identities recognized by Databricks and represented by email addresses. - workspace-iam-v2 These APIs are used to manage identities and the workspace access of these identities in . + users-v2 *Public Preview* User identities recognized by Databricks and represented by email addresses. + workspace-iam-v2 *Beta* These APIs are used to manage identities and the workspace access of these identities in . Databricks SQL alerts The alerts API can be used to perform CRUD operations on alerts. - alerts-legacy The alerts API can be used to perform CRUD operations on alerts. - alerts-v2 New version of SQL Alerts. + alerts-legacy *Public Preview* The alerts API can be used to perform CRUD operations on alerts. + alerts-v2 *Public Preview* New version of SQL Alerts. dashboards In general, there is little need to modify dashboards using the API. data-sources This API is provided to assist you in making new query objects. queries The queries API can be used to perform CRUD operations on queries. @@ -72,33 +72,34 @@ Unity Catalog connections Connections allow for creating a connection to an external data source. credentials A credential represents an authentication and authorization mechanism for accessing services on your cloud tenant. entity-tag-assignments Tags are attributes that include keys and optional values that you can use to organize and categorize entities in Unity Catalog. - external-lineage External Lineage APIs enable defining and managing lineage relationships between Databricks objects and external systems. + external-lineage *Public Preview* External Lineage APIs enable defining and managing lineage relationships between Databricks objects and external systems. external-locations An external location is an object that combines a cloud storage path with a storage credential that authorizes access to the cloud storage path. - external-metadata External Metadata objects enable customers to register and manage metadata about external systems within Unity Catalog. + external-metadata *Public Preview* External Metadata objects enable customers to register and manage metadata about external systems within Unity Catalog. functions Functions implement User-Defined Functions (UDFs) in Unity Catalog. grants In Unity Catalog, data is secure by default. metastores A metastore is the top-level container of objects in Unity Catalog. model-versions Databricks provides a hosted version of MLflow Model Registry in Unity Catalog. - online-tables Online tables provide lower latency and higher QPS access to data from Delta tables. - policies Attribute-Based Access Control (ABAC) provides high leverage governance for enforcing compliance policies in Unity Catalog. + online-tables *Public Preview* Online tables provide lower latency and higher QPS access to data from Delta tables. + policies *Public Preview* Attribute-Based Access Control (ABAC) provides high leverage governance for enforcing compliance policies in Unity Catalog. quality-monitors Deprecated: Please use the Data Quality Monitors API instead (REST: /api/data-quality/v1/monitors), which manages both Data Profiling and Anomaly Detection. registered-models Databricks provides a hosted version of MLflow Model Registry in Unity Catalog. resource-quotas Unity Catalog enforces resource quotas on all securable objects, which limits the number of resources that can be created. - rfa Request for Access enables users to request access for Unity Catalog securables. + rfa *Public Preview* Request for Access enables users to request access for Unity Catalog securables. schemas A schema (also called a database) is the second layer of Unity Catalog’s three-level namespace. storage-credentials A storage credential represents an authentication and authorization mechanism for accessing data stored on your cloud tenant. - system-schemas A system schema is a schema that lives within the system catalog. + system-schemas *Public Preview* A system schema is a schema that lives within the system catalog. table-constraints Primary key and foreign key constraints encode relationships between fields in tables. tables A table resides in the third layer of Unity Catalog’s three-level namespace. - temporary-path-credentials Temporary Path Credentials refer to short-lived, downscoped credentials used to access external cloud storage locations registered in Databricks. - temporary-table-credentials Temporary Table Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where table data is stored in Databricks. + temporary-path-credentials *Public Preview* Temporary Path Credentials refer to short-lived, downscoped credentials used to access external cloud storage locations registered in Databricks. + temporary-table-credentials *Public Preview* Temporary Table Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where table data is stored in Databricks. + temporary-volume-credentials *Public Preview* Temporary Volume Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where volume data is stored in Databricks. volumes Volumes are a Unity Catalog (UC) capability for accessing, storing, governing, organizing and processing files. workspace-bindings A securable in Databricks can be configured as __OPEN__ or __ISOLATED__. Delta Sharing providers A data provider is an object representing the organization in the real world who shares the data. recipient-activation The Recipient Activation API is only applicable in the open sharing model where the recipient object has the authentication type of TOKEN. - recipient-federation-policies The Recipient Federation Policies APIs are only applicable in the open sharing model where the recipient object has the authentication type of OIDC_RECIPIENT, enabling data sharing from Databricks to non-Databricks recipients. + recipient-federation-policies *Public Preview* The Recipient Federation Policies APIs are only applicable in the open sharing model where the recipient object has the authentication type of OIDC_RECIPIENT, enabling data sharing from Databricks to non-Databricks recipients. recipients A recipient is an object you create using :method:recipients/create to represent an organization which you want to allow access shares. shares A share is a container instantiated with :method:shares/create. @@ -109,54 +110,55 @@ Settings token-management Enables administrators to get all tokens and delete tokens for other users. tokens The Token API allows you to create, list, and revoke tokens that can be used to authenticate and access Databricks REST APIs. workspace-conf This API allows updating known workspace settings for advanced users. - workspace-settings-v2 APIs to manage workspace level settings. + workspace-settings-v2 *Public Preview* APIs to manage workspace level settings. OAuth service-principal-secrets-proxy These APIs enable administrators to manage service principal secrets at the workspace level. Marketplace - consumer-fulfillments Fulfillments are entities that allow consumers to preview installations. - consumer-installations Installations are entities that allow consumers to interact with Databricks Marketplace listings. - consumer-listings Listings are the core entities in the Marketplace. - consumer-personalization-requests Personalization Requests allow customers to interact with the individualized Marketplace listing flow. - consumer-providers Providers are the entities that publish listings to the Marketplace. - provider-exchange-filters Marketplace exchanges filters curate which groups can access an exchange. - provider-exchanges Marketplace exchanges allow providers to share their listings with a curated set of customers. - provider-files Marketplace offers a set of file APIs for various purposes such as preview notebooks and provider icons. - provider-listings Listings are the core entities in the Marketplace. - provider-personalization-requests Personalization requests are an alternate to instantly available listings. - provider-provider-analytics-dashboards Manage templated analytics solution for providers. - provider-providers Providers are entities that manage assets in Marketplace. + consumer-fulfillments *Public Preview* Fulfillments are entities that allow consumers to preview installations. + consumer-installations *Public Preview* Installations are entities that allow consumers to interact with Databricks Marketplace listings. + consumer-listings *Public Preview* Listings are the core entities in the Marketplace. + consumer-personalization-requests *Public Preview* Personalization Requests allow customers to interact with the individualized Marketplace listing flow. + consumer-providers *Public Preview* Providers are the entities that publish listings to the Marketplace. + provider-exchange-filters *Public Preview* Marketplace exchanges filters curate which groups can access an exchange. + provider-exchanges *Public Preview* Marketplace exchanges allow providers to share their listings with a curated set of customers. + provider-files *Public Preview* Marketplace offers a set of file APIs for various purposes such as preview notebooks and provider icons. + provider-listings *Public Preview* Listings are the core entities in the Marketplace. + provider-personalization-requests *Public Preview* Personalization requests are an alternate to instantly available listings. + provider-provider-analytics-dashboards *Public Preview* Manage templated analytics solution for providers. + provider-providers *Public Preview* Providers are entities that manage assets in Marketplace. Clean Rooms - clean-room-asset-revisions Clean Room Asset Revisions denote new versions of uploaded assets (e.g. + clean-room-asset-revisions *Beta* Clean Room Asset Revisions denote new versions of uploaded assets (e.g. clean-room-assets Clean room assets are data and code objects — Tables, volumes, and notebooks that are shared with the clean room. - clean-room-auto-approval-rules Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g. + clean-room-auto-approval-rules *Beta* Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g. clean-room-task-runs Clean room task runs are the executions of notebooks in a clean room. clean-rooms A clean room uses Delta Sharing and serverless compute to provide a secure and privacy-protecting environment where multiple parties can work together on sensitive enterprise data without direct access to each other's data. Quality Monitor - quality-monitor-v2 Deprecated: Please use the Data Quality Monitoring API instead (REST: /api/data-quality/v1/monitors). + quality-monitor-v2 *Beta* Deprecated: Please use the Data Quality Monitoring API instead (REST: /api/data-quality/v1/monitors). Data Classification - data-classification Manage data classification for Unity Catalog catalogs. + data-classification *Beta* Manage data classification for Unity Catalog catalogs. Data Quality Monitoring - data-quality Manage the data quality of Unity Catalog objects (currently support schema and table). + data-quality *Public Preview* Manage the data quality of Unity Catalog objects (currently support schema and table). Database Instances - database Database Instances provide access to a database via REST API or direct SQL. + database *Public Preview* Database Instances provide access to a database via REST API or direct SQL. psql Connect to a Lakebase Postgres database Agent Bricks - knowledge-assistants Manage Knowledge Assistants and related resources. + knowledge-assistants *Beta* Manage Knowledge Assistants and related resources. + supervisor-agents *Beta* Manage Supervisor Agents and related resources. Tags tag-policies The Tag Policy API allows you to manage policies for governed tags in Databricks. - workspace-entity-tag-assignments Manage tag assignments on workspace-scoped objects. + workspace-entity-tag-assignments *Beta* Manage tag assignments on workspace-scoped objects. Postgres - postgres Use the Postgres API to create and manage Lakebase Autoscaling Postgres infrastructure, including projects, branches, compute endpoints, and roles. + postgres *Beta* Use the Postgres API to create and manage Lakebase Autoscaling Postgres infrastructure, including projects, branches, compute endpoints, and roles. Environments environments APIs to manage environment resources. @@ -167,6 +169,7 @@ Developer Tools Additional Commands: account Databricks Account Commands + aitools Databricks AI Tools for coding agents api Perform Databricks API call auth Authentication related commands cache Local cache related commands diff --git a/acceptance/internal/config.go b/acceptance/internal/config.go index 06ac61c39b8..77069297832 100644 --- a/acceptance/internal/config.go +++ b/acceptance/internal/config.go @@ -153,12 +153,6 @@ type ServerStub struct { // Configure as "1ms", "2s", "3m", etc. // See [time.ParseDuration] for details. Delay time.Duration - - // Number of times to kill the caller process before returning normal responses. - // 0 = never kill (default), 1 = kill once then allow, 2 = kill twice then allow, etc. - // Useful for testing crash recovery scenarios where first deploy crashes but retry succeeds. - // Requires DATABRICKS_CLI_TEST_PID=1 to be set in the test environment. - KillCaller int } // FindConfigs finds all the config relevant for this test, @@ -267,6 +261,10 @@ func DoLoadConfig(t *testing.T, path string) TestConfig { meta, err := toml.Decode(string(bytes), &config) require.NoError(t, err, "Failed to parse config %s", path) + if len(meta.Keys()) == 0 { + t.Fatalf("test.toml has no settings (delete it instead of leaving it empty): %s", path) + } + keys := meta.Undecoded() for ind, key := range keys { t.Errorf("Undecoded key in %s[%d]: %#v", path, ind, key) diff --git a/acceptance/internal/prepare_server.go b/acceptance/internal/prepare_server.go index 702b4e145ef..4ed3715d77f 100644 --- a/acceptance/internal/prepare_server.go +++ b/acceptance/internal/prepare_server.go @@ -85,7 +85,7 @@ func PrepareServerAndClient(t *testing.T, config TestConfig, logRequests bool, o w, err := databricks.NewWorkspaceClient() require.NoError(t, err) - user, err := w.CurrentUser.Me(t.Context()) + user, err := w.CurrentUser.Me(t.Context(), iam.MeRequest{}) require.NoError(t, err, "Failed to get current user") cfg := w.Config @@ -183,23 +183,14 @@ func startLocalServer(t *testing.T, s.ResponseCallback = logResponseCallback(t) } - // Track remaining kill counts per pattern (for KillCaller > 0) - killCounters := make(map[string]int) - 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, " ") require.Len(t, items, 2) - // Initialize kill counter for this pattern - if stub.KillCaller > 0 { - killCounters[stub.Pattern] = stub.KillCaller - } - s.Handle(items[0], items[1], func(req testserver.Request) any { if stub.Delay > 0 { ctx := req.Context @@ -218,58 +209,16 @@ func startLocalServer(t *testing.T, } } - if shouldKillCaller(stub, killCounters, killCountersMu) { - killCaller(t, stub.Pattern, req.Headers) - } - return stub.Response }) } - // 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 } -func shouldKillCaller(stub ServerStub, killCounters map[string]int, mu *sync.Mutex) bool { - if stub.KillCaller <= 0 { - return false - } - mu.Lock() - defer mu.Unlock() - if killCounters[stub.Pattern] <= 0 { - return false - } - killCounters[stub.Pattern]-- - return true -} - -func killCaller(t *testing.T, pattern string, headers http.Header) { - pid := testserver.ExtractPidFromHeaders(headers) - if pid == 0 { - t.Errorf("KillCaller configured but test-pid not found in User-Agent") - return - } - - process, err := os.FindProcess(pid) - if err != nil { - t.Errorf("Failed to find process %d: %s", pid, err) - return - } - - // Use process.Kill() for cross-platform compatibility. - // On Unix, this sends SIGKILL. On Windows, this calls TerminateProcess. - if err := process.Kill(); err != nil { - t.Errorf("Failed to kill process %d: %s", pid, err) - return - } - - if !waitForProcessExit(pid, 2*time.Second) { - t.Logf("KillCaller: timed out waiting for PID %d to exit (pattern: %s)", pid, pattern) - } - t.Logf("KillCaller: killed PID %d (pattern: %s)", pid, pattern) -} - func startProxyServer(t *testing.T, recordRequests bool, logRequests bool, diff --git a/acceptance/panic/test.toml b/acceptance/panic/test.toml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance/pipelines/databricks-cli-help/output.txt b/acceptance/pipelines/databricks-cli-help/output.txt index 9043af0f127..f558ed25192 100644 --- a/acceptance/pipelines/databricks-cli-help/output.txt +++ b/acceptance/pipelines/databricks-cli-help/output.txt @@ -31,7 +31,7 @@ Available Commands stop Stop a pipeline Management Commands - apply-environment Apply the latest environment to the pipeline. + apply-environment *Public Preview* 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/output.txt b/acceptance/pipelines/deploy/auto-approve/output.txt index ff5ac099bfd..971748bbaf0 100644 --- a/acceptance/pipelines/deploy/auto-approve/output.txt +++ b/acceptance/pipelines/deploy/auto-approve/output.txt @@ -18,7 +18,9 @@ Streaming Tables (STs) and Materialized Views (MVs) managed by them. Recreating restore the defined STs and MVs through full refresh. Note that recreation is necessary when pipeline properties such as the 'catalog' or 'storage' are changed: delete resources.pipelines.foo -Error: the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed +Error: the deployment requires destructive actions, but the current console does not support prompting. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve after reviewing the plan above. Exit code: 1 diff --git a/acceptance/pipelines/deploy/force-lock/output.txt b/acceptance/pipelines/deploy/force-lock/output.txt index e5fdadbaf9d..b1092d3f523 100644 --- a/acceptance/pipelines/deploy/force-lock/output.txt +++ b/acceptance/pipelines/deploy/force-lock/output.txt @@ -4,8 +4,12 @@ === test deployment without force-lock (should fail) >>> errcode [CLI] pipelines deploy -Error: Failed to acquire deployment lock: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. Use --force-lock to override -Error: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. Use --force-lock to override +Error: Failed to acquire deployment lock: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. +Another deployment may be in progress. Use --force-lock to override, but this may +conflict with the other deployment if it is still active +Error: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. +Another deployment may be in progress. Use --force-lock to override, but this may +conflict with the other deployment if it is still active Exit code: 1 diff --git a/acceptance/pipelines/destroy/auto-approve/output.txt b/acceptance/pipelines/destroy/auto-approve/output.txt index 17aecc20592..3a59e2ffc2f 100644 --- a/acceptance/pipelines/destroy/auto-approve/output.txt +++ b/acceptance/pipelines/destroy/auto-approve/output.txt @@ -8,7 +8,9 @@ Deployment complete! View your pipeline my_pipeline here: [DATABRICKS_URL]/pipelines/[UUID]?o=[NUMID] >>> errcode [CLI] pipelines destroy -Error: please specify --auto-approve since terminal does not support interactive prompts +Error: this command will destroy all resources deployed by this bundle, including workspace files in the deployment directory. +Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed. +To proceed, use --auto-approve. Exit code: 1 diff --git a/acceptance/pipelines/destroy/force-lock/output.txt b/acceptance/pipelines/destroy/force-lock/output.txt index 5b6449f7322..5579565b06f 100644 --- a/acceptance/pipelines/destroy/force-lock/output.txt +++ b/acceptance/pipelines/destroy/force-lock/output.txt @@ -11,8 +11,12 @@ View your pipeline foo here: [DATABRICKS_URL]/pipelines/[UUID]?o=[NUMID] === test deployment without force-lock (should fail) >>> errcode [CLI] pipelines destroy --auto-approve -Error: Failed to acquire deployment lock: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. Use --force-lock to override -Error: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. Use --force-lock to override +Error: Failed to acquire deployment lock: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. +Another deployment may be in progress. Use --force-lock to override, but this may +conflict with the other deployment if it is still active +Error: deploy lock acquired by user-with-lock@databricks.com at [TIMESTAMP] +0000 UTC. +Another deployment may be in progress. Use --force-lock to override, but this may +conflict with the other deployment if it is still active Exit code: 1 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/e2e/test.toml b/acceptance/pipelines/e2e/test.toml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance/script.prepare b/acceptance/script.prepare index b158ca3c74e..3d0d59dc14d 100644 --- a/acceptance/script.prepare +++ b/acceptance/script.prepare @@ -1,3 +1,9 @@ +# Force plaintext token storage so acceptance tests exercise the file-backed +# cache rather than the OS keyring, which is not reliably reachable in CI. +# Tests that want to exercise the secure path or the resolver default unset +# or override this in their own script.prepare or script. +export DATABRICKS_AUTH_STORAGE=plaintext + errcode() { # Temporarily disable 'set -e' to prevent the script from exiting on error set +e @@ -101,6 +107,25 @@ sethome() { local home="$1" mkdir -p "$home" + # Resolve to an absolute path so HOME (and USERPROFILE) keep pointing at + # the same directory after the test cd's elsewhere. The SDK expands + # `~/.databrickscfg` against $HOME at lookup time; with a relative path + # the cfg silently disappears once the cwd changes, and helpers like + # databrickscfg.GetConfiguredDefaultProfile return "" instead of + # erroring. + # + # On Git Bash (MSYS), plain `pwd` returns Unix-style /c/... paths that + # the native Windows Go binary can't open; `pwd -W` returns the mixed + # C:/... form instead. Use the same form for both HOME and USERPROFILE + # so tests that compare a `$HOME`-based placeholder against the CLI's + # `os.UserHomeDir()` output (which reads USERPROFILE on Windows) find + # a match. + if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then + home="$(cd "$home" && pwd -W)" + else + home="$(cd "$home" && pwd)" + fi + # For macOS and Linux, use HOME. export HOME="$home" 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/kill_caller/currentuser/script b/acceptance/selftest/kill_caller/currentuser/script index 821c42d8cf7..dbd96b12a94 100644 --- a/acceptance/selftest/kill_caller/currentuser/script +++ b/acceptance/selftest/kill_caller/currentuser/script @@ -1,2 +1,4 @@ +# Kill the CLI when it calls /Me endpoint (once, then allow) +kill_after.py "GET /api/2.0/preview/scim/v2/Me" 0 1 trace errcode $CLI current-user me echo "Script continued after kill" diff --git a/acceptance/selftest/kill_caller/currentuser/test.toml b/acceptance/selftest/kill_caller/currentuser/test.toml deleted file mode 100644 index b76fe401fcb..00000000000 --- a/acceptance/selftest/kill_caller/currentuser/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -# Kill the CLI when it calls /Me endpoint (once, then allow) -[[Server]] -Pattern = "GET /api/2.0/preview/scim/v2/Me" -KillCaller = 1 diff --git a/acceptance/selftest/kill_caller/multi_pattern/output.txt b/acceptance/selftest/kill_caller/multi_pattern/output.txt index 2c080b2c349..b3528428352 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": "[USERID]", + "userName": "[USERNAME]" } Me attempt 3 done - success! diff --git a/acceptance/selftest/kill_caller/multi_pattern/script b/acceptance/selftest/kill_caller/multi_pattern/script index ba9447a29a7..e0b5523c45c 100644 --- a/acceptance/selftest/kill_caller/multi_pattern/script +++ b/acceptance/selftest/kill_caller/multi_pattern/script @@ -1,3 +1,6 @@ +kill_after.py "GET /api/2.0/preview/scim/v2/Me" 0 2 +kill_after.py "GET /api/2.0/workspace/list" 0 1 + # Test pattern 1: /Me endpoint (kills first 2, then allows) trace errcode $CLI current-user me echo "Me attempt 1 done" diff --git a/acceptance/selftest/kill_caller/multi_pattern/test.toml b/acceptance/selftest/kill_caller/multi_pattern/test.toml index 08bdc17085d..4565475423d 100644 --- a/acceptance/selftest/kill_caller/multi_pattern/test.toml +++ b/acceptance/selftest/kill_caller/multi_pattern/test.toml @@ -1,17 +1,5 @@ -# Test that multiple patterns can have independent KillCaller counts -# Pattern 1: Kill first 2 requests to /Me endpoint -# Pattern 2: Kill first 1 request to /workspace/list endpoint - -[[Server]] -Pattern = "GET /api/2.0/preview/scim/v2/Me" -KillCaller = 2 -Response.Body = ''' -{ - "id": "123", - "userName": "test@example.com" -} -''' +# Test that multiple patterns can have independent kill counts [[Server]] Pattern = "GET /api/2.0/workspace/list" -KillCaller = 1 +Response.Body = '{"objects": []}' diff --git a/acceptance/selftest/kill_caller/multiple/output.txt b/acceptance/selftest/kill_caller/multiple/output.txt index 538672bf865..3b6aea849fd 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": "[USERID]", + "userName": "[USERNAME]" } Attempt 4 done - success! diff --git a/acceptance/selftest/kill_caller/multiple/script b/acceptance/selftest/kill_caller/multiple/script index 03628e203ed..1e089f3cc0f 100644 --- a/acceptance/selftest/kill_caller/multiple/script +++ b/acceptance/selftest/kill_caller/multiple/script @@ -1,3 +1,6 @@ +# Kill the CLI 3 times, then allow the 4th request to succeed +kill_after.py "GET /api/2.0/preview/scim/v2/Me" 0 3 + # First 3 attempts should be killed trace errcode $CLI current-user me echo "Attempt 1 done" diff --git a/acceptance/selftest/kill_caller/multiple/test.toml b/acceptance/selftest/kill_caller/multiple/test.toml deleted file mode 100644 index 5485fc6a6bb..00000000000 --- a/acceptance/selftest/kill_caller/multiple/test.toml +++ /dev/null @@ -1,10 +0,0 @@ -# Kill the CLI 3 times, then allow the 4th request to succeed -[[Server]] -Pattern = "GET /api/2.0/preview/scim/v2/Me" -KillCaller = 3 -Response.Body = ''' -{ - "id": "123", - "userName": "test@example.com" -} -''' diff --git a/acceptance/selftest/kill_caller/offset/out.test.toml b/acceptance/selftest/kill_caller/offset/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/selftest/kill_caller/offset/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/selftest/kill_caller/offset/output.txt b/acceptance/selftest/kill_caller/offset/output.txt new file mode 100644 index 00000000000..b6959aec5e2 --- /dev/null +++ b/acceptance/selftest/kill_caller/offset/output.txt @@ -0,0 +1,33 @@ + +>>> [CLI] current-user me +{ + "id": "[USERID]", + "userName": "[USERNAME]" +} +Attempt 1 done - success (offset) + +>>> [CLI] current-user me +{ + "id": "[USERID]", + "userName": "[USERNAME]" +} +Attempt 2 done - success (offset) + +>>> errcode [CLI] current-user me +[PROCESS_KILLED] + +Exit code: [KILLED] +Attempt 3 done - killed + +>>> errcode [CLI] current-user me +[PROCESS_KILLED] + +Exit code: [KILLED] +Attempt 4 done - killed + +>>> [CLI] current-user me +{ + "id": "[USERID]", + "userName": "[USERNAME]" +} +Attempt 5 done - success (past kill window) diff --git a/acceptance/selftest/kill_caller/offset/script b/acceptance/selftest/kill_caller/offset/script new file mode 100644 index 00000000000..6abee0dcac7 --- /dev/null +++ b/acceptance/selftest/kill_caller/offset/script @@ -0,0 +1,20 @@ +# Let first 2 requests pass, kill next 2, then allow rest +kill_after.py "GET /api/2.0/preview/scim/v2/Me" 2 2 + +# First 2 attempts should succeed (offset period) +trace $CLI current-user me +echo "Attempt 1 done - success (offset)" + +trace $CLI current-user me +echo "Attempt 2 done - success (offset)" + +# Attempts 3-4 should be killed +trace errcode $CLI current-user me +echo "Attempt 3 done - killed" + +trace errcode $CLI current-user me +echo "Attempt 4 done - killed" + +# Attempt 5 should succeed again +trace $CLI current-user me +echo "Attempt 5 done - success (past kill window)" diff --git a/acceptance/selftest/kill_caller/workspace/script b/acceptance/selftest/kill_caller/workspace/script index 076972136c9..5a21881ab3f 100644 --- a/acceptance/selftest/kill_caller/workspace/script +++ b/acceptance/selftest/kill_caller/workspace/script @@ -1,2 +1,4 @@ +# Kill the CLI when it calls workspace list endpoint (once, then allow) +kill_after.py "GET /api/2.0/workspace/list" 0 1 trace errcode $CLI workspace list / echo "Script continued after kill" diff --git a/acceptance/selftest/kill_caller/workspace/test.toml b/acceptance/selftest/kill_caller/workspace/test.toml deleted file mode 100644 index eac10a6329b..00000000000 --- a/acceptance/selftest/kill_caller/workspace/test.toml +++ /dev/null @@ -1,4 +0,0 @@ -# Kill the CLI when it calls workspace list endpoint (once, then allow) -[[Server]] -Pattern = "GET /api/2.0/workspace/list" -KillCaller = 1 diff --git a/acceptance/test.toml b/acceptance/test.toml index a482f492603..e3c4898f8a5 100644 --- a/acceptance/test.toml +++ b/acceptance/test.toml @@ -17,15 +17,6 @@ Env.PYTHONDONTWRITEBYTECODE = "1" Env.PYTHONUNBUFFERED = "1" Env.PYTHONUTF8 = "1" -# Clear agent env vars to prevent them from affecting test output -Env.ANTIGRAVITY_AGENT = "" -Env.CLAUDECODE = "" -Env.CLINE_ACTIVE = "" -Env.CODEX_CI = "" -Env.CURSOR_AGENT = "" -Env.GEMINI_CLI = "" -Env.OPENCODE = "" - EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] EnvMatrixExclude.noplantf = ["DATABRICKS_BUNDLE_ENGINE=terraform", "READPLAN=1"] EnvRepl.DATABRICKS_BUNDLE_ENGINE = false 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/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/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/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/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/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/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/populate_current_user.go b/bundle/config/mutator/populate_current_user.go index 2a3cc492829..b1ec81b33e8 100644 --- a/bundle/config/mutator/populate_current_user.go +++ b/bundle/config/mutator/populate_current_user.go @@ -31,7 +31,7 @@ func (m *populateCurrentUser) Apply(ctx context.Context, b *bundle.Bundle) diag. fingerprint := b.GetUserFingerprint(ctx) me, err := cache.GetOrCompute(ctx, b.Cache, fingerprint, func(ctx context.Context) (*iam.User, error) { - return w.CurrentUser.Me(ctx) + return w.CurrentUser.Me(ctx, iam.MeRequest{}) }) if err != nil { return diag.FromErr(err) 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 5ea8868b170..cf81da5f78c 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -20,6 +20,7 @@ import ( "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" ) @@ -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/resourcemutator/apply_bundle_permissions_test.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go index e472241f282..d3fbbbc2cf0 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go @@ -28,6 +28,8 @@ var unsupportedResources = []string{ "synced_database_tables", "postgres_branches", "postgres_endpoints", + "postgres_catalogs", + "postgres_synced_tables", } func TestApplyBundlePermissions(t *testing.T) { diff --git a/bundle/config/mutator/resourcemutator/apply_presets.go b/bundle/config/mutator/resourcemutator/apply_presets.go index d5c97266cdc..83d512ca518 100644 --- a/bundle/config/mutator/resourcemutator/apply_presets.go +++ b/bundle/config/mutator/resourcemutator/apply_presets.go @@ -290,13 +290,9 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos } } - // Vector Search Endpoints: Prefix - for _, e := range r.VectorSearchEndpoints { - if e == nil { - continue - } - e.Name = normalizePrefix(prefix) + e.Name - } + // 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 } diff --git a/bundle/config/mutator/resourcemutator/apply_target_mode_test.go b/bundle/config/mutator/resourcemutator/apply_target_mode_test.go index 84f2acf781b..ab6a80f5e7e 100644 --- a/bundle/config/mutator/resourcemutator/apply_target_mode_test.go +++ b/bundle/config/mutator/resourcemutator/apply_target_mode_test.go @@ -247,6 +247,20 @@ func mockBundle(mode config.Mode) *bundle.Bundle { }, }, }, + PostgresCatalogs: map[string]*resources.PostgresCatalog{ + "postgres_catalog1": { + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "postgres_catalog_1", + }, + }, + }, + PostgresSyncedTables: map[string]*resources.PostgresSyncedTable{ + "postgres_synced_table1": { + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "catalog.schema.table1", + }, + }, + }, VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ "vs_endpoint1": { CreateEndpoint: vectorsearch.CreateEndpoint{ @@ -303,8 +317,8 @@ 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 - assert.Equal(t, "dev_lennart_vs_endpoint1", b.Config.Resources.VectorSearchEndpoints["vs_endpoint1"].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) @@ -414,17 +428,35 @@ 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{ + 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", + "PostgresCatalogs", + "PostgresSyncedTables", } diags := bundle.ApplySeq(t.Context(), b, ApplyTargetMode(), ApplyPresets()) @@ -433,28 +465,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_test.go b/bundle/config/mutator/resourcemutator/capture_uc_dependencies_test.go index 12aac0c472e..a9a3a626268 100644 --- a/bundle/config/mutator/resourcemutator/capture_uc_dependencies_test.go +++ b/bundle/config/mutator/resourcemutator/capture_uc_dependencies_test.go @@ -239,6 +239,7 @@ func TestCaptureUCDependenciesModelServingEndpointEdgeCases(t *testing.T) { // AutoCaptureConfig path. "auto_capture": {CreateServingEndpoint: serving.CreateServingEndpoint{ Config: &serving.EndpointCoreConfigInput{ + //nolint:staticcheck // SA1019: deprecated AutoCaptureConfigInput kept for bundle config compatibility AutoCaptureConfig: &serving.AutoCaptureConfigInput{ CatalogName: "mycatalog", SchemaName: "myschema", }, diff --git a/bundle/config/mutator/resourcemutator/run_as_test.go b/bundle/config/mutator/resourcemutator/run_as_test.go index 0b7003f5873..e80edd6711f 100644 --- a/bundle/config/mutator/resourcemutator/run_as_test.go +++ b/bundle/config/mutator/resourcemutator/run_as_test.go @@ -46,8 +46,10 @@ func allResourceTypes(t *testing.T) []string { "models", "pipelines", "postgres_branches", + "postgres_catalogs", "postgres_endpoints", "postgres_projects", + "postgres_synced_tables", "quality_monitors", "registered_models", "schemas", @@ -174,8 +176,10 @@ var allowList = []string{ "pipelines", "models", "postgres_branches", + "postgres_catalogs", "postgres_endpoints", "postgres_projects", + "postgres_synced_tables", "registered_models", "experiments", "schemas", diff --git a/bundle/config/mutator/validate_direct_only_resources.go b/bundle/config/mutator/validate_direct_only_resources.go index 5717497205b..a76feb9482f 100644 --- a/bundle/config/mutator/validate_direct_only_resources.go +++ b/bundle/config/mutator/validate_direct_only_resources.go @@ -6,56 +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 - }, - }, - { - resourceType: "vector_search_endpoints", - pluralName: "Vector Search Endpoint", - singularName: "vector search endpoint", - getResources: func(b *bundle.Bundle) map[string]any { - result := make(map[string]any) - for k, v := range b.Config.Resources.VectorSearchEndpoints { - result[k] = v - } - return result - }, - }, -} - type validateDirectOnlyResources struct { engine engine.EngineType } @@ -70,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' or set 'bundle.engine: direct' in your databricks.yml 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..37c5b679cf4 --- /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' or set 'bundle.engine: direct' in your databricks.yml 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' or set 'bundle.engine: direct' in your databricks.yml 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' or set 'bundle.engine: direct' in your databricks.yml 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/mutator/validate_git_details.go b/bundle/config/mutator/validate_git_details.go index 69a4221fdcb..bb25a198093 100644 --- a/bundle/config/mutator/validate_git_details.go +++ b/bundle/config/mutator/validate_git_details.go @@ -4,6 +4,7 @@ import ( "context" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/diag" ) @@ -23,7 +24,8 @@ func (m *validateGitDetails) Apply(ctx context.Context, b *bundle.Bundle) diag.D } if b.Config.Bundle.Git.Branch != b.Config.Bundle.Git.ActualBranch && !b.Config.Bundle.Force { - return diag.Errorf("not on the right Git branch:\n expected according to configuration: %s\n actual: %s\nuse --force to override", b.Config.Bundle.Git.Branch, b.Config.Bundle.Git.ActualBranch) + return diag.Errorf("not on the right Git branch:\n expected according to configuration: %s\n actual: %s\nTo deploy from this branch anyway, use --force. Note that this may push unexpected code to the target.%s", + b.Config.Bundle.Git.Branch, b.Config.Bundle.Git.ActualBranch, agent.AgentNotice()) } return nil } diff --git a/bundle/config/mutator/validate_git_details_test.go b/bundle/config/mutator/validate_git_details_test.go index b29f221edc1..b7eaad9aeb6 100644 --- a/bundle/config/mutator/validate_git_details_test.go +++ b/bundle/config/mutator/validate_git_details_test.go @@ -40,8 +40,9 @@ func TestValidateGitDetailsNonMatchingBranches(t *testing.T) { m := ValidateGitDetails() diags := bundle.Apply(t.Context(), b, m) - expectedError := "not on the right Git branch:\n expected according to configuration: main\n actual: feature\nuse --force to override" - assert.EqualError(t, diags.Error(), expectedError) + err := diags.Error() + assert.ErrorContains(t, err, "not on the right Git branch:\n expected according to configuration: main\n actual: feature") + assert.ErrorContains(t, err, "To deploy from this branch anyway, use --force. Note that this may push unexpected code to the target.") } func TestValidateGitDetailsNotUsingGit(t *testing.T) { diff --git a/bundle/config/resources.go b/bundle/config/resources.go index 225ec32165d..636186ddb7a 100644 --- a/bundle/config/resources.go +++ b/bundle/config/resources.go @@ -35,6 +35,8 @@ 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"` + PostgresCatalogs map[string]*resources.PostgresCatalog `json:"postgres_catalogs,omitempty"` + PostgresSyncedTables map[string]*resources.PostgresSyncedTable `json:"postgres_synced_tables,omitempty"` VectorSearchEndpoints map[string]*resources.VectorSearchEndpoint `json:"vector_search_endpoints,omitempty"` } @@ -112,6 +114,8 @@ 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["postgres_catalogs"], r.PostgresCatalogs), + collectResourceMap(descriptions["postgres_synced_tables"], r.PostgresSyncedTables), collectResourceMap(descriptions["vector_search_endpoints"], r.VectorSearchEndpoints), } } @@ -167,6 +171,8 @@ func SupportedResources() map[string]resources.ResourceDescription { "postgres_projects": (&resources.PostgresProject{}).ResourceDescription(), "postgres_branches": (&resources.PostgresBranch{}).ResourceDescription(), "postgres_endpoints": (&resources.PostgresEndpoint{}).ResourceDescription(), + "postgres_catalogs": (&resources.PostgresCatalog{}).ResourceDescription(), + "postgres_synced_tables": (&resources.PostgresSyncedTable{}).ResourceDescription(), "vector_search_endpoints": (&resources.VectorSearchEndpoint{}).ResourceDescription(), } } diff --git a/bundle/config/resources/dashboard_test.go b/bundle/config/resources/dashboard_test.go index c010d949157..83c80b17107 100644 --- a/bundle/config/resources/dashboard_test.go +++ b/bundle/config/resources/dashboard_test.go @@ -29,8 +29,7 @@ func TestDashboardConfigIsSupersetOfSDKDashboard(t *testing.T) { // Create a map of SDK fields by name and their JSON tags sdkFields := make(map[string]string) - for i := range sdkType.NumField() { - field := sdkType.Field(i) + for field := range sdkType.Fields() { jsonTag := field.Tag.Get("json") jsonName := getJSONTagName(jsonTag) if jsonName != "" { @@ -40,8 +39,7 @@ func TestDashboardConfigIsSupersetOfSDKDashboard(t *testing.T) { // Create a map of config fields by name and their JSON tags configFields := make(map[string]string) - for i := range configType.NumField() { - field := configType.Field(i) + for field := range configType.Fields() { jsonTag := field.Tag.Get("json") jsonName := getJSONTagName(jsonTag) if jsonName != "" { diff --git a/bundle/config/resources/postgres_branch.go b/bundle/config/resources/postgres_branch.go index dd6e9daacdf..a5d093ca194 100644 --- a/bundle/config/resources/postgres_branch.go +++ b/bundle/config/resources/postgres_branch.go @@ -19,6 +19,11 @@ type PostgresBranchConfig struct { // Parent is the project containing this branch. Format: "projects/{project_id}" Parent string `json:"parent"` + + // ReplaceExisting, when true, takes over an existing branch with the same ID + // instead of returning ALREADY_EXISTS. Used to manage the implicitly-created + // production branch of a new project. Input-only: not returned by the GET API. + ReplaceExisting bool `json:"replace_existing,omitempty"` } func (c *PostgresBranchConfig) UnmarshalJSON(b []byte) error { diff --git a/bundle/config/resources/postgres_catalog.go b/bundle/config/resources/postgres_catalog.go new file mode 100644 index 00000000000..c17ffaa1682 --- /dev/null +++ b/bundle/config/resources/postgres_catalog.go @@ -0,0 +1,66 @@ +package resources + +import ( + "context" + "net/url" + + "github.com/databricks/cli/libs/log" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/marshal" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +type PostgresCatalogConfig struct { + postgres.CatalogCatalogSpec + + // CatalogId is the user-specified UC catalog name. Becomes the trailing + // component of the server-assigned Name: "catalogs/{catalog_id}". + CatalogId string `json:"catalog_id"` +} + +func (c *PostgresCatalogConfig) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, c) +} + +func (c *PostgresCatalogConfig) MarshalJSON() ([]byte, error) { + return marshal.Marshal(c) +} + +type PostgresCatalog struct { + BaseResource + PostgresCatalogConfig +} + +func (c *PostgresCatalog) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) { + _, err := w.Postgres.GetCatalog(ctx, postgres.GetCatalogRequest{Name: name}) + if err != nil { + log.Debugf(ctx, "postgres catalog %s does not exist", name) + return false, err + } + return true, nil +} + +func (c *PostgresCatalog) ResourceDescription() ResourceDescription { + return ResourceDescription{ + SingularName: "postgres_catalog", + PluralName: "postgres_catalogs", + SingularTitle: "Postgres catalog", + PluralTitle: "Postgres catalogs", + } +} + +func (c *PostgresCatalog) GetName() string { + return c.CatalogId +} + +func (c *PostgresCatalog) GetURL() string { + return c.URL +} + +func (c *PostgresCatalog) InitializeURL(baseURL url.URL) { + if c.CatalogId == "" { + return + } + baseURL.Path = "explore/data/" + c.CatalogId + c.URL = baseURL.String() +} diff --git a/bundle/config/resources/postgres_endpoint.go b/bundle/config/resources/postgres_endpoint.go index 6dfe4fe7f33..1a9ce34665c 100644 --- a/bundle/config/resources/postgres_endpoint.go +++ b/bundle/config/resources/postgres_endpoint.go @@ -19,6 +19,11 @@ type PostgresEndpointConfig struct { // Parent is the branch containing this endpoint. Format: "projects/{project_id}/branches/{branch_id}" Parent string `json:"parent"` + + // ReplaceExisting, when true, takes over an existing endpoint with the same ID + // instead of returning ALREADY_EXISTS. Used to manage the implicitly-created + // primary read-write endpoint of a new branch. Input-only: not returned by the GET API. + ReplaceExisting bool `json:"replace_existing,omitempty"` } func (c *PostgresEndpointConfig) UnmarshalJSON(b []byte) error { diff --git a/bundle/config/resources/postgres_synced_table.go b/bundle/config/resources/postgres_synced_table.go new file mode 100644 index 00000000000..d4c665f6e3c --- /dev/null +++ b/bundle/config/resources/postgres_synced_table.go @@ -0,0 +1,82 @@ +package resources + +import ( + "context" + "net/url" + "strings" + + "github.com/databricks/cli/libs/log" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/marshal" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +type PostgresSyncedTableConfig struct { + postgres.SyncedTableSyncedTableSpec + + // SyncedTableId is the user-specified three-part UC name (catalog.schema.table). + // Becomes the trailing component of the server-assigned Name: + // "synced_tables/{synced_table_id}". + SyncedTableId string `json:"synced_table_id"` +} + +func (c *PostgresSyncedTableConfig) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, c) +} + +func (c *PostgresSyncedTableConfig) MarshalJSON() ([]byte, error) { + return marshal.Marshal(c) +} + +type PostgresSyncedTable struct { + BaseResource + PostgresSyncedTableConfig +} + +func (s *PostgresSyncedTable) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) { + _, err := w.Postgres.GetSyncedTable(ctx, postgres.GetSyncedTableRequest{Name: name}) + if err != nil { + log.Debugf(ctx, "postgres synced table %s does not exist", name) + return false, err + } + return true, nil +} + +func (s *PostgresSyncedTable) ResourceDescription() ResourceDescription { + return ResourceDescription{ + SingularName: "postgres_synced_table", + PluralName: "postgres_synced_tables", + SingularTitle: "Postgres synced table", + PluralTitle: "Postgres synced tables", + } +} + +func (s *PostgresSyncedTable) GetName() string { + // Synced tables don't expose a display name distinct from their three-part + // id, so the id IS the name. Prefer the post-deploy ID + // ("synced_tables/{catalog}.{schema}.{table}") so bundle summary renders + // the resolved id even when SyncedTableId still has unresolved + // cross-resource references like ${resources.X.Y.Z}. + if id, ok := strings.CutPrefix(s.ID, "synced_tables/"); ok { + return id + } + return s.SyncedTableId +} + +func (s *PostgresSyncedTable) GetURL() string { + return s.URL +} + +func (s *PostgresSyncedTable) InitializeURL(baseURL url.URL) { + // UC explore expects /{catalog}/{schema}/{table}, not a single dotted segment. + catalog, rest, ok := strings.Cut(s.GetName(), ".") + if !ok { + return + } + schema, table, ok := strings.Cut(rest, ".") + if !ok { + return + } + baseURL.Path = "explore/data/" + catalog + "/" + schema + "/" + table + s.URL = baseURL.String() +} diff --git a/bundle/config/resources_test.go b/bundle/config/resources_test.go index 943b279a288..c0e714d5345 100644 --- a/bundle/config/resources_test.go +++ b/bundle/config/resources_test.go @@ -53,15 +53,13 @@ import ( func TestCustomMarshallerIsImplemented(t *testing.T) { rt := reflect.TypeFor[Resources]() - for i := range rt.NumField() { - field := rt.Field(i) - + for field := range rt.Fields() { // Fields in Resources are expected be of the form map[string]*resourceStruct assert.Equal(t, reflect.Map, field.Type.Kind(), "Resource %s is not a map", field.Name) kt := field.Type.Key() assert.Equal(t, reflect.String, kt.Kind(), "Resource %s is not a map with string keys", field.Name) vt := field.Type.Elem() - assert.Equal(t, reflect.Ptr, vt.Kind(), "Resource %s is not a map with pointer values", field.Name) + assert.Equal(t, reflect.Pointer, vt.Kind(), "Resource %s is not a map with pointer values", field.Name) // Marshalling a resourceStruct will panic if resourceStruct does not have a custom marshaller // This is because resourceStruct embeds a Go SDK struct that implements @@ -95,8 +93,7 @@ func TestResourcesAllResourcesCompleteness(t *testing.T) { types = append(types, group.Description.PluralName) } - for i := range rt.NumField() { - field := rt.Field(i) + for field := range rt.Fields() { jsonTag := field.Tag.Get("json") if idx := strings.Index(jsonTag, ","); idx != -1 { @@ -112,8 +109,7 @@ func TestSupportedResources(t *testing.T) { actual := SupportedResources() typ := reflect.TypeFor[Resources]() - for i := range typ.NumField() { - field := typ.Field(i) + for field := range typ.Fields() { jsonTags := strings.Split(field.Tag.Get("json"), ",") pluralName := jsonTags[0] assert.Equal(t, actual[pluralName].PluralName, pluralName) @@ -273,6 +269,20 @@ func TestResourcesBindSupport(t *testing.T) { }, }, }, + PostgresCatalogs: map[string]*resources.PostgresCatalog{ + "my_postgres_catalog": { + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "my_postgres_catalog", + }, + }, + }, + PostgresSyncedTables: map[string]*resources.PostgresSyncedTable{ + "my_postgres_synced_table": { + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "catalog.schema.my_postgres_synced_table", + }, + }, + }, VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ "my_vector_search_endpoint": { CreateEndpoint: vectorsearch.CreateEndpoint{ @@ -312,6 +322,8 @@ 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.GetMockPostgresAPI().EXPECT().GetCatalog(mock.Anything, mock.Anything).Return(nil, nil) + m.GetMockPostgresAPI().EXPECT().GetSyncedTable(mock.Anything, mock.Anything).Return(nil, nil) m.GetMockVectorSearchEndpointsAPI().EXPECT().GetEndpoint(mock.Anything, mock.Anything).Return(nil, nil) allResources := supportedResources.AllResources() diff --git a/bundle/config/resources_types.go b/bundle/config/resources_types.go index 15fac1b93f1..dcc91545f19 100644 --- a/bundle/config/resources_types.go +++ b/bundle/config/resources_types.go @@ -26,7 +26,7 @@ var ResourcesTypes = func() map[string]reflect.Type { continue } elemType := field.Type.Elem() - if elemType.Kind() == reflect.Ptr { + if elemType.Kind() == reflect.Pointer { elemType = elemType.Elem() } diff --git a/bundle/config/root.go b/bundle/config/root.go index 764d801bc27..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" @@ -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) } 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/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/configsync/diff.go b/bundle/configsync/diff.go index dee7fa48116..4a0b4f01ca3 100644 --- a/bundle/configsync/diff.go +++ b/bundle/configsync/diff.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/bundle/deploy" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct" + "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/libs/dyn" "github.com/databricks/cli/libs/dyn/convert" "github.com/databricks/cli/libs/log" @@ -85,7 +86,12 @@ func filterEntityDefaults(basePath string, value any) any { } func convertChangeDesc(path string, cd *deployplan.ChangeDesc) (*ConfigChangeDesc, error) { - hasConfigValue := cd.Old != nil || cd.New != nil + // Use cd.New (current config) to decide whether the field exists "on the config side". + // cd.Old (saved state) must not be considered: when the user has already synced a rename + // locally (cd.New == nil for the old key) but state still holds the prior key, including + // cd.Old in this check would classify the change as Replace and fail later in + // resolveSelectors because the old key no longer exists in the YAML. + hasConfigValue := cd.New != nil normalizedValue, err := normalizeValue(cd.Remote) if err != nil { return nil, fmt.Errorf("failed to normalize remote value: %w", err) @@ -134,7 +140,7 @@ func DetectChanges(ctx context.Context, b *bundle.Bundle, engine engine.EngineTy } else { deployBundle = &direct.DeploymentBundle{} _, statePath := b.StateFilenameConfigSnapshot(ctx) - if err := deployBundle.StateDB.Open(statePath); err != nil { + if err := deployBundle.StateDB.Open(ctx, statePath, dstate.WithRecovery(true), dstate.WithWrite(false)); err != nil { return nil, fmt.Errorf("failed to open state: %w", err) } } diff --git a/bundle/configsync/diff_test.go b/bundle/configsync/diff_test.go index f55a6453784..5402fa5ab01 100644 --- a/bundle/configsync/diff_test.go +++ b/bundle/configsync/diff_test.go @@ -3,9 +3,86 @@ package configsync import ( "testing" + "github.com/databricks/cli/bundle/deployplan" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func TestConvertChangeDesc(t *testing.T) { + tests := []struct { + name string + path string + cd *deployplan.ChangeDesc + wantOp OperationType + wantVal any + }{ + { + name: "add: new in remote only", + path: "resources.jobs.my_job.description", + cd: &deployplan.ChangeDesc{Old: nil, New: nil, Remote: "remote-desc"}, + wantOp: OperationAdd, + wantVal: "remote-desc", + }, + { + name: "remove: in config, missing from remote", + path: "resources.jobs.my_job.description", + cd: &deployplan.ChangeDesc{Old: "state-desc", New: "config-desc", Remote: nil}, + wantOp: OperationRemove, + wantVal: nil, + }, + { + name: "replace: differs between config and remote", + path: "resources.jobs.my_job.description", + cd: &deployplan.ChangeDesc{Old: "state-desc", New: "config-desc", Remote: "remote-desc"}, + wantOp: OperationReplace, + wantVal: "remote-desc", + }, + { + name: "skip: absent everywhere", + path: "resources.jobs.my_job.description", + cd: &deployplan.ChangeDesc{Old: nil, New: nil, Remote: nil}, + wantOp: OperationSkip, + wantVal: nil, + }, + // Regression: rename-back-and-forth. State holds the old key (user did not + // redeploy after the first sync), config holds the intermediate key, and + // remote now matches the original. The element at this path is missing from + // config, so the change must be Add — Replace would error in resolveSelectors + // because the keyed element no longer exists in the YAML. + { + name: "add: rename-back path, state has it but config does not", + path: "resources.jobs.my_job.tasks[task_key='new_task']", + cd: &deployplan.ChangeDesc{ + Old: map[string]any{"task_key": "new_task"}, + New: nil, + Remote: map[string]any{"task_key": "new_task"}, + }, + wantOp: OperationAdd, + wantVal: map[string]any{"task_key": "new_task"}, + }, + { + name: "skip: state has it, config and remote do not", + path: "resources.jobs.my_job.tasks[task_key='gone']", + cd: &deployplan.ChangeDesc{ + Old: map[string]any{"task_key": "gone"}, + New: nil, + Remote: nil, + }, + wantOp: OperationSkip, + wantVal: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := convertChangeDesc(tt.path, tt.cd) + require.NoError(t, err) + assert.Equal(t, tt.wantOp, got.Operation) + assert.Equal(t, tt.wantVal, got.Value) + }) + } +} + func TestStripNamePrefix(t *testing.T) { tests := []struct { name string diff --git a/bundle/configsync/variables.go b/bundle/configsync/variables.go new file mode 100644 index 00000000000..0745bfba43b --- /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(ctx, statePath, dstate.WithRecovery(false), dstate.WithWrite(false)); 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/terraform/check_dashboards_modified_remotely.go b/bundle/deploy/terraform/check_dashboards_modified_remotely.go index 4e56eb8e1d1..ed873618ed1 100644 --- a/bundle/deploy/terraform/check_dashboards_modified_remotely.go +++ b/bundle/deploy/terraform/check_dashboards_modified_remotely.go @@ -7,6 +7,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config/engine" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" ) @@ -120,8 +121,8 @@ func (l *checkDashboardsModifiedRemotely) Apply(ctx context.Context, b *bundle.B "Make sure that the local dashboard definition matches what you intend to deploy\n" + "before proceeding with the deployment.\n" + "\n" + - "Run `databricks bundle deploy --force` to bypass this error." + - "", + "To overwrite the remote changes with your local version, use --force.\n" + + "The remote modifications will be lost." + agent.AgentNotice(), Paths: []dyn.Path{path}, Locations: []dyn.Location{loc}, }) diff --git a/bundle/deploy/terraform/import.go b/bundle/deploy/terraform/import.go index 8fdfe1212d5..48ad622c6cb 100644 --- a/bundle/deploy/terraform/import.go +++ b/bundle/deploy/terraform/import.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/diag" "github.com/hashicorp/terraform-exec/tfexec" @@ -72,7 +73,7 @@ func (m *importResource) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagn cmdio.LogString(ctx, output) if !cmdio.IsPromptSupported(ctx) { - return diag.Errorf("This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed.") + return diag.Errorf("this bind operation requires user confirmation, but the current console does not support prompting.\nTo proceed, use --auto-approve after reviewing the plan above.%s", agent.AgentNotice()) } ans, err := cmdio.AskYesOrNo(ctx, "Confirm import changes? Changes will be remotely applied only after running 'bundle deploy'.") diff --git a/bundle/deploy/terraform/interpolate.go b/bundle/deploy/terraform/interpolate.go index fdcb671bdd3..92df9e61cc9 100644 --- a/bundle/deploy/terraform/interpolate.go +++ b/bundle/deploy/terraform/interpolate.go @@ -16,7 +16,7 @@ type interpolateMutator struct{} // Postgres resources use "name" instead of "id" as their identifier attribute. func isPostgresResource(resourceType string) bool { switch resourceType { - case "postgres_projects", "postgres_branches", "postgres_endpoints": + case "postgres_projects", "postgres_branches", "postgres_endpoints", "postgres_catalogs", "postgres_synced_tables": return true default: return false diff --git a/bundle/deploy/terraform/pkg.go b/bundle/deploy/terraform/pkg.go index a66e5cb6a06..d8dd56c04ea 100644 --- a/bundle/deploy/terraform/pkg.go +++ b/bundle/deploy/terraform/pkg.go @@ -129,6 +129,8 @@ var GroupToTerraformName = map[string]string{ "postgres_projects": "databricks_postgres_project", "postgres_branches": "databricks_postgres_branch", "postgres_endpoints": "databricks_postgres_endpoint", + "postgres_catalogs": "databricks_postgres_catalog", + "postgres_synced_tables": "databricks_postgres_synced_table", // 3 level groups: resources.*.GROUP "permissions": "databricks_permissions", diff --git a/bundle/deploy/terraform/tfdyn/convert_job_test.go b/bundle/deploy/terraform/tfdyn/convert_job_test.go index 1f67369e916..dc0b4862cd7 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_job_test.go @@ -296,9 +296,7 @@ func TestSupportedTypeTasksComplete(t *testing.T) { taskType := reflect.TypeFor[jobs.Task]() var tasksWithSource []string - for i := range taskType.NumField() { - field := taskType.Field(i) - + for field := range taskType.Fields() { // Skip non-task fields (like DependsOn, Libraries, etc.) if !strings.HasSuffix(field.Name, "Task") { continue @@ -306,7 +304,7 @@ func TestSupportedTypeTasksComplete(t *testing.T) { // Get the type of the task field (e.g., *NotebookTask) taskFieldType := field.Type - if taskFieldType.Kind() == reflect.Ptr { + if taskFieldType.Kind() == reflect.Pointer { taskFieldType = taskFieldType.Elem() } @@ -341,7 +339,7 @@ func TestSupportedTypeTasksComplete(t *testing.T) { // findSourceFieldsShallow searches for Source fields in a struct type, going only one level deep. // Returns a list of paths to Source fields (e.g., "" for direct Source, "file" for sql_task.file). func findSourceFieldsShallow(t reflect.Type) []string { - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { t = t.Elem() } @@ -351,9 +349,7 @@ func findSourceFieldsShallow(t reflect.Type) []string { var paths []string - for i := range t.NumField() { - field := t.Field(i) - + for field := range t.Fields() { // Check if this field is named "Source" if field.Name == "Source" { paths = append(paths, "") @@ -362,7 +358,7 @@ func findSourceFieldsShallow(t reflect.Type) []string { // Only search one level deep in nested structs fieldType := field.Type - if fieldType.Kind() == reflect.Ptr { + if fieldType.Kind() == reflect.Pointer { fieldType = fieldType.Elem() } diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_branch.go b/bundle/deploy/terraform/tfdyn/convert_postgres_branch.go index c1d598a1e64..12914e93074 100644 --- a/bundle/deploy/terraform/tfdyn/convert_postgres_branch.go +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_branch.go @@ -15,7 +15,7 @@ func (c postgresBranchConverter) Convert(ctx context.Context, key string, vin dy // The bundle config has flattened BranchSpec fields at the top level. // Terraform expects them nested in a "spec" block. specFields := specFieldNames(schema.ResourcePostgresBranchSpec{}) - topLevelFields := []string{"branch_id", "parent"} + topLevelFields := []string{"branch_id", "parent", "replace_existing"} // Build the spec block from the flattened fields specMap := make(map[string]dyn.Value) diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_catalog.go b/bundle/deploy/terraform/tfdyn/convert_postgres_catalog.go new file mode 100644 index 00000000000..b84fb09d717 --- /dev/null +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_catalog.go @@ -0,0 +1,56 @@ +package tfdyn + +import ( + "context" + + "github.com/databricks/cli/bundle/internal/tf/schema" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/log" +) + +type postgresCatalogConverter struct{} + +func (c postgresCatalogConverter) Convert(ctx context.Context, key string, vin dyn.Value, out *schema.Resources) error { + // The bundle config has flattened CatalogCatalogSpec fields at the top level. + // Terraform expects them nested in a "spec" block. + specFields := specFieldNames(schema.ResourcePostgresCatalogSpec{}) + topLevelFields := []string{"catalog_id"} + + specMap := make(map[string]dyn.Value) + for _, field := range specFields { + if v := vin.Get(field); v.Kind() != dyn.KindInvalid { + specMap[field] = v + } + } + + outMap := make(map[string]dyn.Value) + for _, field := range topLevelFields { + if v := vin.Get(field); v.Kind() != dyn.KindInvalid { + outMap[field] = v + } + } + if len(specMap) > 0 { + outMap["spec"] = dyn.V(specMap) + } + + vout := dyn.V(outMap) + + vout, diags := convert.Normalize(schema.ResourcePostgresCatalog{}, vout) + for _, diag := range diags { + log.Debugf(ctx, "postgres catalog normalization diagnostic: %s", diag.Summary) + } + + vout, err := convertLifecycle(ctx, vout, vin.Get("lifecycle")) + if err != nil { + return err + } + + out.PostgresCatalog[key] = vout.AsAny() + + return nil +} + +func init() { + registerConverter("postgres_catalogs", postgresCatalogConverter{}) +} diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_catalog_test.go b/bundle/deploy/terraform/tfdyn/convert_postgres_catalog_test.go new file mode 100644 index 00000000000..8eca766f613 --- /dev/null +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_catalog_test.go @@ -0,0 +1,73 @@ +package tfdyn + +import ( + "testing" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/internal/tf/schema" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConvertPostgresCatalog(t *testing.T) { + src := resources.PostgresCatalog{ + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "shop_lakebase", + CatalogCatalogSpec: postgres.CatalogCatalogSpec{ + Branch: "projects/my-shop/branches/production", + PostgresDatabase: "appdb", + CreateDatabaseIfMissing: true, + }, + }, + } + + vin, err := convert.FromTyped(src, dyn.NilValue) + require.NoError(t, err) + + ctx := t.Context() + out := schema.NewResources() + err = postgresCatalogConverter{}.Convert(ctx, "my_postgres_catalog", vin, out) + require.NoError(t, err) + + postgresCatalog := out.PostgresCatalog["my_postgres_catalog"] + assert.Equal(t, map[string]any{ + "catalog_id": "shop_lakebase", + "spec": map[string]any{ + "branch": "projects/my-shop/branches/production", + "postgres_database": "appdb", + "create_database_if_missing": true, + }, + }, postgresCatalog) +} + +func TestConvertPostgresCatalogMinimal(t *testing.T) { + src := resources.PostgresCatalog{ + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "minimal_catalog", + CatalogCatalogSpec: postgres.CatalogCatalogSpec{ + Branch: "projects/my-shop/branches/production", + PostgresDatabase: "appdb", + }, + }, + } + + vin, err := convert.FromTyped(src, dyn.NilValue) + require.NoError(t, err) + + ctx := t.Context() + out := schema.NewResources() + err = postgresCatalogConverter{}.Convert(ctx, "minimal_postgres_catalog", vin, out) + require.NoError(t, err) + + postgresCatalog := out.PostgresCatalog["minimal_postgres_catalog"] + assert.Equal(t, map[string]any{ + "catalog_id": "minimal_catalog", + "spec": map[string]any{ + "branch": "projects/my-shop/branches/production", + "postgres_database": "appdb", + }, + }, postgresCatalog) +} diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_endpoint.go b/bundle/deploy/terraform/tfdyn/convert_postgres_endpoint.go index f97582a2a33..b603abfcb75 100644 --- a/bundle/deploy/terraform/tfdyn/convert_postgres_endpoint.go +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_endpoint.go @@ -15,7 +15,7 @@ func (c postgresEndpointConverter) Convert(ctx context.Context, key string, vin // The bundle config has flattened EndpointSpec fields at the top level. // Terraform expects them nested in a "spec" block. specFields := specFieldNames(schema.ResourcePostgresEndpointSpec{}) - topLevelFields := []string{"endpoint_id", "parent"} + topLevelFields := []string{"endpoint_id", "parent", "replace_existing"} // Build the spec block from the flattened fields specMap := make(map[string]dyn.Value) diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table.go b/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table.go new file mode 100644 index 00000000000..2f777294295 --- /dev/null +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table.go @@ -0,0 +1,56 @@ +package tfdyn + +import ( + "context" + + "github.com/databricks/cli/bundle/internal/tf/schema" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/cli/libs/log" +) + +type postgresSyncedTableConverter struct{} + +func (c postgresSyncedTableConverter) Convert(ctx context.Context, key string, vin dyn.Value, out *schema.Resources) error { + // The bundle config has flattened SyncedTableSyncedTableSpec fields at the top level. + // Terraform expects them nested in a "spec" block. + specFields := specFieldNames(schema.ResourcePostgresSyncedTableSpec{}) + topLevelFields := []string{"synced_table_id"} + + specMap := make(map[string]dyn.Value) + for _, field := range specFields { + if v := vin.Get(field); v.Kind() != dyn.KindInvalid { + specMap[field] = v + } + } + + outMap := make(map[string]dyn.Value) + for _, field := range topLevelFields { + if v := vin.Get(field); v.Kind() != dyn.KindInvalid { + outMap[field] = v + } + } + if len(specMap) > 0 { + outMap["spec"] = dyn.V(specMap) + } + + vout := dyn.V(outMap) + + vout, diags := convert.Normalize(schema.ResourcePostgresSyncedTable{}, vout) + for _, diag := range diags { + log.Debugf(ctx, "postgres synced table normalization diagnostic: %s", diag.Summary) + } + + vout, err := convertLifecycle(ctx, vout, vin.Get("lifecycle")) + if err != nil { + return err + } + + out.PostgresSyncedTable[key] = vout.AsAny() + + return nil +} + +func init() { + registerConverter("postgres_synced_tables", postgresSyncedTableConverter{}) +} diff --git a/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table_test.go b/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table_test.go new file mode 100644 index 00000000000..84e854e6e03 --- /dev/null +++ b/bundle/deploy/terraform/tfdyn/convert_postgres_synced_table_test.go @@ -0,0 +1,95 @@ +package tfdyn + +import ( + "testing" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/internal/tf/schema" + "github.com/databricks/cli/libs/dyn" + "github.com/databricks/cli/libs/dyn/convert" + "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConvertPostgresSyncedTable(t *testing.T) { + src := resources.PostgresSyncedTable{ + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "shop_lakebase.public.orders_synced", + SyncedTableSyncedTableSpec: postgres.SyncedTableSyncedTableSpec{ + Branch: "projects/my-shop/branches/production", + PostgresDatabase: "appdb", + SourceTableFullName: "main.raw.orders", + PrimaryKeyColumns: []string{"order_id"}, + TimeseriesKey: "updated_at", + SchedulingPolicy: postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicySnapshot, + CreateDatabaseObjectsIfMissing: true, + NewPipelineSpec: &postgres.NewPipelineSpec{ + StorageCatalog: "main", + StorageSchema: "pipelines", + }, + }, + }, + } + + vin, err := convert.FromTyped(src, dyn.NilValue) + require.NoError(t, err) + + ctx := t.Context() + out := schema.NewResources() + err = postgresSyncedTableConverter{}.Convert(ctx, "my_postgres_synced_table", vin, out) + require.NoError(t, err) + + postgresSyncedTable := out.PostgresSyncedTable["my_postgres_synced_table"] + assert.Equal(t, map[string]any{ + "synced_table_id": "shop_lakebase.public.orders_synced", + "spec": map[string]any{ + "branch": "projects/my-shop/branches/production", + "postgres_database": "appdb", + "source_table_full_name": "main.raw.orders", + "primary_key_columns": []any{"order_id"}, + "timeseries_key": "updated_at", + "scheduling_policy": "SNAPSHOT", + "create_database_objects_if_missing": true, + "new_pipeline_spec": map[string]any{ + "storage_catalog": "main", + "storage_schema": "pipelines", + }, + }, + }, postgresSyncedTable) +} + +func TestConvertPostgresSyncedTableMinimal(t *testing.T) { + src := resources.PostgresSyncedTable{ + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "shop_lakebase.public.orders_synced", + SyncedTableSyncedTableSpec: postgres.SyncedTableSyncedTableSpec{ + Branch: "projects/my-shop/branches/production", + PostgresDatabase: "appdb", + SourceTableFullName: "main.raw.orders", + PrimaryKeyColumns: []string{"order_id"}, + SchedulingPolicy: postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicySnapshot, + }, + }, + } + + vin, err := convert.FromTyped(src, dyn.NilValue) + require.NoError(t, err) + + ctx := t.Context() + out := schema.NewResources() + err = postgresSyncedTableConverter{}.Convert(ctx, "minimal_postgres_synced_table", vin, out) + require.NoError(t, err) + + postgresSyncedTable := out.PostgresSyncedTable["minimal_postgres_synced_table"] + assert.Equal(t, map[string]any{ + "synced_table_id": "shop_lakebase.public.orders_synced", + "spec": map[string]any{ + "branch": "projects/my-shop/branches/production", + "postgres_database": "appdb", + "source_table_full_name": "main.raw.orders", + "primary_key_columns": []any{"order_id"}, + "scheduling_policy": "SNAPSHOT", + }, + }, postgresSyncedTable) +} diff --git a/bundle/deploy/terraform/util.go b/bundle/deploy/terraform/util.go index 632d32bca19..7ca5e9a1d14 100644 --- a/bundle/deploy/terraform/util.go +++ b/bundle/deploy/terraform/util.go @@ -96,7 +96,7 @@ func parseResourcesState(ctx context.Context, path string) (ExportedResourcesMap // The direct engine manages permissions as a sub-resource // (SecretScopeFixups adds MANAGE ACL for the current user). result[resourceKey+".permissions"] = ResourceState{ID: instance.Attributes.Name} - case "apps", "database_instances", "database_catalogs", "synced_database_tables", "postgres_projects", "postgres_branches", "postgres_endpoints": + case "apps", "database_instances", "database_catalogs", "synced_database_tables", "postgres_projects", "postgres_branches", "postgres_endpoints", "postgres_catalogs", "postgres_synced_tables": resourceKey = "resources." + groupName + "." + resource.Name resourceState = ResourceState{ID: instance.Attributes.Name} case "dashboards": 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/direct/apply.go b/bundle/direct/apply.go index 42d80efab34..e327cb8563e 100644 --- a/bundle/direct/apply.go +++ b/bundle/direct/apply.go @@ -66,7 +66,7 @@ func (d *DeploymentUnit) Create(ctx context.Context, db *dstate.DeploymentState, return fmt.Errorf("saving state after creating id=%s: %w", newID, err) } - waitRemoteState, err := d.Adapter.WaitAfterCreate(ctx, newState) + waitRemoteState, err := d.Adapter.WaitAfterCreate(ctx, newID, newState) if err != nil { return fmt.Errorf("waiting after creating id=%s: %w", newID, err) } @@ -80,13 +80,18 @@ func (d *DeploymentUnit) Create(ctx context.Context, db *dstate.DeploymentState, } func (d *DeploymentUnit) Recreate(ctx context.Context, db *dstate.DeploymentState, oldID string, newState any) error { - // Note, unlike Delete(), we hard error on 403 here intentionally + // Note, unlike Delete(), we hard error on 403 here intentionally. + // MANAGED_BY_PARENT is still disregarded — the subsequent Create with + // replace_existing=true will reconfigure the parent-managed resource in + // place, matching the Terraform provider's recreate behaviour. err := d.Adapter.DoDelete(ctx, oldID) - if err != nil && !isResourceGone(err) { + if err != nil && !isResourceGone(err) && !isManagedByParent(err) { 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) } @@ -114,7 +119,7 @@ func (d *DeploymentUnit) Update(ctx context.Context, db *dstate.DeploymentState, return fmt.Errorf("saving state id=%s: %w", id, err) } - waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, newState) + waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, id, newState) if err != nil { return fmt.Errorf("waiting after updating id=%s: %w", id, err) } @@ -150,7 +155,7 @@ func (d *DeploymentUnit) UpdateWithID(ctx context.Context, db *dstate.Deployment return fmt.Errorf("saving state id=%s: %w", oldID, err) } - waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, newState) + waitRemoteState, err := d.Adapter.WaitAfterUpdate(ctx, newID, newState) if err != nil { return fmt.Errorf("waiting after updating id=%s: %w", newID, err) } @@ -166,7 +171,7 @@ func (d *DeploymentUnit) UpdateWithID(ctx context.Context, db *dstate.Deployment func (d *DeploymentUnit) Delete(ctx context.Context, db *dstate.DeploymentState, oldID string) error { err := d.Adapter.DoDelete(ctx, oldID) - if err != nil && !isResourceGone(err) { + if err != nil && !isResourceGone(err) && !isManagedByParent(err) { // Rather than failing delete and requiring user to unbind, we perform unbind automatically there. // Some services, e.g. jobs, return 403 for missing resources if caller did not have permissions to it when job existed. // In those cases 403 hides 404. In other cases, user not having permissions to resource but having in the bundle might diff --git a/bundle/direct/bind.go b/bundle/direct/bind.go index ed5cbbc07bc..f1c534bea9d 100644 --- a/bundle/direct/bind.go +++ b/bundle/direct/bind.go @@ -62,8 +62,12 @@ type BindResult struct { func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.WorkspaceClient, configRoot *config.Root, statePath, resourceKey, resourceID string) (*BindResult, error) { // Check if the resource is already managed (bound to a different ID) var checkStateDB dstate.DeploymentState - if err := checkStateDB.Open(statePath); err == nil { - if existingID := checkStateDB.GetResourceID(resourceKey); existingID != "" { + if err := checkStateDB.Open(ctx, statePath, dstate.WithRecovery(true), dstate.WithWrite(false)); err == nil { + existingID := checkStateDB.GetResourceID(resourceKey) + if _, err := checkStateDB.Finalize(ctx); err != nil { + log.Warnf(ctx, "failed to finalize state: %v", err) + } + if existingID != "" { return nil, ErrResourceAlreadyBound{ ResourceKey: resourceKey, ExistingID: existingID, @@ -82,7 +86,7 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac } // Open temp state - err := b.StateDB.Open(tmpStatePath) + err := b.StateDB.Open(ctx, tmpStatePath, dstate.WithRecovery(false), dstate.WithWrite(true)) if err != nil { os.Remove(tmpStatePath) return nil, err @@ -96,7 +100,7 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac } // Finalize to persist temp state to disk - err = b.StateDB.Finalize() + _, err = b.StateDB.Finalize(ctx) if err != nil { os.Remove(tmpStatePath) return nil, err @@ -105,11 +109,19 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac log.Infof(ctx, "Bound %s to id=%s (in temp state)", resourceKey, resourceID) // First plan + update: populate state with resolved config + err = b.StateDB.Open(ctx, tmpStatePath, dstate.WithRecovery(true), dstate.WithWrite(false)) + if err != nil { + os.Remove(tmpStatePath) + return nil, err + } plan, err := b.CalculatePlan(ctx, client, configRoot) if err != nil { os.Remove(tmpStatePath) return nil, err } + if _, err := b.StateDB.Finalize(ctx); err != nil { + log.Warnf(ctx, "failed to finalize state: %v", err) + } // Populate the state with the resolved config entry := plan.Plan[resourceKey] @@ -132,13 +144,19 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac } } + err = b.StateDB.Open(ctx, tmpStatePath, dstate.WithRecovery(true), dstate.WithWrite(true)) + if err != nil { + os.Remove(tmpStatePath) + return nil, err + } + err = b.StateDB.SaveState(resourceKey, resourceID, sv.Value, dependsOn) if err != nil { os.Remove(tmpStatePath) return nil, err } - err = b.StateDB.Finalize() + _, err = b.StateDB.Finalize(ctx) if err != nil { os.Remove(tmpStatePath) return nil, err @@ -146,7 +164,15 @@ func (b *DeploymentBundle) Bind(ctx context.Context, client *databricks.Workspac } // Second plan: this is the plan to present to the user (change between remote resource and config) + err = b.StateDB.Open(ctx, tmpStatePath, dstate.WithRecovery(true), dstate.WithWrite(false)) + if err != nil { + os.Remove(tmpStatePath) + return nil, err + } plan, err = b.CalculatePlan(ctx, client, configRoot) + if _, ferr := b.StateDB.Finalize(ctx); ferr != nil { + log.Warnf(ctx, "failed to finalize state: %v", ferr) + } if err != nil { os.Remove(tmpStatePath) return nil, err @@ -188,7 +214,7 @@ func (result *BindResult) Cancel() { // Unbind removes a resource from direct engine state without deleting // the workspace resource. Also removes associated permissions/grants entries. func (b *DeploymentBundle) Unbind(ctx context.Context, statePath, resourceKey string) error { - err := b.StateDB.Open(statePath) + err := b.StateDB.Open(ctx, statePath, dstate.WithRecovery(true), dstate.WithWrite(true)) if err != nil { return err } @@ -216,5 +242,6 @@ func (b *DeploymentBundle) Unbind(ctx context.Context, statePath, resourceKey st } } - return b.StateDB.Finalize() + _, err = b.StateDB.Finalize(ctx) + return err } diff --git a/bundle/direct/bundle_apply.go b/bundle/direct/bundle_apply.go index a7f3ee65fc2..6bad8091469 100644 --- a/bundle/direct/bundle_apply.go +++ b/bundle/direct/bundle_apply.go @@ -25,7 +25,7 @@ func (b *DeploymentBundle) Apply(ctx context.Context, client *databricks.Workspa return } - b.StateDB.AssertOpened() + b.StateDB.AssertOpenedForWrite() b.RemoteStateCache.Clear() g, err := makeGraph(plan) diff --git a/bundle/direct/bundle_plan.go b/bundle/direct/bundle_plan.go index 3fab4c3f4ff..5c543619b06 100644 --- a/bundle/direct/bundle_plan.go +++ b/bundle/direct/bundle_plan.go @@ -37,24 +37,19 @@ func (b *DeploymentBundle) init(client *databricks.WorkspaceClient) error { return err } -// ValidatePlanAgainstState validates that a plan's lineage and serial match the current state. -// This should be called early in the deployment process, before any file operations. +// ValidatePlanAgainstState validates that a plan's lineage and serial match the given state. // If the plan has no lineage (first deployment), validation is skipped. func ValidatePlanAgainstState(stateDB *dstate.DeploymentState, plan *deployplan.Plan) error { - // If plan has no lineage, this is a first deployment before any state exists - // No validation needed if plan.Lineage == "" { return nil } - stateDB.AssertOpened() + stateDB.AssertOpenedForReadOrWrite() - // Validate that the plan's lineage matches the current state's lineage if plan.Lineage != stateDB.Data.Lineage { return fmt.Errorf("plan lineage %q does not match state lineage %q; the state may have been modified by another process", plan.Lineage, stateDB.Data.Lineage) } - // Validate that the plan's serial matches the current state's serial if plan.Serial != stateDB.Data.Serial { return fmt.Errorf("plan serial %d does not match state serial %d; the state has been modified since the plan was created. Please run 'bundle plan' again", plan.Serial, stateDB.Data.Serial) } @@ -63,9 +58,9 @@ func ValidatePlanAgainstState(stateDB *dstate.DeploymentState, plan *deployplan. } // InitForApply initializes the DeploymentBundle for applying a pre-computed plan. -// This is used when --plan is specified to skip the planning phase. +// StateDB must already be open for write before calling this function. func (b *DeploymentBundle) InitForApply(ctx context.Context, client *databricks.WorkspaceClient, plan *deployplan.Plan) error { - b.StateDB.AssertOpened() + b.StateDB.AssertOpenedForWrite() err := b.init(client) if err != nil { @@ -97,8 +92,10 @@ func (b *DeploymentBundle) InitForApply(ctx context.Context, client *databricks. return nil } +// CalculatePlan computes the deployment plan by comparing local config against remote state. +// StateDB must already be open for read before calling this function. func (b *DeploymentBundle) CalculatePlan(ctx context.Context, client *databricks.WorkspaceClient, configRoot *config.Root) (*deployplan.Plan, error) { - b.StateDB.AssertOpened() + b.StateDB.AssertOpenedForRead() err := b.init(client) if err != nil { @@ -181,16 +178,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)) @@ -526,7 +522,7 @@ func isEmpty(rv reflect.Value) bool { } func isEmptyStruct(rv reflect.Value) bool { - if rv.Kind() == reflect.Ptr { + if rv.Kind() == reflect.Pointer { if rv.IsNil() { return false } diff --git a/bundle/direct/dresources/.gitattributes b/bundle/direct/dresources/.gitattributes new file mode 100644 index 00000000000..48aea316546 --- /dev/null +++ b/bundle/direct/dresources/.gitattributes @@ -0,0 +1 @@ +*.generated.yml linguist-generated=true diff --git a/bundle/direct/dresources/adapter.go b/bundle/direct/dresources/adapter.go index f931208c3cc..a32cbd8a5da 100644 --- a/bundle/direct/dresources/adapter.go +++ b/bundle/direct/dresources/adapter.go @@ -67,10 +67,10 @@ type IResource interface { // [Optional] WaitAfterCreate waits for the resource to become ready after creation. Returns optionally updated remote state. // TODO: wait status should be persisted in the state. - WaitAfterCreate(ctx context.Context, newState any) (remoteState any, e error) + WaitAfterCreate(ctx context.Context, id string, newState any) (remoteState any, e error) // [Optional] WaitAfterUpdate waits for the resource to become ready after update. Returns optionally updated remote state. - WaitAfterUpdate(ctx context.Context, newState any) (remoteState any, e error) + WaitAfterUpdate(ctx context.Context, id string, newState any) (remoteState any, e error) // [Optional] KeyedSlices returns a map from path patterns to KeyFunc for comparing slices by key instead of by index. // Example: func (*ResourcePermissions) KeyedSlices(state *PermissionsState) map[string]any @@ -306,7 +306,7 @@ func (a *Adapter) validate() error { } if a.waitAfterCreate != nil { - validations = append(validations, "WaitAfterCreate newState", a.waitAfterCreate.InTypes[1], stateType) + validations = append(validations, "WaitAfterCreate newState", a.waitAfterCreate.InTypes[2], stateType) // WaitAfterCreate must return (remoteType, error) if len(a.waitAfterCreate.OutTypes) != 2 { return fmt.Errorf("WaitAfterCreate must return (remoteType, error), got %d return values", len(a.waitAfterCreate.OutTypes)) @@ -315,7 +315,7 @@ func (a *Adapter) validate() error { } if a.waitAfterUpdate != nil { - validations = append(validations, "WaitAfterUpdate newState", a.waitAfterUpdate.InTypes[1], stateType) + validations = append(validations, "WaitAfterUpdate newState", a.waitAfterUpdate.InTypes[2], stateType) // WaitAfterUpdate must return (remoteType, error) if len(a.waitAfterUpdate.OutTypes) != 2 { return fmt.Errorf("WaitAfterUpdate must return (remoteType, error), got %d return values", len(a.waitAfterUpdate.OutTypes)) @@ -414,7 +414,7 @@ func normalizeNilPointer(v any) any { return nil } rv := reflect.ValueOf(v) - if rv.Kind() == reflect.Ptr && rv.IsNil() { + if rv.Kind() == reflect.Pointer && rv.IsNil() { return nil } return v @@ -485,12 +485,12 @@ func (a *Adapter) DoResize(ctx context.Context, id string, newState any) error { // WaitAfterCreate waits for the resource to become ready after creation. // If the resource doesn't implement this method, this is a no-op. // Returns the updated remoteState if available, otherwise returns nil -func (a *Adapter) WaitAfterCreate(ctx context.Context, newState any) (any, error) { +func (a *Adapter) WaitAfterCreate(ctx context.Context, id string, newState any) (any, error) { if a.waitAfterCreate == nil { return nil, nil // no-op if not implemented } - outs, err := a.waitAfterCreate.Call(ctx, newState) + outs, err := a.waitAfterCreate.Call(ctx, id, newState) if err != nil { return nil, err } @@ -502,12 +502,12 @@ func (a *Adapter) WaitAfterCreate(ctx context.Context, newState any) (any, error // WaitAfterUpdate waits for the resource to become ready after update. // If the resource doesn't implement this method, this is a no-op. // Returns the updated remoteState if available, otherwise returns nil. -func (a *Adapter) WaitAfterUpdate(ctx context.Context, newState any) (any, error) { +func (a *Adapter) WaitAfterUpdate(ctx context.Context, id string, newState any) (any, error) { if a.waitAfterUpdate == nil { return nil, nil // no-op if not implemented } - outs, err := a.waitAfterUpdate.Call(ctx, newState) + outs, err := a.waitAfterUpdate.Call(ctx, id, newState) if err != nil { return nil, err } @@ -550,7 +550,7 @@ func validatePointerToStruct(t reflect.Type, context string) error { if t == nil { return fmt.Errorf("%s not set", context) } - if t.Kind() != reflect.Ptr { + if t.Kind() != reflect.Pointer { return fmt.Errorf("%s must be a pointer, got %s", context, t.Kind()) } if t.Elem().Kind() != reflect.Struct { diff --git a/bundle/direct/dresources/all.go b/bundle/direct/dresources/all.go index ddc30c41f54..799e5ae14d7 100644 --- a/bundle/direct/dresources/all.go +++ b/bundle/direct/dresources/all.go @@ -23,6 +23,8 @@ var SupportedResources = map[string]any{ "postgres_projects": (*ResourcePostgresProject)(nil), "postgres_branches": (*ResourcePostgresBranch)(nil), "postgres_endpoints": (*ResourcePostgresEndpoint)(nil), + "postgres_catalogs": (*ResourcePostgresCatalog)(nil), + "postgres_synced_tables": (*ResourcePostgresSyncedTable)(nil), "alerts": (*ResourceAlert)(nil), "clusters": (*ResourceCluster)(nil), "registered_models": (*ResourceRegisteredModel)(nil), diff --git a/bundle/direct/dresources/all_test.go b/bundle/direct/dresources/all_test.go index 9f0dc07e90b..7d4df60ccdf 100644 --- a/bundle/direct/dresources/all_test.go +++ b/bundle/direct/dresources/all_test.go @@ -141,6 +141,7 @@ var testConfig map[string]any = map[string]any{ Name: "my-endpoint", Config: &serving.EndpointCoreConfigInput{ Name: "my-endpoint", + //nolint:staticcheck // SA1019: deprecated AutoCaptureConfigInput kept for bundle config compatibility AutoCaptureConfig: &serving.AutoCaptureConfigInput{ CatalogName: "main", SchemaName: "myschema", @@ -190,6 +191,18 @@ var testConfig map[string]any = map[string]any{ }, }, + "postgres_catalogs": &resources.PostgresCatalog{ + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "test_catalog", + }, + }, + + "postgres_synced_tables": &resources.PostgresSyncedTable{ + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "main.public.trips_synced", + }, + }, + "alerts": &resources.Alert{ AlertV2: sql.AlertV2{ DisplayName: "my-alert", @@ -404,20 +417,19 @@ var testDeps = map[string]prepareWorkspace{ }, "postgres_projects.permissions": func(ctx context.Context, client *databricks.WorkspaceClient) (any, error) { + const projectID = "permissions-project" waiter, err := client.Postgres.CreateProject(ctx, postgres.CreateProjectRequest{ - ProjectId: "permissions-project", + ProjectId: projectID, }) if err != nil { return nil, err } - result, err := waiter.Wait(ctx) - if err != nil { + if _, err := waiter.Wait(ctx); err != nil { return nil, err } - components, _ := ParsePostgresName(result.Name) return &PermissionsState{ - ObjectID: "/database-projects/" + components.ProjectID, + ObjectID: "/database-projects/" + projectID, EmbeddedSlice: []StatePermission{{ Level: "CAN_MANAGE", UserName: "user@example.com", @@ -797,7 +809,7 @@ func testCRUD(t *testing.T, group string, adapter *Adapter, client *databricks.W "unexpected differences between remappedState and remappedRemoteStateFromCreate") } - remoteStateFromWaitCreate, err := adapter.WaitAfterCreate(ctx, newState) + remoteStateFromWaitCreate, err := adapter.WaitAfterCreate(ctx, createdID, newState) require.NoError(t, err) if remoteStateFromWaitCreate != nil { require.Equal(t, remote, remoteStateFromWaitCreate) @@ -813,7 +825,7 @@ func testCRUD(t *testing.T, group string, adapter *Adapter, client *databricks.W "unexpected differences between remappedState and remappedStateFromUpdate") } - remoteStateFromWaitUpdate, err := adapter.WaitAfterUpdate(ctx, newState) + remoteStateFromWaitUpdate, err := adapter.WaitAfterUpdate(ctx, createdID, newState) require.NoError(t, err) if remoteStateFromWaitUpdate != nil { remappedStateFromWaitUpdate, err := adapter.RemapState(remoteStateFromWaitUpdate) diff --git a/bundle/direct/dresources/apitypes.generated.yml b/bundle/direct/dresources/apitypes.generated.yml index a80b3baa69b..767ad08049a 100644 --- a/bundle/direct/dresources/apitypes.generated.yml +++ b/bundle/direct/dresources/apitypes.generated.yml @@ -28,10 +28,14 @@ pipelines: pipelines.CreatePipeline postgres_branches: postgres.BranchSpec +postgres_catalogs: postgres.CatalogCatalogSpec + postgres_endpoints: postgres.EndpointSpec postgres_projects: postgres.ProjectStatus +postgres_synced_tables: postgres.SyncedTableSyncedTableSpec + quality_monitors: catalog.CreateMonitor registered_models: catalog.RegisteredModelInfo diff --git a/bundle/direct/dresources/apitypes.yml b/bundle/direct/dresources/apitypes.yml index 29db9b67b20..7d478be47f7 100644 --- a/bundle/direct/dresources/apitypes.yml +++ b/bundle/direct/dresources/apitypes.yml @@ -9,3 +9,7 @@ postgres_branches: postgres.BranchSpec postgres_endpoints: postgres.EndpointSpec postgres_projects: postgres.ProjectSpec + +postgres_catalogs: postgres.CatalogCatalogSpec + +postgres_synced_tables: postgres.SyncedTableSyncedTableSpec diff --git a/bundle/direct/dresources/app.go b/bundle/direct/dresources/app.go index 5f882170613..8cae2e50e67 100644 --- a/bundle/direct/dresources/app.go +++ b/bundle/direct/dresources/app.go @@ -321,7 +321,7 @@ func (r *ResourceApp) DoDelete(ctx context.Context, id string) error { return err } -func (r *ResourceApp) WaitAfterCreate(ctx context.Context, config *AppState) (*AppRemote, error) { +func (r *ResourceApp) WaitAfterCreate(ctx context.Context, id string, config *AppState) (*AppRemote, error) { remote, err := r.waitForApp(ctx, r.client, config.Name) if err != nil { return nil, err diff --git a/bundle/direct/dresources/app_test.go b/bundle/direct/dresources/app_test.go index edb99c4cffb..c7cbfacd1af 100644 --- a/bundle/direct/dresources/app_test.go +++ b/bundle/direct/dresources/app_test.go @@ -149,8 +149,7 @@ func TestAppDoUpdate_UpdateMaskHasAllFields(t *testing.T) { fields := reflect.TypeFor[apps.App]() var allFields []string - for i := range fields.NumField() { - field := fields.Field(i) + for field := range fields.Fields() { jsonTag := field.Tag.Get("json") if jsonTag == "" || jsonTag == "-" { continue diff --git a/bundle/direct/dresources/config_test.go b/bundle/direct/dresources/config_test.go index 6d871268975..a9a347ec747 100644 --- a/bundle/direct/dresources/config_test.go +++ b/bundle/direct/dresources/config_test.go @@ -15,3 +15,60 @@ func TestGetResourceConfig(t *testing.T) { assert.NotEmpty(t, GetResourceConfig("volumes").RecreateOnChanges) assert.Empty(t, GetResourceConfig("nonexistent").RecreateOnChanges) } + +// categoryRules projects ResourceLifecycleConfig's five categories onto a +// uniform [name, []FieldRule] shape so the redundancy check can iterate them. +func categoryRules(c ResourceLifecycleConfig) []struct { + name string + rules []FieldRule +} { + backendAsFieldRules := make([]FieldRule, len(c.BackendDefaults)) + for i, r := range c.BackendDefaults { + backendAsFieldRules[i] = FieldRule{Field: r.Field} + } + return []struct { + name string + rules []FieldRule + }{ + {"ignore_remote_changes", c.IgnoreRemoteChanges}, + {"ignore_local_changes", c.IgnoreLocalChanges}, + {"recreate_on_changes", c.RecreateOnChanges}, + {"update_id_on_changes", c.UpdateIDOnChanges}, + {"backend_defaults", backendAsFieldRules}, + } +} + +// TestResourcesYMLNoRedundantRules guards against two redundancy classes in +// resources.yml: duplicate field entries within the same category of a +// resource, and entries that the autogenerated resources.generated.yml already +// produces from the OpenAPI schema. +func TestResourcesYMLNoRedundantRules(t *testing.T) { + handWritten := MustLoadConfig() + generated := MustLoadGeneratedConfig() + + for resourceType, rc := range handWritten.Resources { + genCats := categoryRules(generated.Resources[resourceType]) + genFields := make(map[string]map[string]bool, len(genCats)) + for _, c := range genCats { + fields := make(map[string]bool, len(c.rules)) + for _, r := range c.rules { + fields[r.Field.String()] = true + } + genFields[c.name] = fields + } + + for _, c := range categoryRules(rc) { + seen := make(map[string]bool, len(c.rules)) + for _, r := range c.rules { + field := r.Field.String() + if seen[field] { + t.Errorf("bundle/direct/dresources/resources.yml: %s.%s lists %q twice; remove the duplicate entry", resourceType, c.name, field) + } + seen[field] = true + if genFields[c.name][field] { + t.Errorf("bundle/direct/dresources/resources.yml: %s.%s entry %q is already produced by resources.generated.yml; remove it from resources.yml", resourceType, c.name, field) + } + } + } + } +} diff --git a/bundle/direct/dresources/database_instance.go b/bundle/direct/dresources/database_instance.go index e7a5e8d824c..d3bceda4b7d 100644 --- a/bundle/direct/dresources/database_instance.go +++ b/bundle/direct/dresources/database_instance.go @@ -46,7 +46,7 @@ func (d *ResourceDatabaseInstance) DoUpdate(ctx context.Context, id string, conf return nil, err } -func (d *ResourceDatabaseInstance) WaitAfterCreate(ctx context.Context, config *database.DatabaseInstance) (*database.DatabaseInstance, error) { +func (d *ResourceDatabaseInstance) WaitAfterCreate(ctx context.Context, id string, config *database.DatabaseInstance) (*database.DatabaseInstance, error) { waiter := &database.WaitGetDatabaseInstanceDatabaseAvailable[database.DatabaseInstance]{ Response: config, Name: config.Name, diff --git a/bundle/direct/dresources/job.go b/bundle/direct/dresources/job.go index 9477bf52517..1b167cafee7 100644 --- a/bundle/direct/dresources/job.go +++ b/bundle/direct/dresources/job.go @@ -94,9 +94,10 @@ func (r *ResourceJob) DoRead(ctx context.Context, id string) (*JobRemote, error) // GetByJobId only fetches the first page (100 tasks). Jobs.Get handles // pagination and returns the complete job with all tasks merged. job, err := r.client.Jobs.Get(ctx, jobs.GetJobRequest{ - JobId: idInt, - PageToken: "", - ForceSendFields: nil, + JobId: idInt, + PageToken: "", + IncludeTriggerState: false, + ForceSendFields: nil, }) if err != nil { return nil, err diff --git a/bundle/direct/dresources/model_serving_endpoint.go b/bundle/direct/dresources/model_serving_endpoint.go index 06a8dbda40f..35aec6ffd44 100644 --- a/bundle/direct/dresources/model_serving_endpoint.go +++ b/bundle/direct/dresources/model_serving_endpoint.go @@ -34,6 +34,11 @@ func (*ResourceModelServingEndpoint) PrepareState(input *resources.ModelServingE return &input.CreateServingEndpoint } +// AutoCaptureConfig is the legacy inference-table API; the recommended replacement +// is AI Gateway inference tables. Bundles still expose it, so the conversion has to +// keep working until users have migrated. +// +//nolint:staticcheck // SA1019: deprecated AutoCaptureConfig{Input,Output} kept for bundle config compatibility func autoCaptureConfigOutputToInput(output *serving.AutoCaptureConfigOutput) *serving.AutoCaptureConfigInput { if output == nil { return nil @@ -145,11 +150,11 @@ func (r *ResourceModelServingEndpoint) waitForEndpointReady(ctx context.Context, }, nil } -func (r *ResourceModelServingEndpoint) WaitAfterCreate(ctx context.Context, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { +func (r *ResourceModelServingEndpoint) WaitAfterCreate(ctx context.Context, id string, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { return r.waitForEndpointReady(ctx, config.Name) } -func (r *ResourceModelServingEndpoint) WaitAfterUpdate(ctx context.Context, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { +func (r *ResourceModelServingEndpoint) WaitAfterUpdate(ctx context.Context, id string, config *serving.CreateServingEndpoint) (*ModelServingEndpointRemote, error) { return r.waitForEndpointReady(ctx, config.Name) } diff --git a/bundle/direct/dresources/postgres_branch.go b/bundle/direct/dresources/postgres_branch.go index cb042699dec..625cccc5282 100644 --- a/bundle/direct/dresources/postgres_branch.go +++ b/bundle/direct/dresources/postgres_branch.go @@ -6,9 +6,37 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/postgres" ) +// PostgresBranchRemote is the return type for DoRead. It embeds BranchSpec so that +// all paths in StateType are valid paths in RemoteType, enabling drift detection +// for spec fields once the backend echoes spec on GET. +type PostgresBranchRemote struct { + postgres.BranchSpec + + BranchId string `json:"branch_id,omitempty"` + Parent string `json:"parent,omitempty"` + + Name string `json:"name,omitempty"` + Status *postgres.BranchStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + CreateTime *sdktime.Time `json:"create_time,omitempty"` + UpdateTime *sdktime.Time `json:"update_time,omitempty"` +} + +// Custom marshaler needed because embedded BranchSpec has its own MarshalJSON +// which would otherwise take over and ignore the additional fields. +func (s *PostgresBranchRemote) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s PostgresBranchRemote) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + type ResourcePostgresBranch struct { client *databricks.WorkspaceClient } @@ -21,42 +49,60 @@ func (*ResourcePostgresBranch) New(client *databricks.WorkspaceClient) *Resource func (*ResourcePostgresBranch) PrepareState(input *resources.PostgresBranch) *PostgresBranchState { return &PostgresBranchState{ - BranchId: input.BranchId, - Parent: input.Parent, - BranchSpec: input.BranchSpec, + BranchId: input.BranchId, + Parent: input.Parent, + ReplaceExisting: input.ReplaceExisting, + BranchSpec: input.BranchSpec, } } -func (*ResourcePostgresBranch) RemapState(remote *postgres.Branch) *PostgresBranchState { - // Extract branch_id from hierarchical name: "projects/{project_id}/branches/{branch_id}" - // TODO: log error when we have access to the context - components, _ := ParsePostgresName(remote.Name) - +func (*ResourcePostgresBranch) RemapState(remote *PostgresBranchRemote) *PostgresBranchState { return &PostgresBranchState{ - BranchId: components.BranchID, + BranchId: remote.BranchId, Parent: remote.Parent, - // The read API does not return the spec, only the status. - // This means we cannot detect remote drift for spec fields. - // Use an empty struct (not nil) so field-level diffing works correctly. - BranchSpec: postgres.BranchSpec{ - ExpireTime: nil, - IsProtected: false, - NoExpiry: false, - SourceBranch: "", - SourceBranchLsn: "", - SourceBranchTime: nil, - Ttl: nil, - ForceSendFields: nil, - }, + // replace_existing is a create-time-only flag; the GET API never returns + // it, so RemapState leaves it false. + ReplaceExisting: false, + + BranchSpec: remote.BranchSpec, } } -func (r *ResourcePostgresBranch) DoRead(ctx context.Context, id string) (*postgres.Branch, error) { - return r.client.Postgres.GetBranch(ctx, postgres.GetBranchRequest{Name: id}) +// makePostgresBranchRemote converts the SDK Branch into the embedded remote shape. +// GET does not echo spec today (only status is returned); the embedded spec fields +// stay at their zero values, and resources.yml suppresses phantom drift via +// ignore_remote_changes with reason spec:input_only. +func makePostgresBranchRemote(branch *postgres.Branch) *PostgresBranchRemote { + var spec postgres.BranchSpec + if branch.Spec != nil { + spec = *branch.Spec + } + var branchID string + if branch.Status != nil { + branchID = branch.Status.BranchId + } + return &PostgresBranchRemote{ + BranchSpec: spec, + BranchId: branchID, + Parent: branch.Parent, + Name: branch.Name, + Status: branch.Status, + Uid: branch.Uid, + CreateTime: branch.CreateTime, + UpdateTime: branch.UpdateTime, + } } -func (r *ResourcePostgresBranch) DoCreate(ctx context.Context, config *PostgresBranchState) (string, *postgres.Branch, error) { +func (r *ResourcePostgresBranch) DoRead(ctx context.Context, id string) (*PostgresBranchRemote, error) { + branch, err := r.client.Postgres.GetBranch(ctx, postgres.GetBranchRequest{Name: id}) + if err != nil { + return nil, err + } + return makePostgresBranchRemote(branch), nil +} + +func (r *ResourcePostgresBranch) DoCreate(ctx context.Context, config *PostgresBranchState) (string, *PostgresBranchRemote, error) { waiter, err := r.client.Postgres.CreateBranch(ctx, postgres.CreateBranchRequest{ BranchId: config.BranchId, Parent: config.Parent, @@ -72,6 +118,8 @@ func (r *ResourcePostgresBranch) DoCreate(ctx context.Context, config *PostgresB UpdateTime: nil, ForceSendFields: nil, }, + ReplaceExisting: config.ReplaceExisting, + ForceSendFields: nil, }) if err != nil { return "", nil, err @@ -83,10 +131,11 @@ func (r *ResourcePostgresBranch) DoCreate(ctx context.Context, config *PostgresB return "", nil, err } - return result.Name, result, nil + remote := makePostgresBranchRemote(result) + return remote.Name, remote, nil } -func (r *ResourcePostgresBranch) DoUpdate(ctx context.Context, id string, config *PostgresBranchState, entry *PlanEntry) (*postgres.Branch, error) { +func (r *ResourcePostgresBranch) DoUpdate(ctx context.Context, id string, config *PostgresBranchState, entry *PlanEntry) (*PostgresBranchRemote, error) { // Build update mask from fields that have action="update" in the changes map. // This excludes immutable fields and fields that haven't changed. // Prefix with "spec." because the API expects paths relative to the Branch object, @@ -117,12 +166,17 @@ func (r *ResourcePostgresBranch) DoUpdate(ctx context.Context, id string, config // Wait for the update to complete result, err := waiter.Wait(ctx) - return result, err + if err != nil { + return nil, err + } + return makePostgresBranchRemote(result), nil } func (r *ResourcePostgresBranch) DoDelete(ctx context.Context, id string) error { waiter, err := r.client.Postgres.DeleteBranch(ctx, postgres.DeleteBranchRequest{ - Name: id, + Name: id, + Purge: false, + ForceSendFields: nil, }) if err != nil { return err diff --git a/bundle/direct/dresources/postgres_catalog.go b/bundle/direct/dresources/postgres_catalog.go new file mode 100644 index 00000000000..48a3262e4ad --- /dev/null +++ b/bundle/direct/dresources/postgres_catalog.go @@ -0,0 +1,133 @@ +package dresources + +import ( + "context" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/databricks-sdk-go" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/marshal" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +// PostgresCatalogRemote is the return type for DoRead. It embeds CatalogCatalogSpec so +// that all paths in StateType are valid paths in RemoteType, enabling drift detection +// for spec fields once the backend echoes spec on GET. +type PostgresCatalogRemote struct { + postgres.CatalogCatalogSpec + + CatalogId string `json:"catalog_id,omitempty"` + + Name string `json:"name,omitempty"` + Status *postgres.CatalogCatalogStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + CreateTime *sdktime.Time `json:"create_time,omitempty"` + UpdateTime *sdktime.Time `json:"update_time,omitempty"` +} + +// Custom marshaler needed because embedded CatalogCatalogSpec has its own MarshalJSON +// which would otherwise take over and ignore the additional fields. +func (s *PostgresCatalogRemote) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s PostgresCatalogRemote) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + +type ResourcePostgresCatalog struct { + client *databricks.WorkspaceClient +} + +type PostgresCatalogState = resources.PostgresCatalogConfig + +func (*ResourcePostgresCatalog) New(client *databricks.WorkspaceClient) *ResourcePostgresCatalog { + return &ResourcePostgresCatalog{client: client} +} + +func (*ResourcePostgresCatalog) PrepareState(input *resources.PostgresCatalog) *PostgresCatalogState { + return &PostgresCatalogState{ + CatalogId: input.CatalogId, + CatalogCatalogSpec: input.CatalogCatalogSpec, + } +} + +func (*ResourcePostgresCatalog) RemapState(remote *PostgresCatalogRemote) *PostgresCatalogState { + return &PostgresCatalogState{ + CatalogId: remote.CatalogId, + CatalogCatalogSpec: remote.CatalogCatalogSpec, + } +} + +// makePostgresCatalogRemote converts the SDK Catalog into the embedded remote shape. +// GET does not echo spec today (only status is returned); the embedded spec fields +// stay at their zero values, and resources.yml suppresses phantom drift via +// ignore_remote_changes with reason spec:input_only. +// +// Status.CatalogId is the short identifier and matches the user-supplied config. +// Prefer it over parsing remote.Name — semantic contract from the API rather than +// string manipulation on the hierarchical path. +func makePostgresCatalogRemote(catalog *postgres.Catalog) *PostgresCatalogRemote { + var spec postgres.CatalogCatalogSpec + if catalog.Spec != nil { + spec = *catalog.Spec + } + var catalogId string + if catalog.Status != nil { + catalogId = catalog.Status.CatalogId + } + return &PostgresCatalogRemote{ + CatalogCatalogSpec: spec, + CatalogId: catalogId, + Name: catalog.Name, + Status: catalog.Status, + Uid: catalog.Uid, + CreateTime: catalog.CreateTime, + UpdateTime: catalog.UpdateTime, + } +} + +func (r *ResourcePostgresCatalog) DoRead(ctx context.Context, id string) (*PostgresCatalogRemote, error) { + catalog, err := r.client.Postgres.GetCatalog(ctx, postgres.GetCatalogRequest{Name: id}) + if err != nil { + return nil, err + } + return makePostgresCatalogRemote(catalog), nil +} + +func (r *ResourcePostgresCatalog) DoCreate(ctx context.Context, config *PostgresCatalogState) (string, *PostgresCatalogRemote, error) { + waiter, err := r.client.Postgres.CreateCatalog(ctx, postgres.CreateCatalogRequest{ + CatalogId: config.CatalogId, + Catalog: postgres.Catalog{ + Spec: &config.CatalogCatalogSpec, + + // Output-only fields. + CreateTime: nil, + Name: "", + Status: nil, + Uid: "", + UpdateTime: nil, + ForceSendFields: nil, + }, + }) + if err != nil { + return "", nil, err + } + + result, err := waiter.Wait(ctx) + if err != nil { + return "", nil, err + } + remote := makePostgresCatalogRemote(result) + return remote.Name, remote, nil +} + +func (r *ResourcePostgresCatalog) DoDelete(ctx context.Context, id string) error { + waiter, err := r.client.Postgres.DeleteCatalog(ctx, postgres.DeleteCatalogRequest{ + Name: id, + }) + if err != nil { + return err + } + return waiter.Wait(ctx) +} diff --git a/bundle/direct/dresources/postgres_endpoint.go b/bundle/direct/dresources/postgres_endpoint.go index 63f0eb0f2db..df9f79ebd3b 100644 --- a/bundle/direct/dresources/postgres_endpoint.go +++ b/bundle/direct/dresources/postgres_endpoint.go @@ -11,6 +11,8 @@ import ( "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/postgres" ) @@ -18,6 +20,32 @@ import ( // This value is a heuristic and is being discussed with the backend team. const endpointReconciliationTimeout = 2 * time.Minute +// PostgresEndpointRemote is the return type for DoRead. It embeds EndpointSpec so +// that all paths in StateType are valid paths in RemoteType, enabling drift +// detection for spec fields once the backend echoes spec on GET. +type PostgresEndpointRemote struct { + postgres.EndpointSpec + + EndpointId string `json:"endpoint_id,omitempty"` + Parent string `json:"parent,omitempty"` + + Name string `json:"name,omitempty"` + Status *postgres.EndpointStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + CreateTime *sdktime.Time `json:"create_time,omitempty"` + UpdateTime *sdktime.Time `json:"update_time,omitempty"` +} + +// Custom marshaler needed because embedded EndpointSpec has its own MarshalJSON +// which would otherwise take over and ignore the additional fields. +func (s *PostgresEndpointRemote) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s PostgresEndpointRemote) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + type ResourcePostgresEndpoint struct { client *databricks.WorkspaceClient } @@ -30,46 +58,63 @@ func (*ResourcePostgresEndpoint) New(client *databricks.WorkspaceClient) *Resour func (*ResourcePostgresEndpoint) PrepareState(input *resources.PostgresEndpoint) *PostgresEndpointState { return &PostgresEndpointState{ - EndpointId: input.EndpointId, - Parent: input.Parent, - EndpointSpec: input.EndpointSpec, + EndpointId: input.EndpointId, + Parent: input.Parent, + ReplaceExisting: input.ReplaceExisting, + EndpointSpec: input.EndpointSpec, } } -func (*ResourcePostgresEndpoint) RemapState(remote *postgres.Endpoint) *PostgresEndpointState { - // Extract endpoint_id from hierarchical name: "projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}" - // TODO: log error when we have access to the context - components, _ := ParsePostgresName(remote.Name) - +func (*ResourcePostgresEndpoint) RemapState(remote *PostgresEndpointRemote) *PostgresEndpointState { return &PostgresEndpointState{ - EndpointId: components.EndpointID, + EndpointId: remote.EndpointId, Parent: remote.Parent, - // The read API does not return the spec, only the status. - // This means we cannot detect remote drift for spec fields. - // Use an empty struct (not nil) so field-level diffing works correctly. - EndpointSpec: postgres.EndpointSpec{ - AutoscalingLimitMaxCu: 0, - AutoscalingLimitMinCu: 0, - Disabled: false, - EndpointType: "", - Group: nil, - NoSuspension: false, - Settings: nil, - SuspendTimeoutDuration: nil, - ForceSendFields: nil, - }, + // replace_existing is a create-time-only flag; the GET API never returns + // it, so RemapState leaves it false. + ReplaceExisting: false, + + EndpointSpec: remote.EndpointSpec, } } -func (r *ResourcePostgresEndpoint) DoRead(ctx context.Context, id string) (*postgres.Endpoint, error) { - return r.client.Postgres.GetEndpoint(ctx, postgres.GetEndpointRequest{Name: id}) +// makePostgresEndpointRemote converts the SDK Endpoint into the embedded remote shape. +// GET does not echo spec today (only status is returned); the embedded spec fields +// stay at their zero values, and resources.yml suppresses phantom drift via +// ignore_remote_changes with reason spec:input_only. +func makePostgresEndpointRemote(endpoint *postgres.Endpoint) *PostgresEndpointRemote { + var spec postgres.EndpointSpec + if endpoint.Spec != nil { + spec = *endpoint.Spec + } + var endpointID string + if endpoint.Status != nil { + endpointID = endpoint.Status.EndpointId + } + return &PostgresEndpointRemote{ + EndpointSpec: spec, + EndpointId: endpointID, + Parent: endpoint.Parent, + Name: endpoint.Name, + Status: endpoint.Status, + Uid: endpoint.Uid, + CreateTime: endpoint.CreateTime, + UpdateTime: endpoint.UpdateTime, + } +} + +func (r *ResourcePostgresEndpoint) DoRead(ctx context.Context, id string) (*PostgresEndpointRemote, error) { + endpoint, err := r.client.Postgres.GetEndpoint(ctx, postgres.GetEndpointRequest{Name: id}) + if err != nil { + return nil, err + } + return makePostgresEndpointRemote(endpoint), nil } // waitForReconciliation polls the endpoint until PendingState is empty. // This is needed because the operation can complete while internal reconciliation // is still in progress, which would cause subsequent operations to fail. -func (r *ResourcePostgresEndpoint) waitForReconciliation(ctx context.Context, name string) (*postgres.Endpoint, error) { +func (r *ResourcePostgresEndpoint) waitForReconciliation(ctx context.Context, name string) (*PostgresEndpointRemote, error) { deadline := time.Now().Add(endpointReconciliationTimeout) for { endpoint, err := r.client.Postgres.GetEndpoint(ctx, postgres.GetEndpointRequest{Name: name}) @@ -79,7 +124,7 @@ func (r *ResourcePostgresEndpoint) waitForReconciliation(ctx context.Context, na // If there's no pending state, reconciliation is complete if endpoint.Status == nil || endpoint.Status.PendingState == "" { - return endpoint, nil + return makePostgresEndpointRemote(endpoint), nil } // Check if we've exceeded the timeout @@ -96,7 +141,7 @@ func (r *ResourcePostgresEndpoint) waitForReconciliation(ctx context.Context, na } } -func (r *ResourcePostgresEndpoint) DoCreate(ctx context.Context, config *PostgresEndpointState) (string, *postgres.Endpoint, error) { +func (r *ResourcePostgresEndpoint) DoCreate(ctx context.Context, config *PostgresEndpointState) (string, *PostgresEndpointRemote, error) { waiter, err := r.client.Postgres.CreateEndpoint(ctx, postgres.CreateEndpointRequest{ EndpointId: config.EndpointId, Parent: config.Parent, @@ -112,6 +157,8 @@ func (r *ResourcePostgresEndpoint) DoCreate(ctx context.Context, config *Postgre UpdateTime: nil, ForceSendFields: nil, }, + ReplaceExisting: config.ReplaceExisting, + ForceSendFields: nil, }) if err != nil { return "", nil, err @@ -124,15 +171,15 @@ func (r *ResourcePostgresEndpoint) DoCreate(ctx context.Context, config *Postgre } // Wait for reconciliation to complete - result, err = r.waitForReconciliation(ctx, result.Name) + remote, err := r.waitForReconciliation(ctx, result.Name) if err != nil { return "", nil, err } - return result.Name, result, nil + return remote.Name, remote, nil } -func (r *ResourcePostgresEndpoint) DoUpdate(ctx context.Context, id string, config *PostgresEndpointState, entry *PlanEntry) (*postgres.Endpoint, error) { +func (r *ResourcePostgresEndpoint) DoUpdate(ctx context.Context, id string, config *PostgresEndpointState, entry *PlanEntry) (*PostgresEndpointRemote, error) { // Build update mask from fields that have action="update" in the changes map. // This excludes immutable fields and fields that haven't changed. // Prefix with "spec." because the API expects paths relative to the Endpoint object, diff --git a/bundle/direct/dresources/postgres_project.go b/bundle/direct/dresources/postgres_project.go index e1e802b2678..e40af35da00 100644 --- a/bundle/direct/dresources/postgres_project.go +++ b/bundle/direct/dresources/postgres_project.go @@ -6,9 +6,39 @@ import ( "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/marshal" "github.com/databricks/databricks-sdk-go/service/postgres" ) +// PostgresProjectRemote is the return type for DoRead. It embeds ProjectSpec so +// that all paths in StateType are valid paths in RemoteType, enabling drift +// detection for spec fields once the backend echoes spec on GET. +type PostgresProjectRemote struct { + postgres.ProjectSpec + + ProjectId string `json:"project_id,omitempty"` + + InitialEndpointSpec *postgres.InitialEndpointSpec `json:"initial_endpoint_spec,omitempty"` + Name string `json:"name,omitempty"` + Status *postgres.ProjectStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + CreateTime *sdktime.Time `json:"create_time,omitempty"` + DeleteTime *sdktime.Time `json:"delete_time,omitempty"` + PurgeTime *sdktime.Time `json:"purge_time,omitempty"` + UpdateTime *sdktime.Time `json:"update_time,omitempty"` +} + +// Custom marshaler needed because embedded ProjectSpec has its own MarshalJSON +// which would otherwise take over and ignore the additional fields. +func (s *PostgresProjectRemote) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s PostgresProjectRemote) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + type ResourcePostgresProject struct { client *databricks.WorkspaceClient } @@ -26,36 +56,49 @@ func (*ResourcePostgresProject) PrepareState(input *resources.PostgresProject) * } } -func (*ResourcePostgresProject) RemapState(remote *postgres.Project) *PostgresProjectState { - // Extract project_id from hierarchical name: "projects/{project_id}" - // TODO: log error when we have access to the context - components, _ := ParsePostgresName(remote.Name) - +func (*ResourcePostgresProject) RemapState(remote *PostgresProjectRemote) *PostgresProjectState { return &PostgresProjectState{ - ProjectId: components.ProjectID, - - // The read API does not return the spec, only the status. - // This means we cannot detect remote drift for spec fields. - // Use an empty struct (not nil) so field-level diffing works correctly. - ProjectSpec: postgres.ProjectSpec{ - BudgetPolicyId: "", - CustomTags: nil, - DefaultBranch: "", - DefaultEndpointSettings: nil, - DisplayName: "", - EnablePgNativeLogin: false, - HistoryRetentionDuration: nil, - PgVersion: 0, - ForceSendFields: nil, - }, + ProjectId: remote.ProjectId, + ProjectSpec: remote.ProjectSpec, + } +} + +// makePostgresProjectRemote converts the SDK Project into the embedded remote shape. +// GET does not echo spec today (only status is returned); the embedded spec fields +// stay at their zero values, and resources.yml suppresses phantom drift via +// ignore_remote_changes with reason spec:input_only. +func makePostgresProjectRemote(project *postgres.Project) *PostgresProjectRemote { + var spec postgres.ProjectSpec + if project.Spec != nil { + spec = *project.Spec + } + var projectID string + if project.Status != nil { + projectID = project.Status.ProjectId + } + return &PostgresProjectRemote{ + ProjectSpec: spec, + ProjectId: projectID, + InitialEndpointSpec: project.InitialEndpointSpec, + Name: project.Name, + Status: project.Status, + Uid: project.Uid, + CreateTime: project.CreateTime, + DeleteTime: project.DeleteTime, + PurgeTime: project.PurgeTime, + UpdateTime: project.UpdateTime, } } -func (r *ResourcePostgresProject) DoRead(ctx context.Context, id string) (*postgres.Project, error) { - return r.client.Postgres.GetProject(ctx, postgres.GetProjectRequest{Name: id}) +func (r *ResourcePostgresProject) DoRead(ctx context.Context, id string) (*PostgresProjectRemote, error) { + project, err := r.client.Postgres.GetProject(ctx, postgres.GetProjectRequest{Name: id}) + if err != nil { + return nil, err + } + return makePostgresProjectRemote(project), nil } -func (r *ResourcePostgresProject) DoCreate(ctx context.Context, config *PostgresProjectState) (string, *postgres.Project, error) { +func (r *ResourcePostgresProject) DoCreate(ctx context.Context, config *PostgresProjectState) (string, *PostgresProjectRemote, error) { waiter, err := r.client.Postgres.CreateProject(ctx, postgres.CreateProjectRequest{ ProjectId: config.ProjectId, Project: postgres.Project{ @@ -64,7 +107,9 @@ func (r *ResourcePostgresProject) DoCreate(ctx context.Context, config *Postgres // Output-only fields. CreateTime: nil, + DeleteTime: nil, Name: "", + PurgeTime: nil, Status: nil, Uid: "", UpdateTime: nil, @@ -81,10 +126,11 @@ func (r *ResourcePostgresProject) DoCreate(ctx context.Context, config *Postgres return "", nil, err } - return result.Name, result, nil + remote := makePostgresProjectRemote(result) + return remote.Name, remote, nil } -func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, config *PostgresProjectState, entry *PlanEntry) (*postgres.Project, error) { +func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, config *PostgresProjectState, entry *PlanEntry) (*PostgresProjectRemote, error) { // Build update mask from fields that have action="update" in the changes map. // This excludes immutable fields and fields that haven't changed. // Prefix with "spec." because the API expects paths relative to the Project object, @@ -98,7 +144,9 @@ func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, confi // Output-only fields. CreateTime: nil, + DeleteTime: nil, Name: "", + PurgeTime: nil, Status: nil, Uid: "", UpdateTime: nil, @@ -115,12 +163,17 @@ func (r *ResourcePostgresProject) DoUpdate(ctx context.Context, id string, confi // Wait for the update to complete result, err := waiter.Wait(ctx) - return result, err + if err != nil { + return nil, err + } + return makePostgresProjectRemote(result), nil } func (r *ResourcePostgresProject) DoDelete(ctx context.Context, id string) error { waiter, err := r.client.Postgres.DeleteProject(ctx, postgres.DeleteProjectRequest{ - Name: id, + Name: id, + Purge: false, + ForceSendFields: nil, }) if err != nil { return err diff --git a/bundle/direct/dresources/postgres_synced_table.go b/bundle/direct/dresources/postgres_synced_table.go new file mode 100644 index 00000000000..cd4c4cc78d8 --- /dev/null +++ b/bundle/direct/dresources/postgres_synced_table.go @@ -0,0 +1,129 @@ +package dresources + +import ( + "context" + "strings" + + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/databricks-sdk-go" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/marshal" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +// PostgresSyncedTableRemote is the return type for DoRead. It embeds +// SyncedTableSyncedTableSpec so that all paths in StateType are valid paths in +// RemoteType, enabling drift detection for spec fields once the backend echoes +// spec on GET. +type PostgresSyncedTableRemote struct { + postgres.SyncedTableSyncedTableSpec + + SyncedTableId string `json:"synced_table_id,omitempty"` + + Name string `json:"name,omitempty"` + Status *postgres.SyncedTableSyncedTableStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + CreateTime *sdktime.Time `json:"create_time,omitempty"` +} + +// Custom marshaler needed because embedded SyncedTableSyncedTableSpec has its own +// MarshalJSON which would otherwise take over and ignore the additional fields. +func (s *PostgresSyncedTableRemote) UnmarshalJSON(b []byte) error { + return marshal.Unmarshal(b, s) +} + +func (s PostgresSyncedTableRemote) MarshalJSON() ([]byte, error) { + return marshal.Marshal(s) +} + +type ResourcePostgresSyncedTable struct { + client *databricks.WorkspaceClient +} + +type PostgresSyncedTableState = resources.PostgresSyncedTableConfig + +func (*ResourcePostgresSyncedTable) New(client *databricks.WorkspaceClient) *ResourcePostgresSyncedTable { + return &ResourcePostgresSyncedTable{client: client} +} + +func (*ResourcePostgresSyncedTable) PrepareState(input *resources.PostgresSyncedTable) *PostgresSyncedTableState { + return &PostgresSyncedTableState{ + SyncedTableId: input.SyncedTableId, + SyncedTableSyncedTableSpec: input.SyncedTableSyncedTableSpec, + } +} + +func (*ResourcePostgresSyncedTable) RemapState(remote *PostgresSyncedTableRemote) *PostgresSyncedTableState { + return &PostgresSyncedTableState{ + SyncedTableId: remote.SyncedTableId, + SyncedTableSyncedTableSpec: remote.SyncedTableSyncedTableSpec, + } +} + +// makePostgresSyncedTableRemote converts the SDK SyncedTable into the embedded +// remote shape. GET does not echo spec today (only status is returned); the +// embedded spec fields stay at their zero values, and resources.yml suppresses +// phantom drift via ignore_remote_changes with reason spec:input_only. +// +// Unlike postgres_catalogs (which has Status.CatalogId), the synced-table API +// doesn't expose the user-facing id as a named field. It only appears as the +// trailing component of remote.Name, so we strip the constant "synced_tables/" +// prefix. +func makePostgresSyncedTableRemote(syncedTable *postgres.SyncedTable) *PostgresSyncedTableRemote { + var spec postgres.SyncedTableSyncedTableSpec + if syncedTable.Spec != nil { + spec = *syncedTable.Spec + } + return &PostgresSyncedTableRemote{ + SyncedTableSyncedTableSpec: spec, + SyncedTableId: strings.TrimPrefix(syncedTable.Name, "synced_tables/"), + Name: syncedTable.Name, + Status: syncedTable.Status, + Uid: syncedTable.Uid, + CreateTime: syncedTable.CreateTime, + } +} + +func (r *ResourcePostgresSyncedTable) DoRead(ctx context.Context, id string) (*PostgresSyncedTableRemote, error) { + syncedTable, err := r.client.Postgres.GetSyncedTable(ctx, postgres.GetSyncedTableRequest{Name: id}) + if err != nil { + return nil, err + } + return makePostgresSyncedTableRemote(syncedTable), nil +} + +func (r *ResourcePostgresSyncedTable) DoCreate(ctx context.Context, config *PostgresSyncedTableState) (string, *PostgresSyncedTableRemote, error) { + waiter, err := r.client.Postgres.CreateSyncedTable(ctx, postgres.CreateSyncedTableRequest{ + SyncedTableId: config.SyncedTableId, + SyncedTable: postgres.SyncedTable{ + Spec: &config.SyncedTableSyncedTableSpec, + + // Output-only fields. + CreateTime: nil, + Name: "", + Status: nil, + Uid: "", + ForceSendFields: nil, + }, + }) + if err != nil { + return "", nil, err + } + + result, err := waiter.Wait(ctx) + if err != nil { + return "", nil, err + } + remote := makePostgresSyncedTableRemote(result) + return remote.Name, remote, nil +} + +func (r *ResourcePostgresSyncedTable) DoDelete(ctx context.Context, id string) error { + waiter, err := r.client.Postgres.DeleteSyncedTable(ctx, postgres.DeleteSyncedTableRequest{ + Name: id, + }) + if err != nil { + return err + } + return waiter.Wait(ctx) +} diff --git a/bundle/direct/dresources/resources.generated.yml b/bundle/direct/dresources/resources.generated.yml index 6c3778d3494..03f19509671 100644 --- a/bundle/direct/dresources/resources.generated.yml +++ b/bundle/direct/dresources/resources.generated.yml @@ -59,6 +59,8 @@ resources: reason: spec:output_only - field: service_principal_name reason: spec:output_only + - field: thumbnail_url + reason: spec:output_only - field: update_time reason: spec:output_only - field: updater @@ -206,6 +208,20 @@ resources: - field: ttl reason: spec:input_only + postgres_catalogs: + + recreate_on_changes: + - field: postgres_database + reason: spec:immutable + + ignore_remote_changes: + - field: branch + reason: spec:input_only + - field: create_database_if_missing + reason: spec:input_only + - field: postgres_database + reason: spec:input_only + postgres_endpoints: recreate_on_changes: @@ -254,6 +270,28 @@ resources: - field: pg_version reason: spec:input_only + postgres_synced_tables: + + ignore_remote_changes: + - field: branch + reason: spec:input_only + - field: create_database_objects_if_missing + reason: spec:input_only + - field: existing_pipeline_id + reason: spec:input_only + - field: new_pipeline_spec + reason: spec:input_only + - field: postgres_database + reason: spec:input_only + - field: primary_key_columns + reason: spec:input_only + - field: scheduling_policy + reason: spec:input_only + - field: source_table_full_name + reason: spec:input_only + - field: timeseries_key + reason: spec:input_only + # quality_monitors: no api field behaviors # registered_models: no api field behaviors diff --git a/bundle/direct/dresources/resources.yml b/bundle/direct/dresources/resources.yml index 569fca9ee82..eb5cd9809d6 100644 --- a/bundle/direct/dresources/resources.yml +++ b/bundle/direct/dresources/resources.yml @@ -117,18 +117,6 @@ resources: # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L209 - field: ingestion_definition.ingest_from_uc_foreign_catalog reason: immutable - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L204 - - field: gateway_definition.connection_id - reason: immutable - - field: gateway_definition.connection_name - reason: immutable - - field: gateway_definition.gateway_storage_catalog - reason: immutable - - field: gateway_definition.gateway_storage_schema - reason: immutable - # https://github.com/databricks/terraform-provider-databricks/blob/4eba541abe1a9f50993ea7b9dd83874207e224a1/pipelines/resource_pipeline.go#L209 - - field: ingestion_definition.ingest_from_uc_foreign_catalog - reason: immutable ignore_remote_changes: # "id" is handled in a special way before any fields changed @@ -499,6 +487,14 @@ resources: reason: immutable - field: branch_id reason: immutable + ignore_remote_changes: + # replace_existing is a create-time query parameter; not returned by GET. + - field: replace_existing + reason: input_only + ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" postgres_endpoints: recreate_on_changes: @@ -507,6 +503,58 @@ resources: reason: immutable - field: endpoint_id reason: immutable + ignore_remote_changes: + # replace_existing is a create-time query parameter; not returned by GET. + - field: replace_existing + reason: input_only + ignore_local_changes: + # replace_existing only takes effect on create; toggling it later is a no-op. + - field: replace_existing + reason: "input_only; cannot be updated after create" + + postgres_catalogs: + recreate_on_changes: + # catalog_id is part of the hierarchical name and immutable. + - field: catalog_id + reason: immutable + # The Postgres SDK has no UpdateCatalog endpoint, so any local change + # requires delete+create. The OpenAPI spec only marks postgres_database + # as IMMUTABLE (handled by autogen); branch and create_database_if_missing + # need explicit entries here. + - field: branch + reason: immutable + - field: create_database_if_missing + reason: immutable + + postgres_synced_tables: + # The Postgres API has no UpdateSyncedTable endpoint, so every settable + # field is recreate-only on the intent side (local YAML edit -> delete + + # create). The complementary ignore_remote_changes block for this resource + # lives in resources.generated.yml and handles the read side: it suppresses + # drift for the same fields because the GET API does not echo back the + # spec. Together they make no-op deploys idempotent while a real config + # edit still triggers a recreate. Same pattern as secret_scopes. + recreate_on_changes: + - field: synced_table_id + reason: immutable + - field: branch + reason: immutable + - field: postgres_database + reason: immutable + - field: source_table_full_name + reason: immutable + - field: primary_key_columns + reason: immutable + - field: timeseries_key + reason: immutable + - field: scheduling_policy + reason: immutable + - field: create_database_objects_if_missing + reason: immutable + - field: new_pipeline_spec + reason: immutable + - field: existing_pipeline_id + reason: immutable vector_search_endpoints: recreate_on_changes: diff --git a/bundle/direct/dresources/type_test.go b/bundle/direct/dresources/type_test.go index 8e711c9bf03..f47d9ae2b47 100644 --- a/bundle/direct/dresources/type_test.go +++ b/bundle/direct/dresources/type_test.go @@ -44,39 +44,13 @@ var knownMissingInRemoteType = map[string][]string{ "scope_backend_type", }, "postgres_branches": { - "branch_id", - "expire_time", - "is_protected", - "no_expiry", - "source_branch", - "source_branch_lsn", - "source_branch_time", - "ttl", + "replace_existing", }, "postgres_endpoints": { - "autoscaling_limit_max_cu", - "autoscaling_limit_min_cu", - "disabled", - "endpoint_id", - "endpoint_type", - "group", - "no_suspension", - "settings", - "suspend_timeout_duration", - }, - "postgres_projects": { - "budget_policy_id", - "custom_tags", - "default_branch", - "default_endpoint_settings", - "display_name", - "enable_pg_native_login", - "history_retention_duration", - "pg_version", - "project_id", + "replace_existing", }, "vector_search_endpoints": { - "min_qps", + "target_qps", "usage_policy_id", }, } diff --git a/bundle/direct/dresources/util.go b/bundle/direct/dresources/util.go index 3bd0ab4ec73..658de7be528 100644 --- a/bundle/direct/dresources/util.go +++ b/bundle/direct/dresources/util.go @@ -2,42 +2,12 @@ package dresources import ( "errors" - "fmt" - "regexp" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/libs/structs/structpath" "github.com/databricks/databricks-sdk-go/retries" ) -// postgresNamePattern matches hierarchical Postgres resource names: -// - projects/{project_id} -// - projects/{project_id}/branches/{branch_id} -// - projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id} -var postgresNamePattern = regexp.MustCompile(`^projects/([^/]+)(?:/branches/([^/]+)(?:/endpoints/([^/]+))?)?$`) - -// PostgresNameComponents holds the extracted components from a Postgres resource name. -type PostgresNameComponents struct { - ProjectID string - BranchID string - EndpointID string -} - -// ParsePostgresName extracts project, branch, and endpoint IDs from a hierarchical Postgres resource name. -// Returns an error if the name doesn't match the expected format. -func ParsePostgresName(name string) (PostgresNameComponents, error) { - matches := postgresNamePattern.FindStringSubmatch(name) - if matches == nil { - return PostgresNameComponents{}, fmt.Errorf("invalid postgres resource name format: %q", name) - } - - return PostgresNameComponents{ - ProjectID: matches[1], - BranchID: matches[2], - EndpointID: matches[3], - }, nil -} - // 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 { diff --git a/bundle/direct/dresources/util_test.go b/bundle/direct/dresources/util_test.go index bbf04717099..544b3e2bde8 100644 --- a/bundle/direct/dresources/util_test.go +++ b/bundle/direct/dresources/util_test.go @@ -5,7 +5,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) // assertFieldsCovered asserts that all fields in sdkType (except those in skip) @@ -13,15 +12,13 @@ import ( func assertFieldsCovered(t *testing.T, sdkType, remoteType reflect.Type, skip map[string]bool) { t.Helper() remoteFields := map[string]bool{} - for i := range remoteType.NumField() { - f := remoteType.Field(i) + for f := range remoteType.Fields() { if !f.Anonymous { remoteFields[f.Name] = true } } - for i := range sdkType.NumField() { - field := sdkType.Field(i) + for field := range sdkType.Fields() { if skip[field.Name] { assert.NotContains(t, remoteFields, field.Name, "field %s is in skip list but present in %s; remove it from skip", field.Name, remoteType.Name()) continue @@ -29,94 +26,3 @@ func assertFieldsCovered(t *testing.T, sdkType, remoteType reflect.Type, skip ma assert.Contains(t, remoteFields, field.Name, "field %s from %s is missing in %s", field.Name, sdkType.Name(), remoteType.Name()) } } - -func TestParsePostgresName(t *testing.T) { - tests := []struct { - name string - input string - projectID string - branchID string - endpointID string - expectErr bool - }{ - { - name: "project", - input: "projects/my-project", - projectID: "my-project", - }, - { - name: "branch", - input: "projects/my-project/branches/my-branch", - projectID: "my-project", - branchID: "my-branch", - }, - { - name: "endpoint", - input: "projects/my-project/branches/my-branch/endpoints/my-endpoint", - projectID: "my-project", - branchID: "my-branch", - endpointID: "my-endpoint", - }, - { - name: "with hyphens and numbers", - input: "projects/my-app-123/branches/dev-branch/endpoints/primary-1", - projectID: "my-app-123", - branchID: "dev-branch", - endpointID: "primary-1", - }, - { - name: "empty", - input: "", - expectErr: true, - }, - { - name: "no prefix", - input: "my-project", - expectErr: true, - }, - { - name: "wrong prefix", - input: "project/my-project", - expectErr: true, - }, - { - name: "missing branch id", - input: "projects/my-project/branches/", - expectErr: true, - }, - { - name: "missing endpoint id", - input: "projects/my-project/branches/my-branch/endpoints/", - expectErr: true, - }, - { - name: "extra segments", - input: "projects/my-project/branches/my-branch/endpoints/my-endpoint/extra", - expectErr: true, - }, - { - name: "branches without branch", - input: "projects/my-project/branches", - expectErr: true, - }, - { - name: "endpoints without endpoint", - input: "projects/my-project/branches/my-branch/endpoints", - expectErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - components, err := ParsePostgresName(tt.input) - if tt.expectErr { - assert.ErrorContains(t, err, "invalid postgres resource name format") - return - } - require.NoError(t, err) - assert.Equal(t, tt.projectID, components.ProjectID) - assert.Equal(t, tt.branchID, components.BranchID) - assert.Equal(t, tt.endpointID, components.EndpointID) - }) - } -} diff --git a/bundle/direct/dresources/vector_search_endpoint.go b/bundle/direct/dresources/vector_search_endpoint.go index 1322f474a15..39211ca63ab 100644 --- a/bundle/direct/dresources/vector_search_endpoint.go +++ b/bundle/direct/dresources/vector_search_endpoint.go @@ -5,36 +5,17 @@ import ( "time" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/bundle/deployplan" "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/marshal" "github.com/databricks/databricks-sdk-go/service/vectorsearch" ) var ( pathBudgetPolicyId = structpath.MustParsePath("budget_policy_id") - pathMinQps = structpath.MustParsePath("min_qps") + pathTargetQps = structpath.MustParsePath("target_qps") ) -// VectorSearchEndpointState is persisted in deployment state. endpoint_uuid is -// tracked so out-of-band replacement of an endpoint with the same name can be -// detected: when saved UUID differs from remote UUID, the endpoint is recreated. -type VectorSearchEndpointState struct { - vectorsearch.CreateEndpoint - EndpointUuid string `json:"endpoint_uuid,omitempty"` -} - -// Custom marshalers required because embedded CreateEndpoint has its own. -func (s *VectorSearchEndpointState) UnmarshalJSON(b []byte) error { - return marshal.Unmarshal(b, s) -} - -func (s VectorSearchEndpointState) MarshalJSON() ([]byte, error) { - return marshal.Marshal(s) -} - // 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 { @@ -60,28 +41,22 @@ func (*ResourceVectorSearchEndpoint) New(client *databricks.WorkspaceClient) *Re return &ResourceVectorSearchEndpoint{client: client} } -func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *VectorSearchEndpointState { - return &VectorSearchEndpointState{ - CreateEndpoint: input.CreateEndpoint, - EndpointUuid: "", - } +func (*ResourceVectorSearchEndpoint) PrepareState(input *resources.VectorSearchEndpoint) *vectorsearch.CreateEndpoint { + return &input.CreateEndpoint } -func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *VectorSearchEndpointState { - var minQps int64 +func (*ResourceVectorSearchEndpoint) RemapState(remote *VectorSearchEndpointRemote) *vectorsearch.CreateEndpoint { + var targetQps int64 if remote.ScalingInfo != nil { - minQps = remote.ScalingInfo.RequestedMinQps + targetQps = remote.ScalingInfo.RequestedTargetQps } - return &VectorSearchEndpointState{ - CreateEndpoint: 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"), - }, - EndpointUuid: remote.EndpointUuid, + return &vectorsearch.CreateEndpoint{ + Name: remote.Name, + EndpointType: remote.EndpointType, + BudgetPolicyId: remote.BudgetPolicyId, + UsagePolicyId: "", // Missing in remote + TargetQps: targetQps, + ForceSendFields: utils.FilterFields[vectorsearch.CreateEndpoint](remote.ForceSendFields, "UsagePolicyId"), } } @@ -93,19 +68,16 @@ func (r *ResourceVectorSearchEndpoint) DoRead(ctx context.Context, id string) (* return newVectorSearchEndpointRemote(info), nil } -func (r *ResourceVectorSearchEndpoint) DoCreate(ctx context.Context, config *VectorSearchEndpointState) (string, *VectorSearchEndpointRemote, error) { - waiter, err := r.client.VectorSearchEndpoints.CreateEndpoint(ctx, config.CreateEndpoint) +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 - if waiter.Response != nil { - config.EndpointUuid = waiter.Response.Id - } return id, newVectorSearchEndpointRemote(waiter.Response), nil } -func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, config *VectorSearchEndpointState) (*VectorSearchEndpointRemote, error) { +func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, id string, config *vectorsearch.CreateEndpoint) (*VectorSearchEndpointRemote, error) { info, err := r.client.VectorSearchEndpoints.WaitGetEndpointVectorSearchEndpointOnline(ctx, config.Name, 60*time.Minute, nil) if err != nil { return nil, err @@ -113,7 +85,7 @@ func (r *ResourceVectorSearchEndpoint) WaitAfterCreate(ctx context.Context, conf return newVectorSearchEndpointRemote(info), nil } -func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, config *VectorSearchEndpointState, entry *PlanEntry) (*VectorSearchEndpointRemote, error) { +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, @@ -124,10 +96,10 @@ func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, } } - if entry.Changes.HasChange(pathMinQps) { + if entry.Changes.HasChange(pathTargetQps) { _, err := r.client.VectorSearchEndpoints.PatchEndpoint(ctx, vectorsearch.PatchEndpointRequest{ EndpointName: id, - MinQps: config.MinQps, + TargetQps: config.TargetQps, ForceSendFields: nil, }) if err != nil { @@ -135,36 +107,9 @@ func (r *ResourceVectorSearchEndpoint) DoUpdate(ctx context.Context, id string, } } - // Preserve endpoint_uuid in saved state: PrepareState leaves it empty because - // it isn't in config, so copy from remote before SaveState writes newState. - if remote, ok := entry.RemoteState.(*VectorSearchEndpointRemote); ok && remote != nil { - config.EndpointUuid = remote.EndpointUuid - } - return nil, nil } func (r *ResourceVectorSearchEndpoint) DoDelete(ctx context.Context, id string) error { return r.client.VectorSearchEndpoints.DeleteEndpointByEndpointName(ctx, id) } - -// OverrideChangeDesc classifies endpoint_uuid drift: Recreate when saved UUID -// differs from remote (endpoint replaced out-of-band), Skip otherwise. The -// field is not in config, so a synthetic diff between saved state and an empty -// newState is expected on every plan. -func (*ResourceVectorSearchEndpoint) OverrideChangeDesc(_ context.Context, path *structpath.PathNode, change *ChangeDesc, remote *VectorSearchEndpointRemote) error { - if path.String() != "endpoint_uuid" { - return nil - } - savedUuid, _ := change.Old.(string) - var remoteUuid string - if remote != nil { - remoteUuid = remote.EndpointUuid - } - if savedUuid != "" && remoteUuid != "" && savedUuid != remoteUuid { - change.Action = deployplan.Recreate - } else { - change.Action = deployplan.Skip - } - return nil -} diff --git a/bundle/direct/dstate/state.go b/bundle/direct/dstate/state.go index 3f6bcce2fc5..5b2a70adbb3 100644 --- a/bundle/direct/dstate/state.go +++ b/bundle/direct/dstate/state.go @@ -1,6 +1,8 @@ package dstate import ( + "bufio" + "bytes" "context" "encoding/json" "errors" @@ -14,23 +16,45 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/statemgmt/resourcestate" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/log" "github.com/google/uuid" ) -const currentStateVersion = 2 +const ( + currentStateVersion = 2 + initialBufferSize = 64 * 1024 + maxWalEntrySize = 10 * 1024 * 1024 + walSuffix = ".wal" +) + +// errStaleWAL is returned when the WAL serial is behind the expected serial. +// The caller should delete the stale WAL and proceed normally. +var errStaleWAL = errors.New("stale WAL") type DeploymentState struct { - Path string - Data Database - mu sync.Mutex + Path string + Data Database + mu sync.Mutex + walFile *os.File + + // Maps resource key to ID. Unlike Data.State, this is up to date during writes (deploys). + stateIDs map[string]string +} + +type Header struct { + StateVersion int `json:"state_version"` + CLIVersion string `json:"cli_version"` + Lineage string `json:"lineage"` + Serial int `json:"serial"` } type Database struct { - StateVersion int `json:"state_version"` - CLIVersion string `json:"cli_version"` - Lineage string `json:"lineage"` - Serial int `json:"serial"` - State map[string]ResourceEntry `json:"state"` + Header + + // Maps resource key to ResourceEntry which includes ID + full serialized state. + // This is not updated during write/deploy, those writes go to WAL instead. + // The State is then reconstructed from WAL. + State map[string]ResourceEntry `json:"state"` } type ResourceEntry struct { @@ -39,18 +63,25 @@ type ResourceEntry struct { DependsOn []deployplan.DependsOnEntry `json:"depends_on,omitempty"` } +type WALEntry struct { + Key string `json:"k"` + Value *ResourceEntry `json:"v,omitempty"` // nil means delete +} + func NewDatabase(lineage string, serial int) Database { return Database{ - StateVersion: currentStateVersion, - CLIVersion: build.GetInfo().Version, - Lineage: lineage, - Serial: serial, - State: make(map[string]ResourceEntry), + Header: Header{ + StateVersion: currentStateVersion, + CLIVersion: build.GetInfo().Version, + Lineage: lineage, + Serial: serial, + }, + State: make(map[string]ResourceEntry), } } func (db *DeploymentState) SaveState(key, newID string, state any, dependsOn []deployplan.DependsOnEntry) error { - db.AssertOpened() + db.AssertOpenedForWrite() db.mu.Lock() defer db.mu.Unlock() @@ -58,22 +89,27 @@ func (db *DeploymentState) SaveState(key, newID string, state any, dependsOn []d db.Data.State = make(map[string]ResourceEntry) } - jsonMessage, err := json.MarshalIndent(state, " ", " ") + // don't indent so that every WAL entry remains on a single line + jsonMessage, err := json.Marshal(state) if err != nil { return err } - db.Data.State[key] = ResourceEntry{ + entry := ResourceEntry{ ID: newID, State: json.RawMessage(jsonMessage), DependsOn: dependsOn, } - return nil + err = appendJSONLine(db.walFile, WALEntry{Key: key, Value: &entry}) + if err == nil { + db.stateIDs[key] = newID + } + return err } func (db *DeploymentState) DeleteState(key string) error { - db.AssertOpened() + db.AssertOpenedForWrite() db.mu.Lock() defer db.mu.Unlock() @@ -81,13 +117,16 @@ func (db *DeploymentState) DeleteState(key string) error { return nil } - delete(db.Data.State, key) - - return nil + err := appendJSONLine(db.walFile, WALEntry{Key: key}) + if err == nil { + delete(db.stateIDs, key) + } + return err } -func (db *DeploymentState) getResourceEntry(key string) (ResourceEntry, bool) { - db.AssertOpened() +func (db *DeploymentState) GetResourceEntry(key string) (ResourceEntry, bool) { + // Note, if opened for write, you get the state that you had at the beginning of deploy, not most recent one + db.AssertOpenedForReadOrWrite() db.mu.Lock() defer db.mu.Unlock() @@ -99,18 +138,25 @@ func (db *DeploymentState) getResourceEntry(key string) (ResourceEntry, bool) { return result, ok } -// GetResourceEntry returns the full resource entry for the given key. -func (db *DeploymentState) GetResourceEntry(key string) (ResourceEntry, bool) { - return db.getResourceEntry(key) -} - // GetResourceID returns the ID of the resource for the given key, or an empty string if not found. func (db *DeploymentState) GetResourceID(key string) string { - entry, _ := db.getResourceEntry(key) - return entry.ID + db.AssertOpenedForReadOrWrite() + db.mu.Lock() + defer db.mu.Unlock() + + return db.stateIDs[key] } -func (db *DeploymentState) Open(path string) error { +type ( + // If true, then Open reads the WAL and merges it in the state. If false, and WAL is present, Open returns an error. + WithRecovery bool + + // If true, the state is opened in Write mode, which enables methods such as SaveState + // but disables GetResourceEntry (since writes go strictly into WAL and not in memory). + WithWrite bool +) + +func (db *DeploymentState) Open(ctx context.Context, path string, withRecovery WithRecovery, withWrite WithWrite) error { db.mu.Lock() defer db.mu.Unlock() @@ -118,53 +164,280 @@ func (db *DeploymentState) Open(path string) error { panic(fmt.Sprintf("state already opened: %v, cannot open %v", db.Path, path)) } - data, err := os.ReadFile(path) + db.Path = path + data, err := os.ReadFile(db.Path) if err != nil { 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 - return nil + } else { + return err + } + } else { + if err := json.Unmarshal(data, &db.Data); err != nil { + return err } - return err } - err = json.Unmarshal(data, &db.Data) - if err != nil { - return err + db.stateIDs = make(map[string]string) + for key, entry := range db.Data.State { + db.stateIDs[key] = entry.ID + } + + walPath := db.Path + walSuffix + _, err = os.Stat(walPath) + switch { + case errors.Is(err, fs.ErrNotExist): + // no WAL, nothing to do + case err != nil: + return fmt.Errorf("failed to stat WAL file %s: %w", walPath, err) + default: // WAL exists + if withRecovery { + if err := db.replayWAL(ctx); err != nil { + return fmt.Errorf("reading state from %s: %w", path, err) + } + } else { + return fmt.Errorf("unexpected WAL file found at %s", walPath) + } } if err := migrateState(&db.Data); err != nil { return fmt.Errorf("migrating state %s: %w", path, err) } + if withWrite { + if err := os.MkdirAll(filepath.Dir(walPath), 0o755); err != nil { + return fmt.Errorf("failed to create state directory: %w", err) + } + walFile, err := os.OpenFile(walPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) + if err != nil { + return fmt.Errorf("failed to open WAL file %s: %w", walPath, err) + } + db.walFile = walFile + lineage := db.Data.Lineage + if lineage == "" { + // state file is new, does not have lineage yet; store lineage in the WAL only + lineage = uuid.New().String() + } + walHead := Header{ + Lineage: lineage, + Serial: db.Data.Serial + 1, + StateVersion: currentStateVersion, + CLIVersion: build.GetInfo().Version, + } + return appendJSONLine(db.walFile, walHead) + } + + return nil +} + +// OpenWithData initializes the state from an in-memory database without reading from disk. +// The state is opened in read mode; call UpgradeToWrite to transition to write mode. +func (db *DeploymentState) OpenWithData(path string, data Database) { + db.mu.Lock() + defer db.mu.Unlock() + + if db.Path != "" { + panic(fmt.Sprintf("state already opened: %v, cannot open %v", db.Path, path)) + } + db.Path = path + db.Data = data + db.stateIDs = make(map[string]string) + for key, entry := range data.State { + db.stateIDs[key] = entry.ID + } +} + +func (db *DeploymentState) replayWAL(ctx context.Context) error { + walPath := db.Path + walSuffix + hasEntries, err := db.mergeWalIntoState(ctx) + if err != nil { + if errors.Is(err, errStaleWAL) { + log.Debugf(ctx, "Deleting stale WAL file %s", walPath) + _ = os.Remove(walPath) + return nil + } + return fmt.Errorf("WAL recovery failed: %w", err) + } + if hasEntries { + if err := db.unlockedSave(); err != nil { + return err + } + } + if err := os.Remove(walPath); err != nil { + return fmt.Errorf("failed to remove WAL file %s: %w", walPath, err) + } return nil } -func (db *DeploymentState) Finalize() error { +func (db *DeploymentState) mergeWalIntoState(ctx context.Context) (bool, error) { + if db.walFile != nil { + panic("internal error: walFile must be closed") + } + + walPath := db.Path + walSuffix + walFile, err := os.Open(walPath) + if err != nil { + return false, fmt.Errorf("failed to open WAL file %s: %w", walPath, err) + } + defer walFile.Close() + + scanner := bufio.NewScanner(walFile) + scanner.Buffer(make([]byte, 0, initialBufferSize), maxWalEntrySize) + lineNumber := 0 + var corruptedLines [][]byte + + for scanner.Scan() { + lineNumber++ + line := scanner.Bytes() + if lineNumber == 1 { + var header Header + if err := json.Unmarshal(line, &header); err != nil { + return false, fmt.Errorf("failed to parse WAL header: %w", err) + } + + if db.Data.Lineage == "" && header.Lineage != "" { + db.Data.Lineage = header.Lineage + } else if db.Data.Lineage != header.Lineage { + return false, fmt.Errorf("WAL lineage (%q) does not match state lineage (%q)", header.Lineage, db.Data.Lineage) + } + + expectedSerial := db.Data.Serial + 1 + if header.Serial < expectedSerial { + return false, errStaleWAL + } + if header.Serial > expectedSerial { + return false, fmt.Errorf("WAL serial (%d) is ahead of expected (%d), state may be corrupted", header.Serial, expectedSerial) + } + db.Data.Serial = expectedSerial + } else { + var entry WALEntry + if err := json.Unmarshal(line, &entry); err != nil { + log.Warnf(ctx, "Skipping corrupted WAL entry at %s:%d: %v", walPath, lineNumber, err) + corruptedLines = append(corruptedLines, append([]byte(nil), line...)) + continue + } + if db.Data.State == nil { + db.Data.State = make(map[string]ResourceEntry) + } + if entry.Value == nil { + delete(db.Data.State, entry.Key) + delete(db.stateIDs, entry.Key) + } else { + db.Data.State[entry.Key] = *entry.Value + db.stateIDs[entry.Key] = entry.Value.ID + } + } + } + + if err := scanner.Err(); err != nil { + return false, err + } + + if len(corruptedLines) > 0 { + corruptedPath := walPath + ".corrupted" + corruptedData := bytes.Join(corruptedLines, []byte("\n")) + if writeErr := os.WriteFile(corruptedPath, corruptedData, 0o600); writeErr != nil { + log.Warnf(ctx, "Failed to save corrupted WAL entries to %s: %v", corruptedPath, writeErr) + } else { + log.Warnf(ctx, "Saved %d corrupted WAL entries to %s", len(corruptedLines), corruptedPath) + } + } + + return lineNumber > 1, nil +} + +// Finalize replays the WAL (if open for write), captures the resulting state, and resets. +// Safe to call multiple times or on an already-finalized state. +// Returns the exported state as of the end of this operation. +func (db *DeploymentState) Finalize(ctx context.Context) (resourcestate.ExportedResourcesMap, error) { db.mu.Lock() defer db.mu.Unlock() - // Generate lineage on first save - if db.Data.Lineage == "" { - db.Data.Lineage = uuid.New().String() + if db.Path == "" { + return nil, nil + } + + var err error + + if db.walFile != nil { + closeErr := db.walFile.Close() + if closeErr != nil { + log.Warnf(ctx, "Error when closing .wal file, possibly corrupted state file: %s", closeErr) + } + db.walFile = nil + err = db.replayWAL(ctx) } - db.Data.Serial++ + state := ExportStateFromData(db.Data) + + db.Path = "" + db.Data = Database{} + db.stateIDs = nil - return db.unlockedSave() + return state, err } -func (db *DeploymentState) AssertOpened() { +// UpgradeToWrite transitions from read mode to write mode without re-reading state. +// State must already be open for read. This initializes the WAL for writing. +func (db *DeploymentState) UpgradeToWrite() error { + db.mu.Lock() + defer db.mu.Unlock() + + if db.Path == "" { + return errors.New("internal error: DeploymentState must be opened first") + } + if db.walFile != nil { + return errors.New("internal error: DeploymentState is already open for write") + } + + walPath := db.Path + walSuffix + if err := os.MkdirAll(filepath.Dir(walPath), 0o755); err != nil { + return fmt.Errorf("failed to create state directory: %w", err) + } + walFile, err := os.OpenFile(walPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o600) + if err != nil { + return fmt.Errorf("failed to open WAL file %s: %w", walPath, err) + } + db.walFile = walFile + + lineage := db.Data.Lineage + if lineage == "" { + lineage = uuid.New().String() + } + walHead := Header{ + Lineage: lineage, + Serial: db.Data.Serial + 1, + StateVersion: currentStateVersion, + CLIVersion: build.GetInfo().Version, + } + return appendJSONLine(db.walFile, walHead) +} + +func (db *DeploymentState) AssertOpenedForReadOrWrite() { if db.Path == "" { panic("internal error: DeploymentState must be opened first") } } -func (db *DeploymentState) ExportState(ctx context.Context) resourcestate.ExportedResourcesMap { +func (db *DeploymentState) AssertOpenedForRead() { + db.AssertOpenedForReadOrWrite() + if db.walFile != nil { + panic("internal error: DeploymentState must be opened in read mode") + } +} + +func (db *DeploymentState) AssertOpenedForWrite() { + db.AssertOpenedForReadOrWrite() + if db.walFile == nil { + panic("internal error: DeploymentState must be opened in write mode") + } +} + +// ExportStateFromData extracts resource IDs and ETags from a database snapshot. +func ExportStateFromData(data Database) resourcestate.ExportedResourcesMap { result := make(resourcestate.ExportedResourcesMap) - for key, entry := range db.Data.State { + for key, entry := range data.State { var etag string // Extract etag for dashboards. // covered by test case: bundle/deploy/dashboard/detect-change @@ -185,14 +458,16 @@ func (db *DeploymentState) ExportState(ctx context.Context) resourcestate.Export return result } +func (db *DeploymentState) ExportState(ctx context.Context) resourcestate.ExportedResourcesMap { + return ExportStateFromData(db.Data) +} + func (db *DeploymentState) unlockedSave() error { - db.AssertOpened() data, err := json.MarshalIndent(db.Data, "", " ") if err != nil { return err } - // Create parent directories if they don't exist dir := filepath.Dir(db.Path) if err := os.MkdirAll(dir, 0o755); err != nil { return fmt.Errorf("failed to create directory %#v: %w", dir, err) @@ -205,3 +480,15 @@ func (db *DeploymentState) unlockedSave() error { return nil } + +func appendJSONLine(file *os.File, obj any) error { + data, err := json.Marshal(obj) + if err != nil { + return err + } + data = append(data, '\n') + + _, err = file.Write(data) + // no fsync here, not needed + return err +} diff --git a/bundle/direct/dstate/state_test.go b/bundle/direct/dstate/state_test.go index acd2a9e5336..afe8634790a 100644 --- a/bundle/direct/dstate/state_test.go +++ b/bundle/direct/dstate/state_test.go @@ -1,6 +1,7 @@ package dstate import ( + "os" "path/filepath" "testing" @@ -8,46 +9,68 @@ import ( "github.com/stretchr/testify/require" ) +func mustFinalize(t *testing.T, db *DeploymentState) { + t.Helper() + _, err := db.Finalize(t.Context()) + require.NoError(t, err) +} + func TestOpenSaveFinalizeRoundTrip(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") var db DeploymentState - require.NoError(t, db.Open(path)) + require.NoError(t, db.Open(t.Context(), path, WithRecovery(true), WithWrite(true))) require.NoError(t, db.SaveState("jobs.my_job", "123", map[string]string{"key": "val"}, nil)) - require.NoError(t, db.Finalize()) + mustFinalize(t, &db) // Re-open and verify persisted data. var db2 DeploymentState - require.NoError(t, db2.Open(path)) + require.NoError(t, db2.Open(t.Context(), path, WithRecovery(false), WithWrite(false))) assert.Equal(t, 1, db2.Data.Serial) assert.Equal(t, "123", db2.GetResourceID("jobs.my_job")) + mustFinalize(t, &db2) +} + +func TestFinalizeWithNoEntriesDoesNotWriteStateFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "state.json") + + var db DeploymentState + require.NoError(t, db.Open(t.Context(), path, WithRecovery(true), WithWrite(true))) + mustFinalize(t, &db) + + _, err := os.Stat(path) + assert.ErrorIs(t, err, os.ErrNotExist) } func TestPanicOnDoubleOpen(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") var db DeploymentState - require.NoError(t, db.Open(path)) + require.NoError(t, db.Open(t.Context(), path, WithRecovery(true), WithWrite(true))) assert.Panics(t, func() { - _ = db.Open(path) + _ = db.Open(t.Context(), path, WithRecovery(true), WithWrite(true)) }) + mustFinalize(t, &db) } func TestDeleteState(t *testing.T) { path := filepath.Join(t.TempDir(), "state.json") var db DeploymentState - require.NoError(t, db.Open(path)) + require.NoError(t, db.Open(t.Context(), path, WithRecovery(true), WithWrite(true))) require.NoError(t, db.SaveState("jobs.my_job", "123", map[string]string{}, nil)) - require.NoError(t, db.Finalize()) - - require.NoError(t, db.DeleteState("jobs.my_job")) - require.NoError(t, db.Finalize()) + mustFinalize(t, &db) var db2 DeploymentState - require.NoError(t, db2.Open(path)) - assert.Equal(t, 2, db2.Data.Serial) - assert.Equal(t, "", db2.GetResourceID("jobs.my_job")) + require.NoError(t, db2.Open(t.Context(), path, WithRecovery(true), WithWrite(true))) + require.NoError(t, db2.DeleteState("jobs.my_job")) + mustFinalize(t, &db2) + + var db3 DeploymentState + require.NoError(t, db3.Open(t.Context(), path, WithRecovery(false), WithWrite(false))) + assert.Equal(t, 2, db3.Data.Serial) + assert.Equal(t, "", db3.GetResourceID("jobs.my_job")) + mustFinalize(t, &db3) } diff --git a/bundle/direct/pkg.go b/bundle/direct/pkg.go index 58b9bc6b4b1..48a9c5a2ff7 100644 --- a/bundle/direct/pkg.go +++ b/bundle/direct/pkg.go @@ -64,7 +64,9 @@ func (d *DeploymentUnit) SetRemoteState(remoteState any) error { return nil } +// ExportState exports the current deployment state as a resource map. +// StateDB must already be open for read before calling this function. func (b *DeploymentBundle) ExportState(ctx context.Context) resourcestate.ExportedResourcesMap { - b.StateDB.AssertOpened() + b.StateDB.AssertOpenedForRead() return b.StateDB.ExportState(ctx) } diff --git a/bundle/direct/util.go b/bundle/direct/util.go index 43c39c9a67f..3b4c9abd651 100644 --- a/bundle/direct/util.go +++ b/bundle/direct/util.go @@ -9,3 +9,19 @@ import ( func isResourceGone(err error) bool { return errors.Is(err, apierr.ErrResourceDoesNotExist) || errors.Is(err, apierr.ErrNotFound) } + +// isManagedByParent reports whether err is an API error carrying the +// declarative_context=MANAGED_BY_PARENT marker in ErrorInfo.metadata. The +// server uses this to signal that a resource's lifecycle is owned by a +// parent (e.g. a Lakebase RW endpoint inside a branch, or a root branch +// inside a project) and the standalone Delete can be safely disregarded — +// the parent's Delete will cascade-clean. Mirrors the TF provider's +// declarative.IsDeleteError suppression. +func isManagedByParent(err error) bool { + var apiErr *apierr.APIError + if !errors.As(err, &apiErr) || apiErr == nil { + return false + } + info := apiErr.ErrorDetails().ErrorInfo + return info != nil && info.Metadata["declarative_context"] == "MANAGED_BY_PARENT" +} diff --git a/bundle/docsgen/nodes_test.go b/bundle/docsgen/nodes_test.go index 028bf1eff62..227d1bc01b3 100644 --- a/bundle/docsgen/nodes_test.go +++ b/bundle/docsgen/nodes_test.go @@ -25,7 +25,7 @@ func TestBuildNodes_ChildExpansion(t *testing.T) { Items: &jsonschema.Schema{ Type: "object", Properties: map[string]*jsonschema.Schema{ - "listSub": {Reference: strPtr("#/$defs/github.com/listSub")}, + "listSub": {Reference: new("#/$defs/github.com/listSub")}, }, }, }, @@ -61,9 +61,9 @@ func TestBuildNodes_ChildExpansion(t *testing.T) { "myMap": { Type: "object", AdditionalProperties: &jsonschema.Schema{ - Reference: strPtr("#/$defs/github.com/myMap"), + Reference: new("#/$defs/github.com/myMap"), Properties: map[string]*jsonschema.Schema{ - "mapSub": {Type: "object", Reference: strPtr("#/$defs/github.com/mapSub")}, + "mapSub": {Type: "object", Reference: new("#/$defs/github.com/mapSub")}, }, }, }, @@ -73,7 +73,7 @@ func TestBuildNodes_ChildExpansion(t *testing.T) { "github.com/myMap": { Type: "object", Properties: map[string]*jsonschema.Schema{ - "mapSub": {Type: "boolean", Reference: strPtr("#/$defs/github.com/mapSub")}, + "mapSub": {Type: "boolean", Reference: new("#/$defs/github.com/mapSub")}, }, }, "github.com/mapSub": { @@ -170,7 +170,3 @@ func TestDoNotSuggestFields(t *testing.T) { assert.Len(t, nodes[0].Attributes, 1) assert.Equal(t, "nestedNotDoNotSuggestField", nodes[0].Attributes[0].Title) } - -func strPtr(s string) *string { - return &s -} diff --git a/bundle/docsgen/output/.gitattributes b/bundle/docsgen/output/.gitattributes new file mode 100644 index 00000000000..4a85861fe69 --- /dev/null +++ b/bundle/docsgen/output/.gitattributes @@ -0,0 +1 @@ +*.md linguist-generated=true diff --git a/bundle/docsgen/output/reference.md b/bundle/docsgen/output/reference.md index ea8f922575e..c88824d79ab 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: 2026-04-23 + date: 2026-05-21 --- @@ -495,6 +495,10 @@ resources: - Map - See [\_](#resourcespostgres_branches). +- - `postgres_catalogs` + - Map + - The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. See [\_](#resourcespostgres_catalogs). + - - `postgres_endpoints` - Map - See [\_](#resourcespostgres_endpoints). @@ -503,6 +507,10 @@ resources: - Map - See [\_](#resourcespostgres_projects). +- - `postgres_synced_tables` + - Map + - The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. See [\_](#resourcespostgres_synced_tables). + - - `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). @@ -825,6 +833,10 @@ apps: - Sequence - See [\_](#resourcesappsnametelemetry_export_destinations). +- - `thumbnail_url` + - String + - + - - `update_time` - String - @@ -1517,6 +1529,10 @@ postgres_branches: - String - +- - `replace_existing` + - Boolean + - + - - `source_branch` - String - @@ -1544,6 +1560,69 @@ postgres_branches: +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### resources.postgres_catalogs + +**`Type: Map`** + +The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. + +```yaml +postgres_catalogs: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `catalog_id` + - String + - + +- - `create_database_if_missing` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#resourcespostgres_catalogsnamelifecycle). + +- - `postgres_database` + - String + - + +::: + + +### resources.postgres_catalogs._name_.lifecycle + +**`Type: Map`** + + + + + :::list-table - - Key @@ -1612,6 +1691,10 @@ postgres_endpoints: - String - +- - `replace_existing` + - Boolean + - + - - `settings` - Map - See [\_](#resourcespostgres_endpointsnamesettings). @@ -1764,6 +1847,93 @@ postgres_projects: ::: +### resources.postgres_synced_tables + +**`Type: Map`** + +The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. + +```yaml +postgres_synced_tables: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `create_database_objects_if_missing` + - Boolean + - + +- - `existing_pipeline_id` + - String + - + +- - `lifecycle` + - Map + - See [\_](#resourcespostgres_synced_tablesnamelifecycle). + +- - `new_pipeline_spec` + - Map + - See [\_](#resourcespostgres_synced_tablesnamenew_pipeline_spec). + +- - `postgres_database` + - String + - + +- - `primary_key_columns` + - Sequence + - + +- - `scheduling_policy` + - String + - + +- - `source_table_full_name` + - String + - + +- - `synced_table_id` + - String + - + +- - `timeseries_key` + - String + - + +::: + + +### resources.postgres_synced_tables._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + ### resources.secret_scopes **`Type: Map`** @@ -1970,10 +2140,6 @@ vector_search_endpoints: - Map - See [\_](#resourcesvector_search_endpointsnamelifecycle). -- - `min_qps` - - Integer - - - - - `name` - String - @@ -1982,6 +2148,10 @@ vector_search_endpoints: - Sequence - See [\_](#resourcesvector_search_endpointsnamepermissions). +- - `target_qps` + - Integer + - + - - `usage_policy_id` - String - @@ -2569,6 +2739,10 @@ The resource definitions for the target. - Map - See [\_](#targetsnameresourcespostgres_branches). +- - `postgres_catalogs` + - Map + - The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. See [\_](#targetsnameresourcespostgres_catalogs). + - - `postgres_endpoints` - Map - See [\_](#targetsnameresourcespostgres_endpoints). @@ -2577,6 +2751,10 @@ The resource definitions for the target. - Map - See [\_](#targetsnameresourcespostgres_projects). +- - `postgres_synced_tables` + - Map + - The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. See [\_](#targetsnameresourcespostgres_synced_tables). + - - `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). @@ -2899,6 +3077,10 @@ apps: - Sequence - See [\_](#targetsnameresourcesappsnametelemetry_export_destinations). +- - `thumbnail_url` + - String + - + - - `update_time` - String - @@ -3591,6 +3773,10 @@ postgres_branches: - String - +- - `replace_existing` + - Boolean + - + - - `source_branch` - String - @@ -3618,6 +3804,69 @@ postgres_branches: +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +### targets._name_.resources.postgres_catalogs + +**`Type: Map`** + +The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. + +```yaml +postgres_catalogs: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `catalog_id` + - String + - + +- - `create_database_if_missing` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcespostgres_catalogsnamelifecycle). + +- - `postgres_database` + - String + - + +::: + + +### targets._name_.resources.postgres_catalogs._name_.lifecycle + +**`Type: Map`** + + + + + :::list-table - - Key @@ -3686,6 +3935,10 @@ postgres_endpoints: - String - +- - `replace_existing` + - Boolean + - + - - `settings` - Map - See [\_](#targetsnameresourcespostgres_endpointsnamesettings). @@ -3838,6 +4091,93 @@ postgres_projects: ::: +### targets._name_.resources.postgres_synced_tables + +**`Type: Map`** + +The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. + +```yaml +postgres_synced_tables: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `create_database_objects_if_missing` + - Boolean + - + +- - `existing_pipeline_id` + - String + - + +- - `lifecycle` + - Map + - See [\_](#targetsnameresourcespostgres_synced_tablesnamelifecycle). + +- - `new_pipeline_spec` + - Map + - See [\_](#targetsnameresourcespostgres_synced_tablesnamenew_pipeline_spec). + +- - `postgres_database` + - String + - + +- - `primary_key_columns` + - Sequence + - + +- - `scheduling_policy` + - String + - + +- - `source_table_full_name` + - String + - + +- - `synced_table_id` + - String + - + +- - `timeseries_key` + - String + - + +::: + + +### targets._name_.resources.postgres_synced_tables._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + ### targets._name_.resources.secret_scopes **`Type: Map`** @@ -4044,10 +4384,6 @@ vector_search_endpoints: - Map - See [\_](#targetsnameresourcesvector_search_endpointsnamelifecycle). -- - `min_qps` - - Integer - - - - - `name` - String - @@ -4056,6 +4392,10 @@ vector_search_endpoints: - Sequence - See [\_](#targetsnameresourcesvector_search_endpointsnamepermissions). +- - `target_qps` + - Integer + - + - - `usage_policy_id` - String - diff --git a/bundle/docsgen/output/resources.md b/bundle/docsgen/output/resources.md index e262f4620b6..1ab8ef813d7 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: 2026-04-23 + date: 2026-05-21 --- @@ -544,6 +544,10 @@ apps: - String - The description of the app. +- - `git_repository` + - Map + - Git repository configuration for app deployments. When specified, deployments can reference code from this repository by providing only the git reference (branch, tag, or commit). See [\_](#appsnamegit_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 [\_](#appsnamegit_source). @@ -637,6 +641,32 @@ apps: ::: +### apps._name_.git_repository + +**`Type: Map`** + +Git repository configuration for app deployments. When specified, deployments can +reference code from this repository by providing only the git reference (branch, tag, or commit). + + + +:::list-table + +- - Key + - Type + - Description + +- - `provider` + - String + - Git provider. Case insensitive. Supported values: gitHub, gitHubEnterprise, bitbucketCloud, bitbucketServer, azureDevOpsServices, gitLab, gitLabEnterpriseEdition, awsCodeCommit. + +- - `url` + - String + - URL of the Git repository. + +::: + + ### apps._name_.git_source **`Type: Map`** @@ -1150,6 +1180,10 @@ catalogs: - Map - See [\_](#catalogsnamelifecycle). +- - `managed_encryption_settings` + - Map + - Control CMK encryption for managed catalog data. See [\_](#catalogsnamemanaged_encryption_settings). + - - `name` - String - @@ -1230,6 +1264,64 @@ The privileges assigned to the principal. ::: +### catalogs._name_.managed_encryption_settings + +**`Type: Map`** + +Control CMK encryption for managed catalog data + + + +:::list-table + +- - Key + - Type + - Description + +- - `azure_encryption_settings` + - Map + - optional Azure settings - only required if an Azure CMK is used. See [\_](#catalogsnamemanaged_encryption_settingsazure_encryption_settings). + +- - `azure_key_vault_key_id` + - String + - the AKV URL in Azure, null otherwise. + +- - `customer_managed_key_id` + - String + - the CMK uuid in AWS and GCP, null otherwise. + +::: + + +### catalogs._name_.managed_encryption_settings.azure_encryption_settings + +**`Type: Map`** + +optional Azure settings - only required if an Azure CMK is used. + + + +:::list-table + +- - Key + - Type + - Description + +- - `azure_cmk_access_connector_id` + - String + - + +- - `azure_cmk_managed_identity_id` + - String + - + +- - `azure_tenant_id` + - String + - + +::: + + ## clusters **`Type: Map`** @@ -3281,7 +3373,7 @@ For other serverless tasks, the task environment is required to be specified usi - - `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). + - The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported. See [\_](#jobsnameenvironmentsspec). ::: @@ -3290,7 +3382,7 @@ For other serverless tasks, the task environment is required to be specified usi **`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. +The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported. @@ -4588,6 +4680,10 @@ Read endpoints return only 100 tasks. If more than 100 tasks are available, you - Map - The task triggers a Power BI semantic model update when the `power_bi_task` field is present. See [\_](#jobsnametaskspower_bi_task). +- - `python_operator_task` + - Map + - See [\_](#jobsnametaskspython_operator_task). + - - `python_wheel_task` - Map - The task runs a Python wheel when the `python_wheel_task` field is present. See [\_](#jobsnametaskspython_wheel_task). @@ -6068,12 +6164,28 @@ The task triggers a pipeline update when the `pipeline_task` field is present. O - - `full_refresh` - Boolean - - If true, triggers a full refresh on the delta live table. + - If true, triggers a full refresh on the spark declarative pipeline. + +- - `full_refresh_selection` + - Sequence + - - - `pipeline_id` - String - The full name of the pipeline task to execute. +- - `refresh_flow_selection` + - Sequence + - + +- - `refresh_selection` + - Sequence + - + +- - `reset_checkpoint_selection` + - Sequence + - + ::: @@ -6262,7 +6374,23 @@ Controls whether the pipeline should perform a full refresh - - `full_refresh` - Boolean - - If true, triggers a full refresh on the delta live table. + - If true, triggers a full refresh on the spark declarative pipeline. + +- - `full_refresh_selection` + - Sequence + - + +- - `refresh_flow_selection` + - Sequence + - + +- - `refresh_selection` + - Sequence + - + +- - `reset_checkpoint_selection` + - Sequence + - ::: @@ -7329,7 +7457,7 @@ The core config of the serving endpoint. - - `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). + - This field is deprecated - - `served_entities` - Sequence @@ -7346,42 +7474,6 @@ The core config of the serving endpoint. ::: -### 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`** @@ -8262,7 +8354,7 @@ pipelines: - - `channel` - String - - DLT Release Channel that specifies which version to use. + - SDP Release Channel that specifies which version to use. - - `clusters` - Sequence @@ -9128,6 +9220,10 @@ The configuration for a managed ingestion pipeline. These settings cannot be use - Map - (Optional) A window that specifies a set of time ranges for snapshot queries in CDC. See [\_](#pipelinesnameingestion_definitionfull_refresh_window). +- - `ingest_from_uc_foreign_catalog` + - Boolean + - Immutable. If set to true, the pipeline will ingest tables from the UC foreign catalogs directly without the need to specify a UC connection or ingestion gateway. The `source_catalog` fields in objects of IngestionConfig are interpreted as the UC foreign catalogs to ingest from. + - - `ingestion_gateway_id` - String - 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. @@ -9280,6 +9376,18 @@ Configuration settings to control the ingestion of tables. These settings overri - Sequence - The primary key of the table used to apply changes. +- - `query_based_connector_config` + - Map + - Configurations that are only applicable for query-based ingestion connectors. See [\_](#pipelinesnameingestion_definitionobjectsreporttable_configurationquery_based_connector_config). + +- - `row_filter` + - String + - (Optional, Immutable) The row filter condition to be applied to the table. It must not contain the WHERE keyword, only the actual filter condition. It must be in DBSQL format. + +- - `scd_type` + - String + - The SCD type to use to ingest the table. + - - `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. @@ -9322,6 +9430,35 @@ If unspecified, auto full refresh is disabled. ::: +### pipelines._name_.ingestion_definition.objects.report.table_configuration.query_based_connector_config + +**`Type: Map`** + +Configurations that are only applicable for query-based ingestion connectors. + + + +:::list-table + +- - Key + - Type + - Description + +- - `cursor_columns` + - Sequence + - The names of the monotonically increasing columns in the source table that are used to enable the table to be read and ingested incrementally through structured streaming. The columns are allowed to have repeated values but have to be non-decreasing. If the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these columns will implicitly define the `sequence_by` behavior. You can still explicitly set `sequence_by` to override this default. + +- - `deletion_condition` + - String + - Specifies a SQL WHERE condition that specifies that the source row has been deleted. This is sometimes referred to as "soft-deletes". For example: "Operation = 'DELETE'" or "is_deleted = true". This field is orthogonal to `hard_deletion_sync_interval_in_seconds`, one for soft-deletes and the other for hard-deletes. See also the hard_deletion_sync_min_interval_in_seconds field for handling of "hard deletes" where the source rows are physically removed from the table. + +- - `hard_deletion_sync_min_interval_in_seconds` + - Integer + - Specifies the minimum interval (in seconds) between snapshots on primary keys for detecting and synchronizing hard deletions—i.e., rows that have been physically removed from the source table. This interval acts as a lower bound. If ingestion runs less frequently than this value, hard deletion synchronization will align with the actual ingestion frequency instead of happening more often. If not set, hard deletion synchronization via snapshots is disabled. This field is mutable and can be updated without triggering a full snapshot. + +::: + + ### pipelines._name_.ingestion_definition.objects.schema **`Type: Map`** @@ -9336,6 +9473,10 @@ Select all tables from a specific source schema. - Type - Description +- - `connector_options` + - Map + - (Optional) Source Specific Connector Options. See [\_](#pipelinesnameingestion_definitionobjectsschemaconnector_options). + - - `destination_catalog` - String - Required. Destination catalog to store tables. @@ -9359,11 +9500,11 @@ Select all tables from a specific source schema. ::: -### pipelines._name_.ingestion_definition.objects.schema.table_configuration +### pipelines._name_.ingestion_definition.objects.schema.connector_options **`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. +(Optional) Source Specific Connector Options @@ -9373,44 +9514,51 @@ Configuration settings to control the ingestion of tables. These settings are ap - Type - Description -- - `auto_full_refresh_policy` +- - `confluence_options` - 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). + - Confluence specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectsschemaconnector_optionsconfluence_options). -- - `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`. +- - `jira_options` + - Map + - Jira specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectsschemaconnector_optionsjira_options). -- - `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`. +- - `kafka_options` + - Map + - See [\_](#pipelinesnameingestion_definitionobjectsschemaconnector_optionskafka_options). -- - `primary_keys` - - Sequence - - The primary key of the table used to apply changes. +- - `meta_ads_options` + - Map + - Meta Marketing (Meta Ads) specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectsschemaconnector_optionsmeta_ads_options). -- - `sequence_by` +::: + + +### pipelines._name_.ingestion_definition.objects.schema.connector_options.confluence_options + +**`Type: Map`** + +Confluence specific options for ingestion + + + +:::list-table + +- - Key + - Type + - Description + +- - `include_confluence_spaces` - 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. + - (Optional) Spaces to filter Confluence data on ::: -### pipelines._name_.ingestion_definition.objects.schema.table_configuration.auto_full_refresh_policy +### pipelines._name_.ingestion_definition.objects.schema.connector_options.jira_options **`Type: 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. +Jira specific options for ingestion @@ -9420,22 +9568,18 @@ If unspecified, auto full refresh is disabled. - Type - Description -- - `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. +- - `include_jira_spaces` + - Sequence + - (Optional) Projects to filter Jira data on ::: -### pipelines._name_.ingestion_definition.objects.table +### pipelines._name_.ingestion_definition.objects.schema.connector_options.meta_ads_options **`Type: Map`** -Select a specific source table. +Meta Marketing (Meta Ads) specific options for ingestion @@ -9445,42 +9589,46 @@ Select a specific source table. - Type - Description -- - `destination_catalog` - - String - - Required. Destination catalog to store table. +- - `action_attribution_windows` + - Sequence + - (Optional) Action attribution windows for insights reporting (e.g. "28d_click", "1d_view") -- - `destination_schema` - - String - - Required. Destination schema to store table. +- - `action_breakdowns` + - Sequence + - (Optional) Action breakdowns to configure for data aggregation -- - `destination_table` +- - `action_report_time` - 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. + - (Optional) Timing used to report action statistics (impression, conversion, mixed, or lifetime) -- - `source_catalog` - - String - - Source catalog name. Might be optional depending on the type of source. +- - `breakdowns` + - Sequence + - (Optional) Breakdowns to configure for data aggregation -- - `source_schema` +- - `custom_insights_lookback_window` + - Integer + - (Optional) Window in days to revisit data during sync to capture updated conversion data from the API. + +- - `level` - String - - Schema name in the source database. Might be optional depending on the type of source. + - (Optional) Granularity of data to pull (account, ad, adset, campaign) -- - `source_table` +- - `start_date` - String - - Required. Table name in the source database. + - (Optional) Start date in yyyy-MM-dd format (e.g. 2025-01-15). Data added after this date will be ingested -- - `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). +- - `time_increment` + - String + - (Optional) Value in string by which to aggregate statistics (can take all_days, monthly or number of days) ::: -### pipelines._name_.ingestion_definition.objects.table.table_configuration +### pipelines._name_.ingestion_definition.objects.schema.table_configuration **`Type: Map`** -Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. +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. @@ -9492,7 +9640,7 @@ Configuration settings to control the ingestion of tables. These settings overri - - `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_definitionobjectstabletable_configurationauto_full_refresh_policy). + - (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 @@ -9506,6 +9654,18 @@ Configuration settings to control the ingestion of tables. These settings overri - Sequence - The primary key of the table used to apply changes. +- - `query_based_connector_config` + - Map + - Configurations that are only applicable for query-based ingestion connectors. See [\_](#pipelinesnameingestion_definitionobjectsschematable_configurationquery_based_connector_config). + +- - `row_filter` + - String + - (Optional, Immutable) The row filter condition to be applied to the table. It must not contain the WHERE keyword, only the actual filter condition. It must be in DBSQL format. + +- - `scd_type` + - String + - The SCD type to use to ingest the table. + - - `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. @@ -9513,7 +9673,7 @@ Configuration settings to control the ingestion of tables. These settings overri ::: -### pipelines._name_.ingestion_definition.objects.table.table_configuration.auto_full_refresh_policy +### pipelines._name_.ingestion_definition.objects.schema.table_configuration.auto_full_refresh_policy **`Type: Map`** @@ -9548,11 +9708,11 @@ If unspecified, auto full refresh is disabled. ::: -### pipelines._name_.ingestion_definition.source_configurations +### pipelines._name_.ingestion_definition.objects.schema.table_configuration.query_based_connector_config -**`Type: Sequence`** +**`Type: Map`** -Top-level source configurations +Configurations that are only applicable for query-based ingestion connectors. @@ -9562,8 +9722,323 @@ Top-level source configurations - Type - Description -- - `catalog` - - Map +- - `cursor_columns` + - Sequence + - The names of the monotonically increasing columns in the source table that are used to enable the table to be read and ingested incrementally through structured streaming. The columns are allowed to have repeated values but have to be non-decreasing. If the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these columns will implicitly define the `sequence_by` behavior. You can still explicitly set `sequence_by` to override this default. + +- - `deletion_condition` + - String + - Specifies a SQL WHERE condition that specifies that the source row has been deleted. This is sometimes referred to as "soft-deletes". For example: "Operation = 'DELETE'" or "is_deleted = true". This field is orthogonal to `hard_deletion_sync_interval_in_seconds`, one for soft-deletes and the other for hard-deletes. See also the hard_deletion_sync_min_interval_in_seconds field for handling of "hard deletes" where the source rows are physically removed from the table. + +- - `hard_deletion_sync_min_interval_in_seconds` + - Integer + - Specifies the minimum interval (in seconds) between snapshots on primary keys for detecting and synchronizing hard deletions—i.e., rows that have been physically removed from the source table. This interval acts as a lower bound. If ingestion runs less frequently than this value, hard deletion synchronization will align with the actual ingestion frequency instead of happening more often. If not set, hard deletion synchronization via snapshots is disabled. This field is mutable and can be updated without triggering a full snapshot. + +::: + + +### pipelines._name_.ingestion_definition.objects.table + +**`Type: Map`** + +Select a specific source table. + + + +:::list-table + +- - Key + - Type + - Description + +- - `connector_options` + - Map + - (Optional) Source Specific Connector Options. See [\_](#pipelinesnameingestion_definitionobjectstableconnector_options). + +- - `destination_catalog` + - String + - Required. Destination catalog to store table. + +- - `destination_schema` + - String + - Required. Destination schema to store table. + +- - `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. + +- - `source_catalog` + - String + - Source catalog name. Might be optional depending on the type of source. + +- - `source_schema` + - String + - Schema name in the source database. Might be optional depending on the type of source. + +- - `source_table` + - String + - 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_.ingestion_definition.objects.table.connector_options + +**`Type: Map`** + +(Optional) Source Specific Connector Options + + + +:::list-table + +- - Key + - Type + - Description + +- - `confluence_options` + - Map + - Confluence specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectstableconnector_optionsconfluence_options). + +- - `jira_options` + - Map + - Jira specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectstableconnector_optionsjira_options). + +- - `kafka_options` + - Map + - See [\_](#pipelinesnameingestion_definitionobjectstableconnector_optionskafka_options). + +- - `meta_ads_options` + - Map + - Meta Marketing (Meta Ads) specific options for ingestion. See [\_](#pipelinesnameingestion_definitionobjectstableconnector_optionsmeta_ads_options). + +::: + + +### pipelines._name_.ingestion_definition.objects.table.connector_options.confluence_options + +**`Type: Map`** + +Confluence specific options for ingestion + + + +:::list-table + +- - Key + - Type + - Description + +- - `include_confluence_spaces` + - Sequence + - (Optional) Spaces to filter Confluence data on + +::: + + +### pipelines._name_.ingestion_definition.objects.table.connector_options.jira_options + +**`Type: Map`** + +Jira specific options for ingestion + + + +:::list-table + +- - Key + - Type + - Description + +- - `include_jira_spaces` + - Sequence + - (Optional) Projects to filter Jira data on + +::: + + +### pipelines._name_.ingestion_definition.objects.table.connector_options.meta_ads_options + +**`Type: Map`** + +Meta Marketing (Meta Ads) specific options for ingestion + + + +:::list-table + +- - Key + - Type + - Description + +- - `action_attribution_windows` + - Sequence + - (Optional) Action attribution windows for insights reporting (e.g. "28d_click", "1d_view") + +- - `action_breakdowns` + - Sequence + - (Optional) Action breakdowns to configure for data aggregation + +- - `action_report_time` + - String + - (Optional) Timing used to report action statistics (impression, conversion, mixed, or lifetime) + +- - `breakdowns` + - Sequence + - (Optional) Breakdowns to configure for data aggregation + +- - `custom_insights_lookback_window` + - Integer + - (Optional) Window in days to revisit data during sync to capture updated conversion data from the API. + +- - `level` + - String + - (Optional) Granularity of data to pull (account, ad, adset, campaign) + +- - `start_date` + - String + - (Optional) Start date in yyyy-MM-dd format (e.g. 2025-01-15). Data added after this date will be ingested + +- - `time_increment` + - String + - (Optional) Value in string by which to aggregate statistics (can take all_days, monthly or number of days) + +::: + + +### pipelines._name_.ingestion_definition.objects.table.table_configuration + +**`Type: Map`** + +Configuration settings to control the ingestion of tables. These settings override the table_configuration defined in the IngestionPipelineDefinition object and the SchemaSpec. + + + +:::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_definitionobjectstabletable_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. + +- - `query_based_connector_config` + - Map + - Configurations that are only applicable for query-based ingestion connectors. See [\_](#pipelinesnameingestion_definitionobjectstabletable_configurationquery_based_connector_config). + +- - `row_filter` + - String + - (Optional, Immutable) The row filter condition to be applied to the table. It must not contain the WHERE keyword, only the actual filter condition. It must be in DBSQL format. + +- - `scd_type` + - String + - The SCD type to use to ingest the table. + +- - `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_.ingestion_definition.objects.table.table_configuration.auto_full_refresh_policy + +**`Type: 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. + + + +:::list-table + +- - Key + - Type + - Description + +- - `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_.ingestion_definition.objects.table.table_configuration.query_based_connector_config + +**`Type: Map`** + +Configurations that are only applicable for query-based ingestion connectors. + + + +:::list-table + +- - Key + - Type + - Description + +- - `cursor_columns` + - Sequence + - The names of the monotonically increasing columns in the source table that are used to enable the table to be read and ingested incrementally through structured streaming. The columns are allowed to have repeated values but have to be non-decreasing. If the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these columns will implicitly define the `sequence_by` behavior. You can still explicitly set `sequence_by` to override this default. + +- - `deletion_condition` + - String + - Specifies a SQL WHERE condition that specifies that the source row has been deleted. This is sometimes referred to as "soft-deletes". For example: "Operation = 'DELETE'" or "is_deleted = true". This field is orthogonal to `hard_deletion_sync_interval_in_seconds`, one for soft-deletes and the other for hard-deletes. See also the hard_deletion_sync_min_interval_in_seconds field for handling of "hard deletes" where the source rows are physically removed from the table. + +- - `hard_deletion_sync_min_interval_in_seconds` + - Integer + - Specifies the minimum interval (in seconds) between snapshots on primary keys for detecting and synchronizing hard deletions—i.e., rows that have been physically removed from the source table. This interval acts as a lower bound. If ingestion runs less frequently than this value, hard deletion synchronization will align with the actual ingestion frequency instead of happening more often. If not set, hard deletion synchronization via snapshots is disabled. This field is mutable and can be updated without triggering a full snapshot. + +::: + + +### pipelines._name_.ingestion_definition.source_configurations + +**`Type: Sequence`** + +Top-level source configurations + + + +:::list-table + +- - Key + - Type + - Description + +- - `catalog` + - Map - Catalog-level source configuration parameters. See [\_](#pipelinesnameingestion_definitionsource_configurationscatalog). ::: @@ -9670,6 +10145,18 @@ Configuration settings to control the ingestion of tables. These settings are ap - Sequence - The primary key of the table used to apply changes. +- - `query_based_connector_config` + - Map + - Configurations that are only applicable for query-based ingestion connectors. See [\_](#pipelinesnameingestion_definitiontable_configurationquery_based_connector_config). + +- - `row_filter` + - String + - (Optional, Immutable) The row filter condition to be applied to the table. It must not contain the WHERE keyword, only the actual filter condition. It must be in DBSQL format. + +- - `scd_type` + - String + - The SCD type to use to ingest the table. + - - `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. @@ -9712,6 +10199,35 @@ If unspecified, auto full refresh is disabled. ::: +### pipelines._name_.ingestion_definition.table_configuration.query_based_connector_config + +**`Type: Map`** + +Configurations that are only applicable for query-based ingestion connectors. + + + +:::list-table + +- - Key + - Type + - Description + +- - `cursor_columns` + - Sequence + - The names of the monotonically increasing columns in the source table that are used to enable the table to be read and ingested incrementally through structured streaming. The columns are allowed to have repeated values but have to be non-decreasing. If the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these columns will implicitly define the `sequence_by` behavior. You can still explicitly set `sequence_by` to override this default. + +- - `deletion_condition` + - String + - Specifies a SQL WHERE condition that specifies that the source row has been deleted. This is sometimes referred to as "soft-deletes". For example: "Operation = 'DELETE'" or "is_deleted = true". This field is orthogonal to `hard_deletion_sync_interval_in_seconds`, one for soft-deletes and the other for hard-deletes. See also the hard_deletion_sync_min_interval_in_seconds field for handling of "hard deletes" where the source rows are physically removed from the table. + +- - `hard_deletion_sync_min_interval_in_seconds` + - Integer + - Specifies the minimum interval (in seconds) between snapshots on primary keys for detecting and synchronizing hard deletions—i.e., rows that have been physically removed from the source table. This interval acts as a lower bound. If ingestion runs less frequently than this value, hard deletion synchronization will align with the actual ingestion frequency instead of happening more often. If not set, hard deletion synchronization via snapshots is disabled. This field is mutable and can be updated without triggering a full snapshot. + +::: + + ### pipelines._name_.libraries **`Type: Sequence`** @@ -9959,6 +10475,10 @@ postgres_branches: - String - +- - `replace_existing` + - Boolean + - + - - `source_branch` - String - @@ -9986,6 +10506,69 @@ postgres_branches: +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + +## postgres_catalogs + +**`Type: Map`** + +The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. + +```yaml +postgres_catalogs: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `catalog_id` + - String + - + +- - `create_database_if_missing` + - Boolean + - + +- - `lifecycle` + - Map + - See [\_](#postgres_catalogsnamelifecycle). + +- - `postgres_database` + - String + - + +::: + + +### postgres_catalogs._name_.lifecycle + +**`Type: Map`** + + + + + :::list-table - - Key @@ -10054,6 +10637,10 @@ postgres_endpoints: - String - +- - `replace_existing` + - Boolean + - + - - `settings` - Map - A collection of settings for a compute endpoint. See [\_](#postgres_endpointsnamesettings). @@ -10251,7 +10838,7 @@ A collection of settings for a compute endpoint. - - `no_suspension` - Boolean - - When set to true, explicitly disables automatic suspension (never suspend). Should be set to true when provided. + - When set to true, explicitly disables automatic suspension (never suspend). Should be set to true when provided. Mutually exclusive with `suspend_timeout_duration`. When updating, use `spec.project_default_settings.suspension` in the update_mask. - - `pg_settings` - Map @@ -10259,7 +10846,7 @@ A collection of settings for a compute endpoint. - - `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). + - Duration of inactivity after which the compute endpoint is automatically suspended. If specified should be between 60s and 604800s (1 minute to 1 week). Mutually exclusive with `no_suspension`. When updating, use `spec.project_default_settings.suspension` in the update_mask. ::: @@ -10318,6 +10905,93 @@ A collection of settings for a compute endpoint. ::: +## postgres_synced_tables + +**`Type: Map`** + +The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. + +```yaml +postgres_synced_tables: + : + : +``` + + +:::list-table + +- - Key + - Type + - Description + +- - `branch` + - String + - + +- - `create_database_objects_if_missing` + - Boolean + - + +- - `existing_pipeline_id` + - String + - + +- - `lifecycle` + - Map + - See [\_](#postgres_synced_tablesnamelifecycle). + +- - `new_pipeline_spec` + - Map + - See [\_](#postgres_synced_tablesnamenew_pipeline_spec). + +- - `postgres_database` + - String + - + +- - `primary_key_columns` + - Sequence + - + +- - `scheduling_policy` + - String + - + +- - `source_table_full_name` + - String + - + +- - `synced_table_id` + - String + - + +- - `timeseries_key` + - String + - + +::: + + +### postgres_synced_tables._name_.lifecycle + +**`Type: Map`** + + + + + +:::list-table + +- - Key + - Type + - Description + +- - `prevent_destroy` + - Boolean + - Lifecycle setting to prevent the resource from being destroyed. + +::: + + ## quality_monitors **`Type: Map`** @@ -11480,10 +12154,6 @@ vector_search_endpoints: - Map - See [\_](#vector_search_endpointsnamelifecycle). -- - `min_qps` - - Integer - - - - - `name` - String - @@ -11492,6 +12162,10 @@ vector_search_endpoints: - Sequence - See [\_](#vector_search_endpointsnamepermissions). +- - `target_qps` + - Integer + - + ::: diff --git a/bundle/generate/downloader.go b/bundle/generate/downloader.go index d37f2a12f47..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" @@ -73,7 +74,7 @@ func (n *Downloader) markFileForDownload(ctx context.Context, filePath *string) return err } - *filePath = rel + *filePath = filepath.ToSlash(rel) return nil } @@ -109,7 +110,7 @@ func (n *Downloader) MarkDirectoryForDownload(ctx context.Context, dirPath *stri return err } - *dirPath = rel + *dirPath = filepath.ToSlash(rel) return 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/.gitattributes b/bundle/internal/schema/.gitattributes new file mode 100644 index 00000000000..676640bbcca --- /dev/null +++ b/bundle/internal/schema/.gitattributes @@ -0,0 +1 @@ +annotations_openapi.yml linguist-generated=true diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 2f28ca27596..041ba102ddb 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -215,12 +215,18 @@ github.com/databricks/cli/bundle/config.Resources: "postgres_branches": "description": |- PLACEHOLDER + "postgres_catalogs": + "description": |- + The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch. "postgres_endpoints": "description": |- PLACEHOLDER "postgres_projects": "description": |- PLACEHOLDER + "postgres_synced_tables": + "description": |- + The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance. "quality_monitors": "description": |- The quality monitor definitions for the bundle, where each key is the name of the quality monitor. @@ -531,6 +537,9 @@ github.com/databricks/cli/bundle/config/resources.App: 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. + "thumbnail_url": + "description": |- + PLACEHOLDER github.com/databricks/cli/bundle/config/resources.AppConfig: "command": "description": |- @@ -766,6 +775,9 @@ github.com/databricks/cli/bundle/config/resources.PostgresBranch: "parent": "description": |- PLACEHOLDER + "replace_existing": + "description": |- + PLACEHOLDER "source_branch": "description": |- PLACEHOLDER @@ -790,6 +802,22 @@ github.com/databricks/cli/bundle/config/resources.PostgresBranch: "update_time": "description": |- PLACEHOLDER +github.com/databricks/cli/bundle/config/resources.PostgresCatalog: + "branch": + "description": |- + PLACEHOLDER + "catalog_id": + "description": |- + PLACEHOLDER + "create_database_if_missing": + "description": |- + PLACEHOLDER + "lifecycle": + "description": |- + PLACEHOLDER + "postgres_database": + "description": |- + PLACEHOLDER github.com/databricks/cli/bundle/config/resources.PostgresEndpoint: "autoscaling_limit_max_cu": "description": |- @@ -824,6 +852,9 @@ github.com/databricks/cli/bundle/config/resources.PostgresEndpoint: "parent": "description": |- PLACEHOLDER + "replace_existing": + "description": |- + PLACEHOLDER "settings": "description": |- PLACEHOLDER @@ -894,6 +925,40 @@ github.com/databricks/cli/bundle/config/resources.PostgresProject: "update_time": "description": |- PLACEHOLDER +github.com/databricks/cli/bundle/config/resources.PostgresSyncedTable: + "branch": + "description": |- + PLACEHOLDER + "create_database_objects_if_missing": + "description": |- + PLACEHOLDER + "existing_pipeline_id": + "description": |- + PLACEHOLDER + "lifecycle": + "description": |- + PLACEHOLDER + "new_pipeline_spec": + "description": |- + PLACEHOLDER + "postgres_database": + "description": |- + PLACEHOLDER + "primary_key_columns": + "description": |- + PLACEHOLDER + "scheduling_policy": + "description": |- + PLACEHOLDER + "source_table_full_name": + "description": |- + PLACEHOLDER + "synced_table_id": + "description": |- + PLACEHOLDER + "timeseries_key": + "description": |- + PLACEHOLDER github.com/databricks/cli/bundle/config/resources.SecretScope: "backend_type": "description": |- @@ -971,15 +1036,15 @@ github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint: "lifecycle": "description": |- PLACEHOLDER - "min_qps": - "description": |- - PLACEHOLDER "name": "description": |- PLACEHOLDER "permissions": "description": |- PLACEHOLDER + "target_qps": + "description": |- + PLACEHOLDER "usage_policy_id": "description": |- PLACEHOLDER diff --git a/bundle/internal/schema/annotations_openapi.yml b/bundle/internal/schema/annotations_openapi.yml index 84a4b01b545..912d9158e40 100644 --- a/bundle/internal/schema/annotations_openapi.yml +++ b/bundle/internal/schema/annotations_openapi.yml @@ -115,6 +115,9 @@ github.com/databricks/cli/bundle/config/resources.App: "description": |- Git repository configuration for app deployments. When specified, deployments can reference code from this repository by providing only the git reference (branch, tag, or commit). + "git_source": + "description": |- + Complete git source specification including repository location and reference. "x-databricks-preview": |- PRIVATE "id": @@ -150,12 +153,20 @@ github.com/databricks/cli/bundle/config/resources.App: "service_principal_name": "x-databricks-field-behaviors_output_only": |- true + "source_code_path": + "x-databricks-preview": |- + PRIVATE "space": "description": |- Name of the space this app belongs to. "x-databricks-preview": |- PRIVATE "telemetry_export_destinations": {} + "thumbnail_url": + "description": |- + The URL of the thumbnail image for the app. + "x-databricks-field-behaviors_output_only": |- + true "update_time": "description": |- The update time of the app. Formatted timestamp in ISO 6801. @@ -183,8 +194,6 @@ github.com/databricks/cli/bundle/config/resources.Catalog: "managed_encryption_settings": "description": |- Control CMK encryption for managed catalog data - "x-databricks-preview": |- - PRIVATE "name": "description": |- Name of catalog. @@ -255,15 +264,14 @@ github.com/databricks/cli/bundle/config/resources.Cluster: 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`. + * `DATA_SECURITY_MODE_STANDARD`: 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. + * `DATA_SECURITY_MODE_DEDICATED`: 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. + + The following modes are legacy aliases for the above modes: - 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. + * `USER_ISOLATION`: Legacy alias for `DATA_SECURITY_MODE_STANDARD`. + * `SINGLE_USER`: Legacy alias for `DATA_SECURITY_MODE_DEDICATED`. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: @@ -324,7 +332,6 @@ github.com/databricks/cli/bundle/config/resources.Cluster: Clusters with `kind = CLASSIC_PREVIEW` support the following fields, whereas clusters with no specified `kind` do not. * [is_single_node](/api/workspace/clusters/create#is_single_node) * [use_ml_runtime](/api/workspace/clusters/create#use_ml_runtime) - * [data_security_mode](/api/workspace/clusters/create#data_security_mode) set to `DATA_SECURITY_MODE_AUTO`, `DATA_SECURITY_MODE_DEDICATED`, or `DATA_SECURITY_MODE_STANDARD` By using the [simple form](https://docs.databricks.com/compute/simple-form.html), your clusters are automatically using `kind = CLASSIC_PREVIEW`. "node_type_id": @@ -775,7 +782,7 @@ github.com/databricks/cli/bundle/config/resources.Pipeline: 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": "description": |- - DLT Release Channel that specifies which version to use. + SDP Release Channel that specifies which version to use. "clusters": "description": |- Cluster settings for this pipeline deployment. @@ -1136,13 +1143,14 @@ github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint: "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 + Name of the AI Search endpoint + "target_qps": + "description": |- + Target QPS for the endpoint. Mutually exclusive with num_replicas. + The actual replica count is calculated at index creation/sync time based on this value. + Best-effort target; the system does not guarantee this QPS will be achieved. "usage_policy_id": "description": |- The usage policy id to be applied once we've migrated to usage policies @@ -1197,8 +1205,6 @@ github.com/databricks/databricks-sdk-go/service/apps.AppDeployment: "git_source": "description": |- Git repository to use as the source for the app deployment. - "x-databricks-preview": |- - PRIVATE "mode": "description": |- The mode of which the deployment will manage the source code. @@ -1466,6 +1472,8 @@ github.com/databricks/databricks-sdk-go/service/apps.ComputeSize: MEDIUM - |- LARGE + - |- + LIQUID github.com/databricks/databricks-sdk-go/service/apps.ComputeState: "_": "enum": @@ -1547,8 +1555,6 @@ github.com/databricks/databricks-sdk-go/service/apps.GitSource: commit SHA that the branch or tag pointed to at deployment time. "x-databricks-field-behaviors_output_only": |- true - "x-databricks-preview": |- - PRIVATE "source_code_path": "description": |- Relative path to the app source code within the Git repository. If not specified, the root @@ -1881,6 +1887,34 @@ github.com/databricks/databricks-sdk-go/service/catalog.Privilege: EXECUTE_CLEAN_ROOM_TASK - |- EXTERNAL_USE_SCHEMA + - |- + VIEW_OBJECT + - |- + MANAGE_GRANTS + - |- + INSERT + - |- + UPDATE + - |- + DELETE + - |- + VIEW_ADMIN_METADATA + - |- + VIEW_METADATA + - |- + USE_VOLUME + - |- + READ_METADATA + - |- + MANAGE_ACCESS + - |- + MANAGE_ACCESS_CONTROL + - |- + CREATE_SERVICE + - |- + CREATE_FEATURE + - |- + READ_FEATURE github.com/databricks/databricks-sdk-go/service/catalog.PrivilegeAssignment: "principal": "description": |- @@ -2168,15 +2202,14 @@ github.com/databricks/databricks-sdk-go/service/compute.ClusterSpec: 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`. + * `DATA_SECURITY_MODE_STANDARD`: 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. + * `DATA_SECURITY_MODE_DEDICATED`: 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. + + The following modes are legacy aliases for the above modes: - 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. + * `USER_ISOLATION`: Legacy alias for `DATA_SECURITY_MODE_STANDARD`. + * `SINGLE_USER`: Legacy alias for `DATA_SECURITY_MODE_DEDICATED`. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: @@ -2237,7 +2270,6 @@ github.com/databricks/databricks-sdk-go/service/compute.ClusterSpec: Clusters with `kind = CLASSIC_PREVIEW` support the following fields, whereas clusters with no specified `kind` do not. * [is_single_node](/api/workspace/clusters/create#is_single_node) * [use_ml_runtime](/api/workspace/clusters/create#use_ml_runtime) - * [data_security_mode](/api/workspace/clusters/create#data_security_mode) set to `DATA_SECURITY_MODE_AUTO`, `DATA_SECURITY_MODE_DEDICATED`, or `DATA_SECURITY_MODE_STANDARD` By using the [simple form](https://docs.databricks.com/compute/simple-form.html), your clusters are automatically using `kind = CLASSIC_PREVIEW`. "node_type_id": @@ -2316,21 +2348,32 @@ github.com/databricks/databricks-sdk-go/service/compute.ClusterSpec: "workload_type": "description": |- Cluster Attributes showing for clusters workload types. +github.com/databricks/databricks-sdk-go/service/compute.ConfidentialComputeType: + "_": + "description": |- + Confidential computing technology for GCP instances. + Aligns with gcloud's --confidential-compute-type flag and the REST API's + confidentialInstanceConfig.confidentialInstanceType field. + See: https://cloud.google.com/confidential-computing/confidential-vm/docs/create-a-confidential-vm-instance + "enum": + - |- + CONFIDENTIAL_COMPUTE_TYPE_NONE + - |- + SEV_SNP github.com/databricks/databricks-sdk-go/service/compute.DataSecurityMode: "_": "description": |- 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`. + * `DATA_SECURITY_MODE_STANDARD`: 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. + * `DATA_SECURITY_MODE_DEDICATED`: 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. - 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 legacy aliases for the above modes: + + * `USER_ISOLATION`: Legacy alias for `DATA_SECURITY_MODE_STANDARD`. + * `SINGLE_USER`: Legacy alias for `DATA_SECURITY_MODE_DEDICATED`. The following modes are deprecated starting with Databricks Runtime 15.0 and will be removed for future Databricks Runtime versions: @@ -2394,7 +2437,7 @@ github.com/databricks/databricks-sdk-go/service/compute.EbsVolumeType: github.com/databricks/databricks-sdk-go/service/compute.Environment: "_": "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. + The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported. "base_environment": "description": |- @@ -2403,7 +2446,10 @@ github.com/databricks/databricks-sdk-go/service/compute.Environment: (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 + Either `environment_version` or `base_environment` can be provided. + For more information about Databricks-provided base environments, see the + [list workspace base environments](:method:Environments/ListWorkspaceBaseEnvironments) API. + For more information, see "client": "description": |- Use `environment_version` instead. @@ -2433,6 +2479,13 @@ github.com/databricks/databricks-sdk-go/service/compute.GcpAttributes: "boot_disk_size": "description": |- Boot disk size in GB + "confidential_compute_type": + "description": |- + The confidential computing technology for this cluster's instances. + Currently only SEV_SNP is supported, and only on N2D instance types. + When not set, no confidential computing is applied. + "x-databricks-preview": |- + PRIVATE "first_on_demand": "description": |- The first `first_on_demand` nodes of the cluster will be placed on on-demand instances. @@ -2499,6 +2552,8 @@ github.com/databricks/databricks-sdk-go/service/compute.HardwareAcceleratorType: GPU_1xA10 - |- GPU_8xH100 + - |- + GPU_1xH100 github.com/databricks/databricks-sdk-go/service/compute.InitScriptInfo: "_": "description": |- @@ -2755,6 +2810,8 @@ github.com/databricks/databricks-sdk-go/service/database.DatabaseInstanceState: UPDATING - |- FAILING_OVER + - |- + UPGRADING github.com/databricks/databricks-sdk-go/service/database.DeltaTableSyncInfo: "delta_commit_timestamp": "description": |- @@ -3102,6 +3159,8 @@ github.com/databricks/databricks-sdk-go/service/iam.PermissionLevel: CAN_MONITOR_ONLY - |- CAN_CREATE_APP + - |- + UNSPECIFIED github.com/databricks/databricks-sdk-go/service/jobs.AlertTask: "alert_id": "description": |- @@ -3483,7 +3542,7 @@ github.com/databricks/databricks-sdk-go/service/jobs.JobEnvironment: The key of an environment. It has to be unique within a job. "spec": "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. + The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported. github.com/databricks/databricks-sdk-go/service/jobs.JobNotificationSettings: "no_alert_for_canceled_runs": @@ -3705,14 +3764,40 @@ github.com/databricks/databricks-sdk-go/service/jobs.PeriodicTriggerConfiguratio github.com/databricks/databricks-sdk-go/service/jobs.PipelineParams: "full_refresh": "description": |- - If true, triggers a full refresh on the delta live table. + If true, triggers a full refresh on the spark declarative pipeline. + "full_refresh_selection": + "description": |- + A list of tables to update with fullRefresh. + "refresh_flow_selection": + "description": |- + Flow names to selectively refresh. These are unioned with other selective refresh + options (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh. + "refresh_selection": + "description": |- + A list of tables to update without fullRefresh. + "reset_checkpoint_selection": + "description": |- + A list of streaming flows to reset checkpoints without clearing data. github.com/databricks/databricks-sdk-go/service/jobs.PipelineTask: "full_refresh": "description": |- - If true, triggers a full refresh on the delta live table. + If true, triggers a full refresh on the spark declarative pipeline. + "full_refresh_selection": + "description": |- + A list of tables to update with fullRefresh. "pipeline_id": "description": |- The full name of the pipeline task to execute. + "refresh_flow_selection": + "description": |- + Flow names to selectively refresh. These are unioned with other selective refresh + options (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh. + "refresh_selection": + "description": |- + A list of tables to update without fullRefresh. + "reset_checkpoint_selection": + "description": |- + A list of streaming flows to reset checkpoints without clearing data. github.com/databricks/databricks-sdk-go/service/jobs.PowerBiModel: "authentication_method": "description": |- @@ -3758,6 +3843,18 @@ github.com/databricks/databricks-sdk-go/service/jobs.PowerBiTask: "warehouse_id": "description": |- The SQL warehouse ID to use as the Power BI data source +github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTask: + "main": + "description": |- + Fully qualified name of the main class or function. + For example, `my_project.my_function` or `my_project.MyOperator`. + "parameters": + "description": |- + An ordered list of task parameters. + TODO(JOBS-30885): Add limits for parameters. +github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter: + "name": {} + "value": {} github.com/databricks/databricks-sdk-go/service/jobs.PythonWheelTask: "entry_point": "description": |- @@ -4113,8 +4210,6 @@ github.com/databricks/databricks-sdk-go/service/jobs.Task: "disabled": "description": |- An optional flag to disable the task. If set to true, the task will not run even if it is part of a job. - "x-databricks-preview": |- - PRIVATE "email_notifications": "description": |- 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. @@ -4164,6 +4259,11 @@ github.com/databricks/databricks-sdk-go/service/jobs.Task: "power_bi_task": "description": |- The task triggers a Power BI semantic model update when the `power_bi_task` field is present. + "python_operator_task": + "description": |- + The task runs a Python operator task. + "x-databricks-preview": |- + PRIVATE "python_wheel_task": "description": |- The task runs a Python wheel when the `python_wheel_task` field is present. @@ -4355,6 +4455,13 @@ github.com/databricks/databricks-sdk-go/service/pipelines.AutoFullRefreshPolicy: (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. +github.com/databricks/databricks-sdk-go/service/pipelines.ConfluenceConnectorOptions: + "_": + "description": |- + Confluence specific options for ingestion + "include_confluence_spaces": + "description": |- + (Optional) Spaces to filter Confluence data on github.com/databricks/databricks-sdk-go/service/pipelines.ConnectionParameters: "source_catalog": "description": |- @@ -4368,6 +4475,9 @@ github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions: "_": "description": |- Wrapper message for source-specific options to support multiple connector types + "confluence_options": + "description": |- + Confluence specific options for ingestion "gdrive_options": "x-databricks-preview": |- PRIVATE @@ -4378,14 +4488,38 @@ github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions: (source_configurations). "x-databricks-preview": |- PRIVATE + "jira_options": + "description": |- + Jira specific options for ingestion + "kafka_options": + "x-databricks-preview": |- + PRIVATE + "meta_ads_options": + "description": |- + Meta Marketing (Meta Ads) specific options for ingestion + "outlook_options": + "description": |- + Outlook specific options for ingestion + "x-databricks-preview": |- + PRIVATE "sharepoint_options": "x-databricks-preview": |- PRIVATE + "smartsheet_options": + "description": |- + Smartsheet specific options for ingestion + "x-databricks-preview": |- + PRIVATE "tiktok_ads_options": "description": |- TikTok Ads specific options for ingestion "x-databricks-preview": |- PRIVATE + "zendesk_support_options": + "description": |- + Zendesk Support specific options for ingestion + "x-databricks-preview": |- + PRIVATE github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorType: "_": "description": |- @@ -4519,6 +4653,8 @@ github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFi AVRO - |- ORC + - |- + FILE github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode: "_": "description": |- @@ -4545,6 +4681,14 @@ github.com/databricks/databricks-sdk-go/service/pipelines.Filters: "include": "description": |- Paths to include. +github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsConfig: + "manager_account_id": + "description": |- + (Required) Manager Account ID (also called MCC Account ID) used to list and access + customer accounts under this manager account. This is required for fetching the list + of customer accounts during source selection. + If the same field is also set in the object-level GoogleAdsOptions (connector_options), + the object-level value takes precedence over this top-level config. github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsOptions: "_": "description": |- @@ -4580,6 +4724,10 @@ github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoog FILE_METADATA - |- PERMISSION + - |- + FILE_PERMISSION + - |- + GROUP_MEMBERSHIP github.com/databricks/databricks-sdk-go/service/pipelines.IngestionConfig: "report": "description": |- @@ -4648,8 +4796,6 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefin UC foreign catalogs directly without the need to specify a UC connection or ingestion gateway. The `source_catalog` fields in objects of IngestionConfig are interpreted as the UC foreign catalogs to ingest from. - "x-databricks-preview": |- - PRIVATE "ingestion_gateway_id": "description": |- Identifier for the gateway that is used by this ingestion pipeline to communicate with the source database. @@ -4691,8 +4837,6 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefin If the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these columns will implicitly define the `sequence_by` behavior. You can still explicitly set `sequence_by` to override this default. - "x-databricks-preview": |- - PRIVATE "deletion_condition": "description": |- Specifies a SQL WHERE condition that specifies that the source row has been deleted. @@ -4702,8 +4846,6 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefin one for soft-deletes and the other for hard-deletes. See also the hard_deletion_sync_min_interval_in_seconds field for handling of "hard deletes" where the source rows are physically removed from the table. - "x-databricks-preview": |- - PRIVATE "hard_deletion_sync_min_interval_in_seconds": "description": |- Specifies the minimum interval (in seconds) between snapshots on primary keys @@ -4714,8 +4856,6 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefin frequency instead of happening more often. If not set, hard deletion synchronization via snapshots is disabled. This field is mutable and can be updated without triggering a full snapshot. - "x-databricks-preview": |- - PRIVATE github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefinitionWorkdayReportParameters: "incremental": "description": |- @@ -4757,6 +4897,10 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionSourceType: MYSQL - |- POSTGRESQL + - |- + REDSHIFT + - |- + SQLDW - |- SQLSERVER - |- @@ -4783,9 +4927,261 @@ github.com/databricks/databricks-sdk-go/service/pipelines.IngestionSourceType: DYNAMICS365 - |- GOOGLE_DRIVE + - |- + JIRA + - |- + CONFLUENCE + - |- + META_MARKETING + - |- + GOOGLE_ADS + - |- + TIKTOK_ADS + - |- + SALESFORCE_MARKETING_CLOUD + - |- + HUBSPOT + - |- + WORKDAY_HCM + - |- + GUIDEWIRE + - |- + ZENDESK + - |- + COMMUNITY + - |- + SLACK_AUDIT_LOGS + - |- + KAFKA + - |- + CROWDSTRIKE_EVENT_STREAM + - |- + WORKDAY_ACTIVITY_LOGGING + - |- + AKAMAI_WAF + - |- + VEEVA + - |- + VEEVA_VAULT + - |- + M365_AUDIT_LOGS + - |- + OKTA_SYSTEM_LOGS + - |- + ONE_PASSWORD_EVENT_LOGS + - |- + PROOFPOINT_SIEM + - |- + WIZ_AUDIT_LOGS + - |- + GITHUB + - |- + OUTLOOK + - |- + SMARTSHEET + - |- + MICROSOFT_TEAMS + - |- + ADOBE_CAMPAIGNS + - |- + LINKEDIN_ADS + - |- + X_ADS + - |- + BING_ADS + - |- + GOOGLE_SEARCH_CONSOLE + - |- + PINTEREST_ADS + - |- + REDDIT_ADS + - |- + PENDO + - |- + API_SOURCE + - |- + SLACK_ACCESS_AND_INTEGRATION_LOGS + - |- + ORACLE_FUSION_CLOUD + - |- + RABBITMQ + - |- + GOOGLE_ANALYTICS + - |- + AMPLITUDE + - |- + NOTION + - |- + ADP_WORKFORCE_NOW + - |- + SAS + - |- + GONG + - |- + SALESLOFT + - |- + GOOGLE_WORKSPACE + - |- + SHOPIFY + - |- + ORACLE_ELOQUA + - |- + EPIC_CLARITY + - |- + LINEAR + - |- + APPLE_APP_STORE + - |- + SPLUNK + - |- + GITLAB + - |- + ZOOM_LOGS + - |- + MONDAY_COM + - |- + AIRTABLE + - |- + MICROSOFT_ENTRA_ID + - |- + PAGERDUTY + - |- + APPFIGURES + - |- + ADOBE_COMMERCE + - |- + QUICKBOOKS + - |- + SQUARE + - |- + ZOHO_BOOKS + - |- + SNAPCHAT_ADS + - |- + GENESYS + - |- + SAP_SUCCESSFACTORS + - |- + YOUTUBE_ANALYTICS + - |- + CERIDIAN_DAYFORCE + - |- + FRESHSERVICE + - |- + SENDGRID + - |- + AZURE_MONITOR_LOGS + - |- + ATLASSIAN_ORGANIZATION + - |- + HIBOB + - |- + APPLE_SEARCH_ADS + - |- + AWIN + - |- + DELIGHTED + - |- + FRONT + - |- + GURU + - |- + PARTNERSTACK + - |- + MARKETO + - |- + AHA + - |- + NETSKOPE_LOGS - |- FOREIGN_CATALOG +github.com/databricks/databricks-sdk-go/service/pipelines.JiraConnectorOptions: + "_": + "description": |- + Jira specific options for ingestion + "include_jira_spaces": + "description": |- + (Optional) Projects to filter Jira data on +github.com/databricks/databricks-sdk-go/service/pipelines.JsonTransformerOptions: + "as_variant": + "description": |- + Parse the entire value as a single Variant column. + "schema": + "description": |- + Inline schema string for JSON parsing (Spark DDL format). + "schema_evolution_mode": + "description": |- + (Optional) Schema evolution mode for schema inference. + "schema_file_path": + "description": |- + Path to a schema file (.ddl). + "schema_hints": + "description": |- + (Optional) Schema hints as a comma-separated string of "column_name type" pairs. +github.com/databricks/databricks-sdk-go/service/pipelines.KafkaOptions: + "client_config": + "description": |- + Undocumented backdoor mechanism for overriding parameters + to pass to the Kafka client. + This is not supported and may break at any time. + "x-databricks-preview": |- + PRIVATE + "key_transformer": + "description": |- + (Optional) Transformer for the message key. + If not specified, the key is left as raw bytes. + "max_offsets_per_trigger": + "description": |- + Internal option to control the maximum number of offsets to process per trigger. + "x-databricks-preview": |- + PRIVATE + "starting_offset": + "description": |- + (Optional) Where to begin reading when no checkpoint exists. + Valid values: "latest" and "earliest". Defaults to "latest". + "topic_pattern": + "description": |- + Java regex pattern to subscribe to matching topics. + Only one of topics or topic_pattern must be specified. + "topics": + "description": |- + Topics to subscribe to. + Only one of topics or topic_pattern must be specified. + "value_transformer": + "description": |- + (Optional) Transformer for the message value. + If not specified, the value is left as raw bytes. github.com/databricks/databricks-sdk-go/service/pipelines.ManualTrigger: {} +github.com/databricks/databricks-sdk-go/service/pipelines.MetaMarketingOptions: + "_": + "description": |- + Meta Marketing (Meta Ads) specific options for ingestion + "action_attribution_windows": + "description": |- + (Optional) Action attribution windows for insights reporting (e.g. "28d_click", "1d_view") + "action_breakdowns": + "description": |- + (Optional) Action breakdowns to configure for data aggregation + "action_report_time": + "description": |- + (Optional) Timing used to report action statistics (impression, conversion, mixed, or lifetime) + "breakdowns": + "description": |- + (Optional) Breakdowns to configure for data aggregation + "custom_insights_lookback_window": + "description": |- + (Optional) Window in days to revisit data during sync to capture + updated conversion data from the API. + "level": + "description": |- + (Optional) Granularity of data to pull (account, ad, adset, campaign) + "start_date": + "description": |- + (Optional) Start date in yyyy-MM-dd format (e.g. 2025-01-15). Data added + after this date will be ingested + "time_increment": + "description": |- + (Optional) Value in string by which to aggregate statistics (can take all_days, monthly or number of days) github.com/databricks/databricks-sdk-go/service/pipelines.NotebookLibrary: "path": "description": |- @@ -4818,6 +5214,86 @@ github.com/databricks/databricks-sdk-go/service/pipelines.OperationTimeWindow: "description": |- 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. +github.com/databricks/databricks-sdk-go/service/pipelines.OutlookAttachmentMode: + "_": + "description": |- + Attachment behavior mode for Outlook ingestion + "enum": + - |- + ALL + - |- + NON_INLINE_ONLY + - |- + INLINE_ONLY + - |- + NONE +github.com/databricks/databricks-sdk-go/service/pipelines.OutlookBodyFormat: + "_": + "description": |- + Body format for Outlook email content + "enum": + - |- + TEXT_HTML + - |- + TEXT_PLAIN +github.com/databricks/databricks-sdk-go/service/pipelines.OutlookOptions: + "_": + "description": |- + Outlook specific options for ingestion + "attachment_mode": + "description": |- + (Optional) Controls which attachments to ingest. + If not specified, defaults to ALL. + "body_format": + "description": |- + (Optional) Defines how the body_content column is populated. + TEXT_HTML: Preserves full formatting, links, and styling. + TEXT_PLAIN: Converts body to plain text. Recommended for AI/RAG pipelines to reduce token usage and noise. + "folder_filter": + "description": |- + Deprecated. Use include_folders instead. + "deprecation_message": |- + This field is deprecated + "include_folders": + "description": |- + (Optional) Filter mail folders to include in the sync. + If not specified, all folders will be synced. + Examples: Inbox, Sent Items, Custom_Folder + Filter semantics: OR between different folders. + "include_mailboxes": + "description": |- + (Optional) List of mailboxes to sync (e.g. mailbox email addresses or identifiers). + If not specified, all accessible mailboxes are ingested. + Filter semantics: OR between different mailboxes. + "include_senders": + "description": |- + (Optional) Filter emails by sender address. Uses exact email match. + Examples: user@vendor.com, alerts@system.io, noreply@company.com + If not specified, emails from all senders will be synced. + Filter semantics: OR between different senders. + "include_subjects": + "description": |- + (Optional) Filter emails by subject line. Values ending with "*" use prefix match (subject starts with + the part before "*"); otherwise substring match (subject contains the value). + Examples: "Invoice" (substring), "Re:*" (prefix), "Support Ticket", "URGENT*" + If not specified, emails with all subjects will be synced. + Filter semantics: OR between different subjects. + "sender_filter": + "description": |- + Deprecated. Use include_senders instead. + "deprecation_message": |- + This field is deprecated + "start_date": + "description": |- + (Optional) Start date for the initial sync in YYYY-MM-DD format. + Format: YYYY-MM-DD (e.g., 2024-01-01) + This determines the earliest date from which to sync historical data. + If not specified, complete history is ingested. + "subject_filter": + "description": |- + Deprecated. Use include_subjects instead. + "deprecation_message": |- + This field is deprecated github.com/databricks/databricks-sdk-go/service/pipelines.PathPattern: "include": "description": |- @@ -5001,7 +5477,7 @@ github.com/databricks/databricks-sdk-go/service/pipelines.PipelineTrigger: github.com/databricks/databricks-sdk-go/service/pipelines.PipelinesEnvironment: "_": "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. + The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip dependencies are supported. "dependencies": "description": |- @@ -5084,8 +5560,6 @@ 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. @@ -5123,6 +5597,21 @@ github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptionsShare PERMISSION - |- LIST + - |- + FILE_PERMISSION + - |- + GROUP_MEMBERSHIP +github.com/databricks/databricks-sdk-go/service/pipelines.SmartsheetOptions: + "_": + "description": |- + Smartsheet specific options for ingestion + "enforce_schema": + "description": |- + (Optional) When true, maps each column to its Smartsheet-declared type (Text/Number/Date/ + Checkbox/etc.). Cells that do not conform to the declared type are set to NULL. + When false, all columns land as STRING. Use false for sheets with irregular data or columns + that frequently violate their own declared type. + If not specified, defaults to true. github.com/databricks/databricks-sdk-go/service/pipelines.SourceCatalogConfig: "_": "description": |- @@ -5137,12 +5626,13 @@ github.com/databricks/databricks-sdk-go/service/pipelines.SourceConfig: "catalog": "description": |- Catalog-level source configuration parameters + "google_ads_config": + "x-databricks-preview": |- + PRIVATE 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. @@ -5197,15 +5687,11 @@ github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfig: "query_based_connector_config": "description": |- Configurations that are only applicable for query-based ingestion connectors. - "x-databricks-preview": |- - PRIVATE "row_filter": "description": |- (Optional, Immutable) The row filter condition to be applied to the table. It must not contain the WHERE keyword, only the actual filter condition. It must be in DBSQL format. - "x-databricks-preview": |- - PRIVATE "salesforce_include_formula_fields": "description": |- If true, formula fields defined in the table are included in the ingestion. This setting is only valid for the Salesforce connector @@ -5214,8 +5700,6 @@ github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfig: "scd_type": "description": |- The SCD type to use to ingest the table. - "x-databricks-preview": |- - PRIVATE "sequence_by": "description": |- 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. @@ -5303,6 +5787,33 @@ github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTok BUSINESS_CENTER - |- GMV_MAX +github.com/databricks/databricks-sdk-go/service/pipelines.Transformer: + "_": + "description": |- + Specifies how to transform binary data into structured data. + "format": + "description": |- + Required: the wire format of the data. + "json_options": {} +github.com/databricks/databricks-sdk-go/service/pipelines.TransformerFormat: + "_": + "enum": + - |- + STRING + - |- + JSON + - |- + AVRO + - |- + PROTOBUF +github.com/databricks/databricks-sdk-go/service/pipelines.ZendeskSupportOptions: + "_": + "description": |- + Zendesk Support specific options for ingestion + "start_date": + "description": |- + (Optional) Start date in YYYY-MM-DD format for the initial sync. + This determines the earliest date from which to sync historical data. github.com/databricks/databricks-sdk-go/service/postgres.EndpointGroupSpec: "enable_readable_secondaries": "description": |- @@ -5333,6 +5844,18 @@ github.com/databricks/databricks-sdk-go/service/postgres.EndpointType: ENDPOINT_TYPE_READ_WRITE - |- ENDPOINT_TYPE_READ_ONLY +github.com/databricks/databricks-sdk-go/service/postgres.NewPipelineSpec: + "budget_policy_id": + "description": |- + Budget policy to set on the newly created pipeline. + "storage_catalog": + "description": |- + 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. + "storage_schema": + "description": |- + UC schema for the pipeline to store intermediate files (checkpoints, event logs etc). + This needs to be in the standard catalog where the user has permissions to create Delta tables. github.com/databricks/databricks-sdk-go/service/postgres.ProjectCustomTag: "key": "description": |- @@ -5354,6 +5877,7 @@ github.com/databricks/databricks-sdk-go/service/postgres.ProjectDefaultEndpointS "description": |- When set to true, explicitly disables automatic suspension (never suspend). Should be set to true when provided. + Mutually exclusive with `suspend_timeout_duration`. When updating, use `spec.project_default_settings.suspension` in the update_mask. "pg_settings": "description": |- A raw representation of Postgres settings. @@ -5361,6 +5885,18 @@ github.com/databricks/databricks-sdk-go/service/postgres.ProjectDefaultEndpointS "description": |- Duration of inactivity after which the compute endpoint is automatically suspended. If specified should be between 60s and 604800s (1 minute to 1 week). + Mutually exclusive with `no_suspension`. When updating, use `spec.project_default_settings.suspension` in the update_mask. +github.com/databricks/databricks-sdk-go/service/postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy: + "_": + "description": |- + Scheduling policy of the synced table's underlying pipeline. + "enum": + - |- + CONTINUOUS + - |- + TRIGGERED + - |- + SNAPSHOT github.com/databricks/databricks-sdk-go/service/serving.Ai21LabsConfig: "ai21labs_api_key": "description": |- @@ -5566,6 +6102,10 @@ github.com/databricks/databricks-sdk-go/service/serving.ApiKeyAuth: The API Key provided as a plaintext string. If you prefer to reference your token using Databricks Secrets, see `value`. github.com/databricks/databricks-sdk-go/service/serving.AutoCaptureConfigInput: + "_": + "description": |- + Deprecated: legacy inference table configuration. Please use AI Gateway inference tables instead. + See https://docs.databricks.com/aws/en/ai-gateway/inference-tables. "catalog_name": "description": |- The name of the catalog in Unity Catalog. NOTE: On update, you cannot change the catalog name if the inference table is already enabled. @@ -5650,10 +6190,12 @@ github.com/databricks/databricks-sdk-go/service/serving.EmailNotifications: github.com/databricks/databricks-sdk-go/service/serving.EndpointCoreConfigInput: "auto_capture_config": "description": |- - 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. + Configuration for legacy Inference Tables which automatically log requests and responses to Unity + Catalog. + Deprecated: please use AI Gateway inference tables instead. See + https://docs.databricks.com/aws/en/ai-gateway/inference-tables. + "deprecation_message": |- + This field is deprecated "served_entities": "description": |- The list of served entities under the serving endpoint config. @@ -5978,6 +6520,8 @@ github.com/databricks/databricks-sdk-go/service/serving.ServedModelInputWorkload GPU_LARGE - |- MULTIGPU_MEDIUM + - |- + GPU_XLARGE github.com/databricks/databricks-sdk-go/service/serving.ServingEndpointPermissionLevel: "_": "description": |- @@ -6004,6 +6548,8 @@ github.com/databricks/databricks-sdk-go/service/serving.ServingModelWorkloadType GPU_LARGE - |- MULTIGPU_MEDIUM + - |- + GPU_XLARGE github.com/databricks/databricks-sdk-go/service/serving.TrafficConfig: "routes": "description": |- @@ -6155,6 +6701,8 @@ github.com/databricks/databricks-sdk-go/service/sql.CreateWarehouseRequestWareho CLASSIC - |- PRO + - |- + REYDEN github.com/databricks/databricks-sdk-go/service/sql.CronSchedule: "pause_status": "description": |- @@ -6232,6 +6780,8 @@ github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType: STORAGE_OPTIMIZED - |- STANDARD + - |- + STANDARD_ON_ORION 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 690e0c852ee..d93e6068f46 100644 --- a/bundle/internal/schema/annotations_openapi_overrides.yml +++ b/bundle/internal/schema/annotations_openapi_overrides.yml @@ -935,6 +935,13 @@ github.com/databricks/databricks-sdk-go/service/jobs.JobsHealthRules: "rules": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter: + "name": + "description": |- + PLACEHOLDER + "value": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/jobs.RunJobTask: "python_named_params": "description": |- @@ -975,6 +982,9 @@ github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions: "gdrive_options": "description": |- PLACEHOLDER + "kafka_options": + "description": |- + PLACEHOLDER "sharepoint_options": "description": |- PLACEHOLDER @@ -1023,10 +1033,18 @@ github.com/databricks/databricks-sdk-go/service/pipelines.PipelineTrigger: "manual": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/pipelines.SourceConfig: + "google_ads_config": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfig: "workday_report_parameters": "description": |- PLACEHOLDER +github.com/databricks/databricks-sdk-go/service/pipelines.Transformer: + "json_options": + "description": |- + PLACEHOLDER github.com/databricks/databricks-sdk-go/service/serving.Route: "served_entity_name": "description": |- diff --git a/bundle/internal/schema/parser.go b/bundle/internal/schema/parser.go index d72524dc59c..60bf5393b33 100644 --- a/bundle/internal/schema/parser.go +++ b/bundle/internal/schema/parser.go @@ -58,14 +58,14 @@ func (p *openapiParser) findRef(typ reflect.Type) (jsonschema.Schema, bool) { // Check for embedded Databricks Go SDK types. if typ.Kind() == reflect.Struct { - for i := range typ.NumField() { - if !typ.Field(i).Anonymous { + for field := range typ.Fields() { + if !field.Anonymous { continue } // Deference current type if it's a pointer. - ctyp := typ.Field(i).Type - for ctyp.Kind() == reflect.Ptr { + ctyp := field.Type + for ctyp.Kind() == reflect.Pointer { ctyp = ctyp.Elem() } 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/go.mod b/bundle/internal/tf/codegen/go.mod index 14ba8f47ebb..776c067a604 100644 --- a/bundle/internal/tf/codegen/go.mod +++ b/bundle/internal/tf/codegen/go.mod @@ -1,12 +1,12 @@ module github.com/databricks/cli/bundle/internal/tf/codegen -go 1.25.0 +go 1.26.0 -toolchain go1.25.9 +toolchain go1.26.3 require ( - github.com/hashicorp/go-version v1.7.0 - github.com/hashicorp/hc-install v0.9.2 + github.com/hashicorp/go-version v1.9.0 + github.com/hashicorp/hc-install v0.9.4 github.com/hashicorp/terraform-exec v0.24.0 github.com/hashicorp/terraform-json v0.27.2 github.com/iancoleman/strcase v0.3.0 @@ -14,13 +14,13 @@ require ( ) require ( - github.com/ProtonMail/go-crypto v1.1.6 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect golang.org/x/crypto v0.45.0 // indirect - golang.org/x/mod v0.30.0 // indirect + golang.org/x/mod v0.35.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect ) diff --git a/bundle/internal/tf/codegen/go.sum b/bundle/internal/tf/codegen/go.sum index 4bf08058d86..15a5baafe68 100644 --- a/bundle/internal/tf/codegen/go.sum +++ b/bundle/internal/tf/codegen/go.sum @@ -2,8 +2,8 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= @@ -18,10 +18,10 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -30,12 +30,12 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= -github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.4 h1:KKWOpUG0EqIV63Qk2GGFrZ0s275NVs5lKf9N5vjBNoc= +github.com/hashicorp/hc-install v0.9.4/go.mod h1:4LRYeEN2bMIFfIv57ldMWt9awfuZhvpbRt0vWmv51WU= github.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5VoaNtAf8OE= github.com/hashicorp/terraform-exec v0.24.0/go.mod h1:lluc/rDYfAhYdslLJQg3J0oDqo88oGQAdHR+wDqFvo4= github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= @@ -62,8 +62,8 @@ 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/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= diff --git a/bundle/internal/tf/codegen/schema/version.go b/bundle/internal/tf/codegen/schema/version.go index 8b270d7670b..90dbf29d5dd 100644 --- a/bundle/internal/tf/codegen/schema/version.go +++ b/bundle/internal/tf/codegen/schema/version.go @@ -1,4 +1,4 @@ package schema // ProviderVersion is the version of the Databricks Terraform provider used for codegen. -const ProviderVersion = "1.113.0" +const ProviderVersion = "1.115.0" diff --git a/bundle/internal/tf/schema/config.go b/bundle/internal/tf/schema/config.go index e2c68d0e2f4..87d5bcbd7e5 100644 --- a/bundle/internal/tf/schema/config.go +++ b/bundle/internal/tf/schema/config.go @@ -26,7 +26,6 @@ type Config struct { DebugTruncateBytes int `json:"debug_truncate_bytes,omitempty"` DisableOauthRefreshToken bool `json:"disable_oauth_refresh_token,omitempty"` DiscoveryUrl string `json:"discovery_url,omitempty"` - ExperimentalIsUnifiedHost bool `json:"experimental_is_unified_host,omitempty"` GoogleCredentials string `json:"google_credentials,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` Host string `json:"host,omitempty"` 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 2d91042f2f3..98e9506ff18 100644 --- a/bundle/internal/tf/schema/data_source_account_network_policies.go +++ b/bundle/internal/tf/schema/data_source_account_network_policies.go @@ -15,6 +15,11 @@ type DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessAllowedStorageDesti StorageDestinationType string `json:"storage_destination_type,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessBlockedInternetDestinations struct { + Destination string `json:"destination,omitempty"` + InternetDestinationType string `json:"internet_destination_type,omitempty"` +} + type DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessPolicyEnforcement struct { DryRunModeProductFilter []string `json:"dry_run_mode_product_filter,omitempty"` EnforcementMode string `json:"enforcement_mode,omitempty"` @@ -23,6 +28,7 @@ type DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessPolicyEnforcement s type DataSourceAccountNetworkPoliciesItemsEgressNetworkAccess struct { AllowedInternetDestinations []DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessAllowedInternetDestinations `json:"allowed_internet_destinations,omitempty"` AllowedStorageDestinations []DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessAllowedStorageDestinations `json:"allowed_storage_destinations,omitempty"` + BlockedInternetDestinations []DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessBlockedInternetDestinations `json:"blocked_internet_destinations,omitempty"` PolicyEnforcement *DataSourceAccountNetworkPoliciesItemsEgressNetworkAccessPolicyEnforcement `json:"policy_enforcement,omitempty"` RestrictionMode string `json:"restriction_mode"` } @@ -31,6 +37,150 @@ type DataSourceAccountNetworkPoliciesItemsEgress struct { NetworkAccess *DataSourceAccountNetworkPoliciesItemsEgressNetworkAccess `json:"network_access,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestination struct { + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestination struct { + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPrivateAccess struct { + AllowRules []DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPoliciesItemsIngressPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthenticationIdentities struct { PrincipalId int `json:"principal_id,omitempty"` PrincipalType string `json:"principal_type,omitempty"` @@ -41,8 +191,30 @@ type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesAuthentic IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -50,9 +222,14 @@ type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinati } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -86,8 +263,30 @@ type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesAuthentica IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -95,9 +294,14 @@ type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinatio } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -128,7 +332,152 @@ type DataSourceAccountNetworkPoliciesItemsIngressPublicAccess struct { } type DataSourceAccountNetworkPoliciesItemsIngress struct { - PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressPublicAccess `json:"public_access,omitempty"` + PrivateAccess *DataSourceAccountNetworkPoliciesItemsIngressPrivateAccess `json:"private_access,omitempty"` + PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressPublicAccess `json:"public_access,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestination struct { + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestination struct { + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccess struct { + AllowRules []DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { @@ -141,8 +490,30 @@ type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesAut IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -150,9 +521,14 @@ type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDes } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -186,8 +562,30 @@ type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesAuth IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -195,9 +593,14 @@ type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDest } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -228,7 +631,8 @@ type DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccess struct { } type DataSourceAccountNetworkPoliciesItemsIngressDryRun struct { - PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccess `json:"public_access,omitempty"` + PrivateAccess *DataSourceAccountNetworkPoliciesItemsIngressDryRunPrivateAccess `json:"private_access,omitempty"` + PublicAccess *DataSourceAccountNetworkPoliciesItemsIngressDryRunPublicAccess `json:"public_access,omitempty"` } type DataSourceAccountNetworkPoliciesItems 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 e788484c537..ac2ae3ee7bf 100644 --- a/bundle/internal/tf/schema/data_source_account_network_policy.go +++ b/bundle/internal/tf/schema/data_source_account_network_policy.go @@ -15,6 +15,11 @@ type DataSourceAccountNetworkPolicyEgressNetworkAccessAllowedStorageDestinations StorageDestinationType string `json:"storage_destination_type,omitempty"` } +type DataSourceAccountNetworkPolicyEgressNetworkAccessBlockedInternetDestinations struct { + Destination string `json:"destination,omitempty"` + InternetDestinationType string `json:"internet_destination_type,omitempty"` +} + type DataSourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement struct { DryRunModeProductFilter []string `json:"dry_run_mode_product_filter,omitempty"` EnforcementMode string `json:"enforcement_mode,omitempty"` @@ -23,6 +28,7 @@ type DataSourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement struct { type DataSourceAccountNetworkPolicyEgressNetworkAccess struct { AllowedInternetDestinations []DataSourceAccountNetworkPolicyEgressNetworkAccessAllowedInternetDestinations `json:"allowed_internet_destinations,omitempty"` AllowedStorageDestinations []DataSourceAccountNetworkPolicyEgressNetworkAccessAllowedStorageDestinations `json:"allowed_storage_destinations,omitempty"` + BlockedInternetDestinations []DataSourceAccountNetworkPolicyEgressNetworkAccessBlockedInternetDestinations `json:"blocked_internet_destinations,omitempty"` PolicyEnforcement *DataSourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement `json:"policy_enforcement,omitempty"` RestrictionMode string `json:"restriction_mode"` } @@ -31,6 +37,150 @@ type DataSourceAccountNetworkPolicyEgress struct { NetworkAccess *DataSourceAccountNetworkPolicyEgressNetworkAccess `json:"network_access,omitempty"` } +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestination struct { + AccountApi *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestination struct { + AccountApi *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPrivateAccess struct { + AllowRules []DataSourceAccountNetworkPolicyIngressPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPolicyIngressPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities struct { PrincipalId int `json:"principal_id,omitempty"` PrincipalType string `json:"principal_type,omitempty"` @@ -41,8 +191,30 @@ type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication s IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -50,9 +222,14 @@ type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorks } type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -86,8 +263,30 @@ type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication st IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -95,9 +294,14 @@ type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorksp } type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -128,7 +332,152 @@ type DataSourceAccountNetworkPolicyIngressPublicAccess struct { } type DataSourceAccountNetworkPolicyIngress struct { - PublicAccess *DataSourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` + PrivateAccess *DataSourceAccountNetworkPolicyIngressPrivateAccess `json:"private_access,omitempty"` + PublicAccess *DataSourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestination struct { + AccountApi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthentication struct { + Identities []DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestination struct { + AccountApi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRules struct { + Authentication *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPrivateAccess struct { + AllowRules []DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []DataSourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { @@ -141,8 +490,30 @@ type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthentica IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -150,9 +521,14 @@ type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinatio } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -186,8 +562,30 @@ type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticat IdentityType string `json:"identity_type,omitempty"` } +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -195,9 +593,14 @@ type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type DataSourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -228,7 +631,8 @@ type DataSourceAccountNetworkPolicyIngressDryRunPublicAccess struct { } type DataSourceAccountNetworkPolicyIngressDryRun struct { - PublicAccess *DataSourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` + PrivateAccess *DataSourceAccountNetworkPolicyIngressDryRunPrivateAccess `json:"private_access,omitempty"` + PublicAccess *DataSourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` } type DataSourceAccountNetworkPolicy struct { diff --git a/bundle/internal/tf/schema/data_source_alert_v2.go b/bundle/internal/tf/schema/data_source_alert_v2.go index 246b1f1f1dd..c609a8f9189 100644 --- a/bundle/internal/tf/schema/data_source_alert_v2.go +++ b/bundle/internal/tf/schema/data_source_alert_v2.go @@ -54,7 +54,7 @@ type DataSourceAlertV2Evaluation struct { } type DataSourceAlertV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAlertV2RunAs struct { diff --git a/bundle/internal/tf/schema/data_source_alerts_v2.go b/bundle/internal/tf/schema/data_source_alerts_v2.go index b24581aefe4..5675e0aa56c 100644 --- a/bundle/internal/tf/schema/data_source_alerts_v2.go +++ b/bundle/internal/tf/schema/data_source_alerts_v2.go @@ -54,7 +54,7 @@ type DataSourceAlertsV2AlertsEvaluation struct { } type DataSourceAlertsV2AlertsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAlertsV2AlertsRunAs struct { @@ -89,7 +89,7 @@ type DataSourceAlertsV2Alerts struct { } type DataSourceAlertsV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAlertsV2 struct { diff --git a/bundle/internal/tf/schema/data_source_app.go b/bundle/internal/tf/schema/data_source_app.go index 3c3df220cbd..79f34f9a3eb 100644 --- a/bundle/internal/tf/schema/data_source_app.go +++ b/bundle/internal/tf/schema/data_source_app.go @@ -210,6 +210,7 @@ type DataSourceAppApp struct { ServicePrincipalName string `json:"service_principal_name,omitempty"` Space string `json:"space,omitempty"` TelemetryExportDestinations []DataSourceAppAppTelemetryExportDestinations `json:"telemetry_export_destinations,omitempty"` + ThumbnailUrl string `json:"thumbnail_url,omitempty"` UpdateTime string `json:"update_time,omitempty"` Updater string `json:"updater,omitempty"` Url string `json:"url,omitempty"` @@ -218,7 +219,7 @@ type DataSourceAppApp struct { } type DataSourceAppProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceApp struct { diff --git a/bundle/internal/tf/schema/data_source_app_space.go b/bundle/internal/tf/schema/data_source_app_space.go index 3a1e124d5ce..3104376b5ab 100644 --- a/bundle/internal/tf/schema/data_source_app_space.go +++ b/bundle/internal/tf/schema/data_source_app_space.go @@ -3,7 +3,7 @@ package schema type DataSourceAppSpaceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppSpaceResourcesApp struct { diff --git a/bundle/internal/tf/schema/data_source_app_spaces.go b/bundle/internal/tf/schema/data_source_app_spaces.go index a00255ad025..d00ec6ff1d2 100644 --- a/bundle/internal/tf/schema/data_source_app_spaces.go +++ b/bundle/internal/tf/schema/data_source_app_spaces.go @@ -3,11 +3,11 @@ package schema type DataSourceAppSpacesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppSpacesSpacesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppSpacesSpacesResourcesApp struct { diff --git a/bundle/internal/tf/schema/data_source_apps.go b/bundle/internal/tf/schema/data_source_apps.go index 9f775036f84..e20104570d9 100644 --- a/bundle/internal/tf/schema/data_source_apps.go +++ b/bundle/internal/tf/schema/data_source_apps.go @@ -210,6 +210,7 @@ type DataSourceAppsApp struct { ServicePrincipalName string `json:"service_principal_name,omitempty"` Space string `json:"space,omitempty"` TelemetryExportDestinations []DataSourceAppsAppTelemetryExportDestinations `json:"telemetry_export_destinations,omitempty"` + ThumbnailUrl string `json:"thumbnail_url,omitempty"` UpdateTime string `json:"update_time,omitempty"` Updater string `json:"updater,omitempty"` Url string `json:"url,omitempty"` @@ -218,7 +219,7 @@ type DataSourceAppsApp struct { } type DataSourceAppsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceApps struct { diff --git a/bundle/internal/tf/schema/data_source_apps_settings_custom_template.go b/bundle/internal/tf/schema/data_source_apps_settings_custom_template.go index afd082a22e4..e1272bb25f8 100644 --- a/bundle/internal/tf/schema/data_source_apps_settings_custom_template.go +++ b/bundle/internal/tf/schema/data_source_apps_settings_custom_template.go @@ -46,7 +46,7 @@ type DataSourceAppsSettingsCustomTemplateManifest struct { } type DataSourceAppsSettingsCustomTemplateProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppsSettingsCustomTemplate struct { diff --git a/bundle/internal/tf/schema/data_source_apps_settings_custom_templates.go b/bundle/internal/tf/schema/data_source_apps_settings_custom_templates.go index 5c846dba929..116e2c99dfb 100644 --- a/bundle/internal/tf/schema/data_source_apps_settings_custom_templates.go +++ b/bundle/internal/tf/schema/data_source_apps_settings_custom_templates.go @@ -3,7 +3,7 @@ package schema type DataSourceAppsSettingsCustomTemplatesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppsSettingsCustomTemplatesTemplatesManifestResourceSpecsExperimentSpec struct { @@ -50,7 +50,7 @@ type DataSourceAppsSettingsCustomTemplatesTemplatesManifest struct { } type DataSourceAppsSettingsCustomTemplatesTemplatesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceAppsSettingsCustomTemplatesTemplates struct { diff --git a/bundle/internal/tf/schema/data_source_catalog.go b/bundle/internal/tf/schema/data_source_catalog.go index 5c293cd0e60..8a78637645c 100644 --- a/bundle/internal/tf/schema/data_source_catalog.go +++ b/bundle/internal/tf/schema/data_source_catalog.go @@ -52,7 +52,7 @@ type DataSourceCatalogCatalogInfo struct { } type DataSourceCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCatalog struct { diff --git a/bundle/internal/tf/schema/data_source_catalogs.go b/bundle/internal/tf/schema/data_source_catalogs.go index dfe5b2a59a7..80fb1b8bece 100644 --- a/bundle/internal/tf/schema/data_source_catalogs.go +++ b/bundle/internal/tf/schema/data_source_catalogs.go @@ -3,7 +3,7 @@ package schema type DataSourceCatalogsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCatalogs struct { diff --git a/bundle/internal/tf/schema/data_source_cluster.go b/bundle/internal/tf/schema/data_source_cluster.go index b3f7d3a2d44..b77ff1c1bf6 100644 --- a/bundle/internal/tf/schema/data_source_cluster.go +++ b/bundle/internal/tf/schema/data_source_cluster.go @@ -106,6 +106,7 @@ type DataSourceClusterClusterInfoExecutors struct { type DataSourceClusterClusterInfoGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -239,6 +240,7 @@ type DataSourceClusterClusterInfoSpecDriverNodeTypeFlexibility struct { type DataSourceClusterClusterInfoSpecGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -302,7 +304,7 @@ type DataSourceClusterClusterInfoSpecLibraryMaven struct { } type DataSourceClusterClusterInfoSpecLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceClusterClusterInfoSpecLibraryPypi struct { @@ -322,7 +324,7 @@ type DataSourceClusterClusterInfoSpecLibrary struct { } type DataSourceClusterClusterInfoSpecProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceClusterClusterInfoSpecWorkerNodeTypeFlexibility struct { @@ -454,7 +456,7 @@ type DataSourceClusterClusterInfo struct { } type DataSourceClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCluster struct { diff --git a/bundle/internal/tf/schema/data_source_cluster_policy.go b/bundle/internal/tf/schema/data_source_cluster_policy.go index c8e60faf3d4..fd12e287ecb 100644 --- a/bundle/internal/tf/schema/data_source_cluster_policy.go +++ b/bundle/internal/tf/schema/data_source_cluster_policy.go @@ -3,7 +3,7 @@ package schema type DataSourceClusterPolicyProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceClusterPolicy struct { diff --git a/bundle/internal/tf/schema/data_source_clusters.go b/bundle/internal/tf/schema/data_source_clusters.go index 6a4f9f3e5a8..a2eeb7eb1b3 100644 --- a/bundle/internal/tf/schema/data_source_clusters.go +++ b/bundle/internal/tf/schema/data_source_clusters.go @@ -10,7 +10,7 @@ type DataSourceClustersFilterBy struct { } type DataSourceClustersProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceClusters struct { diff --git a/bundle/internal/tf/schema/data_source_current_config.go b/bundle/internal/tf/schema/data_source_current_config.go index 0dc751a724d..f0474e8030d 100644 --- a/bundle/internal/tf/schema/data_source_current_config.go +++ b/bundle/internal/tf/schema/data_source_current_config.go @@ -3,7 +3,7 @@ package schema type DataSourceCurrentConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCurrentConfig struct { diff --git a/bundle/internal/tf/schema/data_source_current_metastore.go b/bundle/internal/tf/schema/data_source_current_metastore.go index 52c2c43f57f..b6107c28a7b 100644 --- a/bundle/internal/tf/schema/data_source_current_metastore.go +++ b/bundle/internal/tf/schema/data_source_current_metastore.go @@ -25,7 +25,7 @@ type DataSourceCurrentMetastoreMetastoreInfo struct { } type DataSourceCurrentMetastoreProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCurrentMetastore struct { diff --git a/bundle/internal/tf/schema/data_source_current_user.go b/bundle/internal/tf/schema/data_source_current_user.go index cdf33ade244..e8208effdcf 100644 --- a/bundle/internal/tf/schema/data_source_current_user.go +++ b/bundle/internal/tf/schema/data_source_current_user.go @@ -3,7 +3,7 @@ package schema type DataSourceCurrentUserProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceCurrentUser struct { diff --git a/bundle/internal/tf/schema/data_source_dashboards.go b/bundle/internal/tf/schema/data_source_dashboards.go index a86458a0883..d9a77b38fbf 100644 --- a/bundle/internal/tf/schema/data_source_dashboards.go +++ b/bundle/internal/tf/schema/data_source_dashboards.go @@ -16,7 +16,7 @@ type DataSourceDashboardsDashboards struct { } type DataSourceDashboardsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDashboards struct { diff --git a/bundle/internal/tf/schema/data_source_data_classification_catalog_config.go b/bundle/internal/tf/schema/data_source_data_classification_catalog_config.go index f6473f72707..9ad6965e041 100644 --- a/bundle/internal/tf/schema/data_source_data_classification_catalog_config.go +++ b/bundle/internal/tf/schema/data_source_data_classification_catalog_config.go @@ -12,7 +12,7 @@ type DataSourceDataClassificationCatalogConfigIncludedSchemas struct { } type DataSourceDataClassificationCatalogConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataClassificationCatalogConfig struct { diff --git a/bundle/internal/tf/schema/data_source_data_quality_monitor.go b/bundle/internal/tf/schema/data_source_data_quality_monitor.go index aca5e6e881c..251ce8ffa2b 100644 --- a/bundle/internal/tf/schema/data_source_data_quality_monitor.go +++ b/bundle/internal/tf/schema/data_source_data_quality_monitor.go @@ -69,7 +69,7 @@ type DataSourceDataQualityMonitorDataProfilingConfig struct { } type DataSourceDataQualityMonitorProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityMonitor struct { diff --git a/bundle/internal/tf/schema/data_source_data_quality_monitors.go b/bundle/internal/tf/schema/data_source_data_quality_monitors.go index be7417e3825..312eac04159 100644 --- a/bundle/internal/tf/schema/data_source_data_quality_monitors.go +++ b/bundle/internal/tf/schema/data_source_data_quality_monitors.go @@ -69,7 +69,7 @@ type DataSourceDataQualityMonitorsMonitorsDataProfilingConfig struct { } type DataSourceDataQualityMonitorsMonitorsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityMonitorsMonitors struct { @@ -81,7 +81,7 @@ type DataSourceDataQualityMonitorsMonitors struct { } type DataSourceDataQualityMonitorsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityMonitors struct { diff --git a/bundle/internal/tf/schema/data_source_data_quality_refresh.go b/bundle/internal/tf/schema/data_source_data_quality_refresh.go index 10078356f87..72688d44ad1 100644 --- a/bundle/internal/tf/schema/data_source_data_quality_refresh.go +++ b/bundle/internal/tf/schema/data_source_data_quality_refresh.go @@ -3,7 +3,7 @@ package schema type DataSourceDataQualityRefreshProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityRefresh struct { diff --git a/bundle/internal/tf/schema/data_source_data_quality_refreshes.go b/bundle/internal/tf/schema/data_source_data_quality_refreshes.go index fb58d1dc078..de614880559 100644 --- a/bundle/internal/tf/schema/data_source_data_quality_refreshes.go +++ b/bundle/internal/tf/schema/data_source_data_quality_refreshes.go @@ -3,11 +3,11 @@ package schema type DataSourceDataQualityRefreshesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityRefreshesRefreshesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDataQualityRefreshesRefreshes struct { diff --git a/bundle/internal/tf/schema/data_source_database_database_catalog.go b/bundle/internal/tf/schema/data_source_database_database_catalog.go index 63ce16ffcd5..318bb423e9f 100644 --- a/bundle/internal/tf/schema/data_source_database_database_catalog.go +++ b/bundle/internal/tf/schema/data_source_database_database_catalog.go @@ -3,7 +3,7 @@ package schema type DataSourceDatabaseDatabaseCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseDatabaseCatalog struct { diff --git a/bundle/internal/tf/schema/data_source_database_database_catalogs.go b/bundle/internal/tf/schema/data_source_database_database_catalogs.go index f44ca414673..e5ad2cb5505 100644 --- a/bundle/internal/tf/schema/data_source_database_database_catalogs.go +++ b/bundle/internal/tf/schema/data_source_database_database_catalogs.go @@ -3,7 +3,7 @@ package schema type DataSourceDatabaseDatabaseCatalogsDatabaseCatalogsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseDatabaseCatalogsDatabaseCatalogs struct { @@ -16,7 +16,7 @@ type DataSourceDatabaseDatabaseCatalogsDatabaseCatalogs struct { } type DataSourceDatabaseDatabaseCatalogsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseDatabaseCatalogs struct { diff --git a/bundle/internal/tf/schema/data_source_database_instance.go b/bundle/internal/tf/schema/data_source_database_instance.go index bf9ca7ca4db..1553ee91b1b 100644 --- a/bundle/internal/tf/schema/data_source_database_instance.go +++ b/bundle/internal/tf/schema/data_source_database_instance.go @@ -29,7 +29,7 @@ type DataSourceDatabaseInstanceParentInstanceRef struct { } type DataSourceDatabaseInstanceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseInstance struct { diff --git a/bundle/internal/tf/schema/data_source_database_instances.go b/bundle/internal/tf/schema/data_source_database_instances.go index 74d70e1badb..315ae1ce89e 100644 --- a/bundle/internal/tf/schema/data_source_database_instances.go +++ b/bundle/internal/tf/schema/data_source_database_instances.go @@ -29,7 +29,7 @@ type DataSourceDatabaseInstancesDatabaseInstancesParentInstanceRef struct { } type DataSourceDatabaseInstancesDatabaseInstancesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseInstancesDatabaseInstances struct { @@ -63,7 +63,7 @@ type DataSourceDatabaseInstancesDatabaseInstances struct { } type DataSourceDatabaseInstancesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseInstances struct { diff --git a/bundle/internal/tf/schema/data_source_database_synced_database_table.go b/bundle/internal/tf/schema/data_source_database_synced_database_table.go index 1ac48769e41..601dc41cc5c 100644 --- a/bundle/internal/tf/schema/data_source_database_synced_database_table.go +++ b/bundle/internal/tf/schema/data_source_database_synced_database_table.go @@ -73,7 +73,7 @@ type DataSourceDatabaseSyncedDatabaseTableDataSynchronizationStatus struct { } type DataSourceDatabaseSyncedDatabaseTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseSyncedDatabaseTableSpecNewPipelineSpec struct { diff --git a/bundle/internal/tf/schema/data_source_database_synced_database_tables.go b/bundle/internal/tf/schema/data_source_database_synced_database_tables.go index e7e280fdf01..60330ecd61b 100644 --- a/bundle/internal/tf/schema/data_source_database_synced_database_tables.go +++ b/bundle/internal/tf/schema/data_source_database_synced_database_tables.go @@ -3,7 +3,7 @@ package schema type DataSourceDatabaseSyncedDatabaseTablesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseSyncedDatabaseTablesSyncedTablesDataSynchronizationStatusContinuousUpdateStatusInitialPipelineSyncProgress struct { @@ -77,7 +77,7 @@ type DataSourceDatabaseSyncedDatabaseTablesSyncedTablesDataSynchronizationStatus } type DataSourceDatabaseSyncedDatabaseTablesSyncedTablesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDatabaseSyncedDatabaseTablesSyncedTablesSpecNewPipelineSpec struct { diff --git a/bundle/internal/tf/schema/data_source_dbfs_file.go b/bundle/internal/tf/schema/data_source_dbfs_file.go index dfd0dc37cd0..1235d0f3751 100644 --- a/bundle/internal/tf/schema/data_source_dbfs_file.go +++ b/bundle/internal/tf/schema/data_source_dbfs_file.go @@ -3,7 +3,7 @@ package schema type DataSourceDbfsFileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDbfsFile struct { diff --git a/bundle/internal/tf/schema/data_source_dbfs_file_paths.go b/bundle/internal/tf/schema/data_source_dbfs_file_paths.go index 8a3fbde369e..ca76dc0ac3c 100644 --- a/bundle/internal/tf/schema/data_source_dbfs_file_paths.go +++ b/bundle/internal/tf/schema/data_source_dbfs_file_paths.go @@ -3,7 +3,7 @@ package schema type DataSourceDbfsFilePathsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDbfsFilePaths struct { diff --git a/bundle/internal/tf/schema/data_source_directory.go b/bundle/internal/tf/schema/data_source_directory.go index fa31e37a506..ce6d2553df3 100644 --- a/bundle/internal/tf/schema/data_source_directory.go +++ b/bundle/internal/tf/schema/data_source_directory.go @@ -3,7 +3,7 @@ package schema type DataSourceDirectoryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceDirectory struct { diff --git a/bundle/internal/tf/schema/data_source_disaster_recovery_failover_group.go b/bundle/internal/tf/schema/data_source_disaster_recovery_failover_group.go new file mode 100644 index 00000000000..5b9015c71a7 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_disaster_recovery_failover_group.go @@ -0,0 +1,44 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsCatalogs struct { + Name string `json:"name"` +} + +type DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappingsUriByRegion struct { + Region string `json:"region"` + Uri string `json:"uri"` +} + +type DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappings struct { + Name string `json:"name"` + UriByRegion []DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappingsUriByRegion `json:"uri_by_region,omitempty"` +} + +type DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssets struct { + Catalogs []DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsCatalogs `json:"catalogs,omitempty"` + DataReplicationWorkspaceSet string `json:"data_replication_workspace_set"` + LocationMappings []DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappings `json:"location_mappings,omitempty"` +} + +type DataSourceDisasterRecoveryFailoverGroupWorkspaceSets struct { + Name string `json:"name"` + ReplicateWorkspaceAssets bool `json:"replicate_workspace_assets"` + StableUrlNames []string `json:"stable_url_names,omitempty"` + WorkspaceIds []string `json:"workspace_ids"` +} + +type DataSourceDisasterRecoveryFailoverGroup struct { + CreateTime string `json:"create_time,omitempty"` + EffectivePrimaryRegion string `json:"effective_primary_region,omitempty"` + Etag string `json:"etag,omitempty"` + InitialPrimaryRegion string `json:"initial_primary_region,omitempty"` + Name string `json:"name"` + Regions []string `json:"regions,omitempty"` + ReplicationPoint string `json:"replication_point,omitempty"` + State string `json:"state,omitempty"` + UnityCatalogAssets *DataSourceDisasterRecoveryFailoverGroupUnityCatalogAssets `json:"unity_catalog_assets,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + WorkspaceSets []DataSourceDisasterRecoveryFailoverGroupWorkspaceSets `json:"workspace_sets,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_disaster_recovery_failover_groups.go b/bundle/internal/tf/schema/data_source_disaster_recovery_failover_groups.go new file mode 100644 index 00000000000..474774c2c49 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_disaster_recovery_failover_groups.go @@ -0,0 +1,50 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsCatalogs struct { + Name string `json:"name"` +} + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsLocationMappingsUriByRegion struct { + Region string `json:"region"` + Uri string `json:"uri"` +} + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsLocationMappings struct { + Name string `json:"name"` + UriByRegion []DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsLocationMappingsUriByRegion `json:"uri_by_region,omitempty"` +} + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssets struct { + Catalogs []DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsCatalogs `json:"catalogs,omitempty"` + DataReplicationWorkspaceSet string `json:"data_replication_workspace_set"` + LocationMappings []DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssetsLocationMappings `json:"location_mappings,omitempty"` +} + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsWorkspaceSets struct { + Name string `json:"name"` + ReplicateWorkspaceAssets bool `json:"replicate_workspace_assets"` + StableUrlNames []string `json:"stable_url_names,omitempty"` + WorkspaceIds []string `json:"workspace_ids"` +} + +type DataSourceDisasterRecoveryFailoverGroupsFailoverGroups struct { + CreateTime string `json:"create_time,omitempty"` + EffectivePrimaryRegion string `json:"effective_primary_region,omitempty"` + Etag string `json:"etag,omitempty"` + InitialPrimaryRegion string `json:"initial_primary_region,omitempty"` + Name string `json:"name"` + Regions []string `json:"regions,omitempty"` + ReplicationPoint string `json:"replication_point,omitempty"` + State string `json:"state,omitempty"` + UnityCatalogAssets *DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsUnityCatalogAssets `json:"unity_catalog_assets,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + WorkspaceSets []DataSourceDisasterRecoveryFailoverGroupsFailoverGroupsWorkspaceSets `json:"workspace_sets,omitempty"` +} + +type DataSourceDisasterRecoveryFailoverGroups struct { + FailoverGroups []DataSourceDisasterRecoveryFailoverGroupsFailoverGroups `json:"failover_groups,omitempty"` + PageSize int `json:"page_size,omitempty"` + Parent string `json:"parent"` +} diff --git a/bundle/internal/tf/schema/data_source_disaster_recovery_stable_url.go b/bundle/internal/tf/schema/data_source_disaster_recovery_stable_url.go new file mode 100644 index 00000000000..3e5913e45ce --- /dev/null +++ b/bundle/internal/tf/schema/data_source_disaster_recovery_stable_url.go @@ -0,0 +1,9 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceDisasterRecoveryStableUrl struct { + InitialWorkspaceId string `json:"initial_workspace_id,omitempty"` + Name string `json:"name"` + Url string `json:"url,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_disaster_recovery_stable_urls.go b/bundle/internal/tf/schema/data_source_disaster_recovery_stable_urls.go new file mode 100644 index 00000000000..3685db12d51 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_disaster_recovery_stable_urls.go @@ -0,0 +1,15 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceDisasterRecoveryStableUrlsStableUrls struct { + InitialWorkspaceId string `json:"initial_workspace_id,omitempty"` + Name string `json:"name"` + Url string `json:"url,omitempty"` +} + +type DataSourceDisasterRecoveryStableUrls struct { + PageSize int `json:"page_size,omitempty"` + Parent string `json:"parent"` + StableUrls []DataSourceDisasterRecoveryStableUrlsStableUrls `json:"stable_urls,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_entity_tag_assignment.go b/bundle/internal/tf/schema/data_source_entity_tag_assignment.go index 15ed8ee4137..1c3e7abfe21 100644 --- a/bundle/internal/tf/schema/data_source_entity_tag_assignment.go +++ b/bundle/internal/tf/schema/data_source_entity_tag_assignment.go @@ -3,7 +3,7 @@ package schema type DataSourceEntityTagAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEntityTagAssignment struct { diff --git a/bundle/internal/tf/schema/data_source_entity_tag_assignments.go b/bundle/internal/tf/schema/data_source_entity_tag_assignments.go index 50126aa3317..18a57950e73 100644 --- a/bundle/internal/tf/schema/data_source_entity_tag_assignments.go +++ b/bundle/internal/tf/schema/data_source_entity_tag_assignments.go @@ -3,11 +3,11 @@ package schema type DataSourceEntityTagAssignmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEntityTagAssignmentsTagAssignmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEntityTagAssignmentsTagAssignments struct { 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 index d44ad8ff58e..f39acfa2543 100644 --- 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 @@ -3,7 +3,7 @@ package schema type DataSourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEnvironmentsDefaultWorkspaceBaseEnvironment struct { 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 index 83354027dd7..379eeb1b583 100644 --- a/bundle/internal/tf/schema/data_source_environments_workspace_base_environment.go +++ b/bundle/internal/tf/schema/data_source_environments_workspace_base_environment.go @@ -3,7 +3,7 @@ package schema type DataSourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEnvironmentsWorkspaceBaseEnvironment struct { 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 index cb4a39cd2db..ce9b2ac906d 100644 --- a/bundle/internal/tf/schema/data_source_environments_workspace_base_environments.go +++ b/bundle/internal/tf/schema/data_source_environments_workspace_base_environments.go @@ -3,11 +3,11 @@ package schema type DataSourceEnvironmentsWorkspaceBaseEnvironmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceEnvironmentsWorkspaceBaseEnvironmentsWorkspaceBaseEnvironments struct { diff --git a/bundle/internal/tf/schema/data_source_external_location.go b/bundle/internal/tf/schema/data_source_external_location.go index 54038eb3205..805918fa1f5 100644 --- a/bundle/internal/tf/schema/data_source_external_location.go +++ b/bundle/internal/tf/schema/data_source_external_location.go @@ -121,7 +121,7 @@ type DataSourceExternalLocationExternalLocationInfo struct { } type DataSourceExternalLocationProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceExternalLocation struct { diff --git a/bundle/internal/tf/schema/data_source_external_locations.go b/bundle/internal/tf/schema/data_source_external_locations.go index 33524023634..c66aa1b2f65 100644 --- a/bundle/internal/tf/schema/data_source_external_locations.go +++ b/bundle/internal/tf/schema/data_source_external_locations.go @@ -3,7 +3,7 @@ package schema type DataSourceExternalLocationsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceExternalLocations struct { diff --git a/bundle/internal/tf/schema/data_source_external_metadata.go b/bundle/internal/tf/schema/data_source_external_metadata.go index 5d1cd000186..8562cc3ad99 100644 --- a/bundle/internal/tf/schema/data_source_external_metadata.go +++ b/bundle/internal/tf/schema/data_source_external_metadata.go @@ -3,7 +3,7 @@ package schema type DataSourceExternalMetadataProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceExternalMetadata struct { diff --git a/bundle/internal/tf/schema/data_source_external_metadatas.go b/bundle/internal/tf/schema/data_source_external_metadatas.go index 9ccce94b4b5..d101df8455f 100644 --- a/bundle/internal/tf/schema/data_source_external_metadatas.go +++ b/bundle/internal/tf/schema/data_source_external_metadatas.go @@ -3,7 +3,7 @@ package schema type DataSourceExternalMetadatasExternalMetadataProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceExternalMetadatasExternalMetadata struct { @@ -25,7 +25,7 @@ type DataSourceExternalMetadatasExternalMetadata struct { } type DataSourceExternalMetadatasProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceExternalMetadatas 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 7cb9e275dc1..fba0cf51e05 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_feature.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_feature.go @@ -125,7 +125,7 @@ type DataSourceFeatureEngineeringFeatureLineageContext struct { } type DataSourceFeatureEngineeringFeatureProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringFeatureSourceDeltaTableSource struct { 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 cc7a00b22d6..69d30533bc3 100644 --- a/bundle/internal/tf/schema/data_source_feature_engineering_features.go +++ b/bundle/internal/tf/schema/data_source_feature_engineering_features.go @@ -125,7 +125,7 @@ type DataSourceFeatureEngineeringFeaturesFeaturesLineageContext struct { } type DataSourceFeatureEngineeringFeaturesFeaturesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringFeaturesFeaturesSourceDeltaTableSource struct { @@ -210,7 +210,7 @@ type DataSourceFeatureEngineeringFeaturesFeatures struct { } type DataSourceFeatureEngineeringFeaturesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringFeatures 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 e16324ca395..7c2cd079f70 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 @@ -16,6 +16,7 @@ type DataSourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource struc } type DataSourceFeatureEngineeringKafkaConfigBackfillSource struct { + DeltaTableName string `json:"delta_table_name,omitempty"` DeltaTableSource *DataSourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource `json:"delta_table_source,omitempty"` } @@ -24,7 +25,7 @@ type DataSourceFeatureEngineeringKafkaConfigKeySchema struct { } type DataSourceFeatureEngineeringKafkaConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringKafkaConfigSubscriptionMode struct { 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 41016125441..dd0114f9c91 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 @@ -16,6 +16,7 @@ type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsBackfillSourceDeltaTabl } type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsBackfillSource struct { + DeltaTableName string `json:"delta_table_name,omitempty"` DeltaTableSource *DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsBackfillSourceDeltaTableSource `json:"delta_table_source,omitempty"` } @@ -24,7 +25,7 @@ type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsKeySchema struct { } type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigsSubscriptionMode struct { @@ -50,7 +51,7 @@ type DataSourceFeatureEngineeringKafkaConfigsKafkaConfigs struct { } type DataSourceFeatureEngineeringKafkaConfigsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringKafkaConfigs struct { 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 bf0000ae622..76c45d435e5 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 @@ -16,7 +16,7 @@ type DataSourceFeatureEngineeringMaterializedFeatureOnlineStoreConfig struct { } type DataSourceFeatureEngineeringMaterializedFeatureProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringMaterializedFeature struct { 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 1b56f945487..92a6a4de9bc 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 @@ -16,7 +16,7 @@ type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeaturesOnlineS } type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeaturesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeatures struct { @@ -33,7 +33,7 @@ type DataSourceFeatureEngineeringMaterializedFeaturesMaterializedFeatures struct } type DataSourceFeatureEngineeringMaterializedFeaturesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFeatureEngineeringMaterializedFeatures struct { diff --git a/bundle/internal/tf/schema/data_source_functions.go b/bundle/internal/tf/schema/data_source_functions.go index 5c6005883e8..3786b6121f8 100644 --- a/bundle/internal/tf/schema/data_source_functions.go +++ b/bundle/internal/tf/schema/data_source_functions.go @@ -101,7 +101,7 @@ type DataSourceFunctionsFunctions struct { } type DataSourceFunctionsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceFunctions struct { diff --git a/bundle/internal/tf/schema/data_source_group.go b/bundle/internal/tf/schema/data_source_group.go index c68aee75fb4..b15819d3824 100644 --- a/bundle/internal/tf/schema/data_source_group.go +++ b/bundle/internal/tf/schema/data_source_group.go @@ -3,7 +3,7 @@ package schema type DataSourceGroupProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceGroup struct { diff --git a/bundle/internal/tf/schema/data_source_instance_pool.go b/bundle/internal/tf/schema/data_source_instance_pool.go index 7deade1f45f..f506278fc1c 100644 --- a/bundle/internal/tf/schema/data_source_instance_pool.go +++ b/bundle/internal/tf/schema/data_source_instance_pool.go @@ -96,7 +96,7 @@ type DataSourceInstancePoolPoolInfo struct { } type DataSourceInstancePoolProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceInstancePool struct { diff --git a/bundle/internal/tf/schema/data_source_instance_profiles.go b/bundle/internal/tf/schema/data_source_instance_profiles.go index 28027f4b2e9..d96b33bb804 100644 --- a/bundle/internal/tf/schema/data_source_instance_profiles.go +++ b/bundle/internal/tf/schema/data_source_instance_profiles.go @@ -10,7 +10,7 @@ type DataSourceInstanceProfilesInstanceProfiles struct { } type DataSourceInstanceProfilesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceInstanceProfiles struct { diff --git a/bundle/internal/tf/schema/data_source_job.go b/bundle/internal/tf/schema/data_source_job.go index 04aff874193..243c1f6ef17 100644 --- a/bundle/internal/tf/schema/data_source_job.go +++ b/bundle/internal/tf/schema/data_source_job.go @@ -242,7 +242,7 @@ type DataSourceJobJobSettingsSettingsLibraryMaven struct { } type DataSourceJobJobSettingsSettingsLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceJobJobSettingsSettingsLibraryPypi struct { @@ -601,7 +601,7 @@ type DataSourceJobJobSettingsSettingsTaskForEachTaskTaskLibraryMaven struct { } type DataSourceJobJobSettingsSettingsTaskForEachTaskTaskLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceJobJobSettingsSettingsTaskForEachTaskTaskLibraryPypi struct { @@ -975,7 +975,7 @@ type DataSourceJobJobSettingsSettingsTaskLibraryMaven struct { } type DataSourceJobJobSettingsSettingsTaskLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceJobJobSettingsSettingsTaskLibraryPypi struct { @@ -1423,7 +1423,7 @@ type DataSourceJobJobSettings struct { } type DataSourceJobProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceJob struct { diff --git a/bundle/internal/tf/schema/data_source_jobs.go b/bundle/internal/tf/schema/data_source_jobs.go index f908f5e6374..996d03e04d1 100644 --- a/bundle/internal/tf/schema/data_source_jobs.go +++ b/bundle/internal/tf/schema/data_source_jobs.go @@ -3,7 +3,7 @@ package schema type DataSourceJobsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceJobs struct { diff --git a/bundle/internal/tf/schema/data_source_knowledge_assistant.go b/bundle/internal/tf/schema/data_source_knowledge_assistant.go index 594816e28da..cf5a16cf7f2 100644 --- a/bundle/internal/tf/schema/data_source_knowledge_assistant.go +++ b/bundle/internal/tf/schema/data_source_knowledge_assistant.go @@ -3,7 +3,7 @@ package schema type DataSourceKnowledgeAssistantProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistant struct { diff --git a/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_source.go b/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_source.go index 1374e80515b..c029839237e 100644 --- a/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_source.go +++ b/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_source.go @@ -18,7 +18,7 @@ type DataSourceKnowledgeAssistantKnowledgeSourceIndex struct { } type DataSourceKnowledgeAssistantKnowledgeSourceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistantKnowledgeSource struct { diff --git a/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_sources.go b/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_sources.go index d0c059b6238..ef634a66abf 100644 --- a/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_sources.go +++ b/bundle/internal/tf/schema/data_source_knowledge_assistant_knowledge_sources.go @@ -18,7 +18,7 @@ type DataSourceKnowledgeAssistantKnowledgeSourcesKnowledgeSourcesIndex struct { } type DataSourceKnowledgeAssistantKnowledgeSourcesKnowledgeSourcesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistantKnowledgeSourcesKnowledgeSources struct { @@ -37,7 +37,7 @@ type DataSourceKnowledgeAssistantKnowledgeSourcesKnowledgeSources struct { } type DataSourceKnowledgeAssistantKnowledgeSourcesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistantKnowledgeSources struct { diff --git a/bundle/internal/tf/schema/data_source_knowledge_assistants.go b/bundle/internal/tf/schema/data_source_knowledge_assistants.go index 4b60da11016..9f198b25b54 100644 --- a/bundle/internal/tf/schema/data_source_knowledge_assistants.go +++ b/bundle/internal/tf/schema/data_source_knowledge_assistants.go @@ -3,7 +3,7 @@ package schema type DataSourceKnowledgeAssistantsKnowledgeAssistantsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistantsKnowledgeAssistants struct { @@ -22,7 +22,7 @@ type DataSourceKnowledgeAssistantsKnowledgeAssistants struct { } type DataSourceKnowledgeAssistantsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceKnowledgeAssistants struct { diff --git a/bundle/internal/tf/schema/data_source_materialized_features_feature_tag.go b/bundle/internal/tf/schema/data_source_materialized_features_feature_tag.go index a8f427afd56..d7585cee559 100644 --- a/bundle/internal/tf/schema/data_source_materialized_features_feature_tag.go +++ b/bundle/internal/tf/schema/data_source_materialized_features_feature_tag.go @@ -3,7 +3,7 @@ package schema type DataSourceMaterializedFeaturesFeatureTagProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMaterializedFeaturesFeatureTag struct { diff --git a/bundle/internal/tf/schema/data_source_materialized_features_feature_tags.go b/bundle/internal/tf/schema/data_source_materialized_features_feature_tags.go index c8a8610ec77..99c72e32a3f 100644 --- a/bundle/internal/tf/schema/data_source_materialized_features_feature_tags.go +++ b/bundle/internal/tf/schema/data_source_materialized_features_feature_tags.go @@ -3,7 +3,7 @@ package schema type DataSourceMaterializedFeaturesFeatureTagsFeatureTagsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMaterializedFeaturesFeatureTagsFeatureTags struct { @@ -13,7 +13,7 @@ type DataSourceMaterializedFeaturesFeatureTagsFeatureTags struct { } type DataSourceMaterializedFeaturesFeatureTagsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMaterializedFeaturesFeatureTags struct { diff --git a/bundle/internal/tf/schema/data_source_mlflow_experiment.go b/bundle/internal/tf/schema/data_source_mlflow_experiment.go index 13a070ae16b..792600443d1 100644 --- a/bundle/internal/tf/schema/data_source_mlflow_experiment.go +++ b/bundle/internal/tf/schema/data_source_mlflow_experiment.go @@ -3,7 +3,7 @@ package schema type DataSourceMlflowExperimentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMlflowExperimentTags struct { diff --git a/bundle/internal/tf/schema/data_source_mlflow_model.go b/bundle/internal/tf/schema/data_source_mlflow_model.go index ac150cf955b..b48390a59aa 100644 --- a/bundle/internal/tf/schema/data_source_mlflow_model.go +++ b/bundle/internal/tf/schema/data_source_mlflow_model.go @@ -24,7 +24,7 @@ type DataSourceMlflowModelLatestVersions struct { } type DataSourceMlflowModelProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMlflowModelTags struct { diff --git a/bundle/internal/tf/schema/data_source_mlflow_models.go b/bundle/internal/tf/schema/data_source_mlflow_models.go index 134c1202e96..8a8913fca42 100644 --- a/bundle/internal/tf/schema/data_source_mlflow_models.go +++ b/bundle/internal/tf/schema/data_source_mlflow_models.go @@ -3,7 +3,7 @@ package schema type DataSourceMlflowModelsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMlflowModels struct { diff --git a/bundle/internal/tf/schema/data_source_mws_credentials.go b/bundle/internal/tf/schema/data_source_mws_credentials.go index 45fa3f817c6..dd048efe127 100644 --- a/bundle/internal/tf/schema/data_source_mws_credentials.go +++ b/bundle/internal/tf/schema/data_source_mws_credentials.go @@ -3,7 +3,7 @@ package schema type DataSourceMwsCredentialsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMwsCredentials struct { diff --git a/bundle/internal/tf/schema/data_source_mws_workspaces.go b/bundle/internal/tf/schema/data_source_mws_workspaces.go index 019a2d3eef4..7369be4a03d 100644 --- a/bundle/internal/tf/schema/data_source_mws_workspaces.go +++ b/bundle/internal/tf/schema/data_source_mws_workspaces.go @@ -3,7 +3,7 @@ package schema type DataSourceMwsWorkspacesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceMwsWorkspaces struct { diff --git a/bundle/internal/tf/schema/data_source_node_type.go b/bundle/internal/tf/schema/data_source_node_type.go index 9393f2b3dbc..e1f8dffaf04 100644 --- a/bundle/internal/tf/schema/data_source_node_type.go +++ b/bundle/internal/tf/schema/data_source_node_type.go @@ -3,7 +3,7 @@ package schema type DataSourceNodeTypeProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceNodeType struct { diff --git a/bundle/internal/tf/schema/data_source_notebook.go b/bundle/internal/tf/schema/data_source_notebook.go index d43338efdec..2392b0c56f4 100644 --- a/bundle/internal/tf/schema/data_source_notebook.go +++ b/bundle/internal/tf/schema/data_source_notebook.go @@ -3,7 +3,7 @@ package schema type DataSourceNotebookProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceNotebook struct { diff --git a/bundle/internal/tf/schema/data_source_notebook_paths.go b/bundle/internal/tf/schema/data_source_notebook_paths.go index 871a30812f5..e7d391adfc8 100644 --- a/bundle/internal/tf/schema/data_source_notebook_paths.go +++ b/bundle/internal/tf/schema/data_source_notebook_paths.go @@ -3,7 +3,7 @@ package schema type DataSourceNotebookPathsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceNotebookPaths struct { diff --git a/bundle/internal/tf/schema/data_source_notification_destinations.go b/bundle/internal/tf/schema/data_source_notification_destinations.go index be66cc9a910..0f5c74f160a 100644 --- a/bundle/internal/tf/schema/data_source_notification_destinations.go +++ b/bundle/internal/tf/schema/data_source_notification_destinations.go @@ -9,7 +9,7 @@ type DataSourceNotificationDestinationsNotificationDestinations struct { } type DataSourceNotificationDestinationsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceNotificationDestinations struct { diff --git a/bundle/internal/tf/schema/data_source_online_store.go b/bundle/internal/tf/schema/data_source_online_store.go index 4c1f3823f3b..d7727a76908 100644 --- a/bundle/internal/tf/schema/data_source_online_store.go +++ b/bundle/internal/tf/schema/data_source_online_store.go @@ -3,7 +3,7 @@ package schema type DataSourceOnlineStoreProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceOnlineStore struct { diff --git a/bundle/internal/tf/schema/data_source_online_stores.go b/bundle/internal/tf/schema/data_source_online_stores.go index 3d33fabb4b0..750241675c5 100644 --- a/bundle/internal/tf/schema/data_source_online_stores.go +++ b/bundle/internal/tf/schema/data_source_online_stores.go @@ -3,7 +3,7 @@ package schema type DataSourceOnlineStoresOnlineStoresProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceOnlineStoresOnlineStores struct { @@ -18,7 +18,7 @@ type DataSourceOnlineStoresOnlineStores struct { } type DataSourceOnlineStoresProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceOnlineStores struct { diff --git a/bundle/internal/tf/schema/data_source_pipelines.go b/bundle/internal/tf/schema/data_source_pipelines.go index bbd44a0b871..21a00996f0f 100644 --- a/bundle/internal/tf/schema/data_source_pipelines.go +++ b/bundle/internal/tf/schema/data_source_pipelines.go @@ -3,7 +3,7 @@ package schema type DataSourcePipelinesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePipelines struct { diff --git a/bundle/internal/tf/schema/data_source_policy_info.go b/bundle/internal/tf/schema/data_source_policy_info.go index 8f901f3d392..9b5886185b8 100644 --- a/bundle/internal/tf/schema/data_source_policy_info.go +++ b/bundle/internal/tf/schema/data_source_policy_info.go @@ -19,7 +19,7 @@ type DataSourcePolicyInfoMatchColumns struct { } type DataSourcePolicyInfoProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePolicyInfoRowFilterUsing struct { diff --git a/bundle/internal/tf/schema/data_source_policy_infos.go b/bundle/internal/tf/schema/data_source_policy_infos.go index 0f82648ff5f..4024bd736b3 100644 --- a/bundle/internal/tf/schema/data_source_policy_infos.go +++ b/bundle/internal/tf/schema/data_source_policy_infos.go @@ -19,7 +19,7 @@ type DataSourcePolicyInfosPoliciesMatchColumns struct { } type DataSourcePolicyInfosPoliciesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePolicyInfosPoliciesRowFilterUsing struct { @@ -54,7 +54,7 @@ type DataSourcePolicyInfosPolicies struct { } type DataSourcePolicyInfosProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePolicyInfos struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_branch.go b/bundle/internal/tf/schema/data_source_postgres_branch.go index bbefd60f441..1705ffe5d94 100644 --- a/bundle/internal/tf/schema/data_source_postgres_branch.go +++ b/bundle/internal/tf/schema/data_source_postgres_branch.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresBranchProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresBranchSpec struct { @@ -17,6 +17,7 @@ type DataSourcePostgresBranchSpec struct { } type DataSourcePostgresBranchStatus struct { + BranchId string `json:"branch_id,omitempty"` CurrentState string `json:"current_state,omitempty"` Default bool `json:"default,omitempty"` ExpireTime string `json:"expire_time,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_postgres_branches.go b/bundle/internal/tf/schema/data_source_postgres_branches.go index b7dd730bdcc..4924d6f16b2 100644 --- a/bundle/internal/tf/schema/data_source_postgres_branches.go +++ b/bundle/internal/tf/schema/data_source_postgres_branches.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresBranchesBranchesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresBranchesBranchesSpec struct { @@ -17,6 +17,7 @@ type DataSourcePostgresBranchesBranchesSpec struct { } type DataSourcePostgresBranchesBranchesStatus struct { + BranchId string `json:"branch_id,omitempty"` CurrentState string `json:"current_state,omitempty"` Default bool `json:"default,omitempty"` ExpireTime string `json:"expire_time,omitempty"` @@ -41,7 +42,7 @@ type DataSourcePostgresBranchesBranches struct { } type DataSourcePostgresBranchesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresBranches struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_catalog.go b/bundle/internal/tf/schema/data_source_postgres_catalog.go index 1a7df9c533f..3986ca847bd 100644 --- a/bundle/internal/tf/schema/data_source_postgres_catalog.go +++ b/bundle/internal/tf/schema/data_source_postgres_catalog.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresCatalogSpec struct { @@ -14,6 +14,7 @@ type DataSourcePostgresCatalogSpec struct { type DataSourcePostgresCatalogStatus struct { Branch string `json:"branch,omitempty"` + CatalogId string `json:"catalog_id,omitempty"` PostgresDatabase string `json:"postgres_database,omitempty"` Project string `json:"project,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_postgres_database.go b/bundle/internal/tf/schema/data_source_postgres_database.go index 230d7a4d2e0..bba759bd110 100644 --- a/bundle/internal/tf/schema/data_source_postgres_database.go +++ b/bundle/internal/tf/schema/data_source_postgres_database.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresDatabaseProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresDatabaseSpec struct { @@ -12,6 +12,7 @@ type DataSourcePostgresDatabaseSpec struct { } type DataSourcePostgresDatabaseStatus struct { + DatabaseId string `json:"database_id,omitempty"` PostgresDatabase string `json:"postgres_database,omitempty"` Role string `json:"role,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_postgres_databases.go b/bundle/internal/tf/schema/data_source_postgres_databases.go index 6b6d144973b..25d883bbdf3 100644 --- a/bundle/internal/tf/schema/data_source_postgres_databases.go +++ b/bundle/internal/tf/schema/data_source_postgres_databases.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresDatabasesDatabasesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresDatabasesDatabasesSpec struct { @@ -12,6 +12,7 @@ type DataSourcePostgresDatabasesDatabasesSpec struct { } type DataSourcePostgresDatabasesDatabasesStatus struct { + DatabaseId string `json:"database_id,omitempty"` PostgresDatabase string `json:"postgres_database,omitempty"` Role string `json:"role,omitempty"` } @@ -27,7 +28,7 @@ type DataSourcePostgresDatabasesDatabases struct { } type DataSourcePostgresDatabasesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresDatabases struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_endpoint.go b/bundle/internal/tf/schema/data_source_postgres_endpoint.go index c3b3f9005ea..cb16cb4ce3f 100644 --- a/bundle/internal/tf/schema/data_source_postgres_endpoint.go +++ b/bundle/internal/tf/schema/data_source_postgres_endpoint.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresEndpointProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresEndpointSpecGroup struct { @@ -47,6 +47,7 @@ type DataSourcePostgresEndpointStatus struct { AutoscalingLimitMinCu float64 `json:"autoscaling_limit_min_cu,omitempty"` CurrentState string `json:"current_state,omitempty"` Disabled bool `json:"disabled,omitempty"` + EndpointId string `json:"endpoint_id,omitempty"` EndpointType string `json:"endpoint_type,omitempty"` Group *DataSourcePostgresEndpointStatusGroup `json:"group,omitempty"` Hosts *DataSourcePostgresEndpointStatusHosts `json:"hosts,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_postgres_endpoints.go b/bundle/internal/tf/schema/data_source_postgres_endpoints.go index 92384ca6adc..42a54d0e0ba 100644 --- a/bundle/internal/tf/schema/data_source_postgres_endpoints.go +++ b/bundle/internal/tf/schema/data_source_postgres_endpoints.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresEndpointsEndpointsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresEndpointsEndpointsSpecGroup struct { @@ -47,6 +47,7 @@ type DataSourcePostgresEndpointsEndpointsStatus struct { AutoscalingLimitMinCu float64 `json:"autoscaling_limit_min_cu,omitempty"` CurrentState string `json:"current_state,omitempty"` Disabled bool `json:"disabled,omitempty"` + EndpointId string `json:"endpoint_id,omitempty"` EndpointType string `json:"endpoint_type,omitempty"` Group *DataSourcePostgresEndpointsEndpointsStatusGroup `json:"group,omitempty"` Hosts *DataSourcePostgresEndpointsEndpointsStatusHosts `json:"hosts,omitempty"` @@ -67,7 +68,7 @@ type DataSourcePostgresEndpointsEndpoints struct { } type DataSourcePostgresEndpointsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresEndpoints struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_project.go b/bundle/internal/tf/schema/data_source_postgres_project.go index 8a9c3ccc69e..321c3a6e8ad 100644 --- a/bundle/internal/tf/schema/data_source_postgres_project.go +++ b/bundle/internal/tf/schema/data_source_postgres_project.go @@ -13,7 +13,7 @@ type DataSourcePostgresProjectInitialEndpointSpec struct { } type DataSourcePostgresProjectProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresProjectSpecCustomTags struct { @@ -64,14 +64,17 @@ type DataSourcePostgresProjectStatus struct { HistoryRetentionDuration string `json:"history_retention_duration,omitempty"` Owner string `json:"owner,omitempty"` PgVersion int `json:"pg_version,omitempty"` + ProjectId string `json:"project_id,omitempty"` SyntheticStorageSizeBytes int `json:"synthetic_storage_size_bytes,omitempty"` } type DataSourcePostgresProject struct { CreateTime string `json:"create_time,omitempty"` + DeleteTime string `json:"delete_time,omitempty"` InitialEndpointSpec *DataSourcePostgresProjectInitialEndpointSpec `json:"initial_endpoint_spec,omitempty"` Name string `json:"name"` ProviderConfig *DataSourcePostgresProjectProviderConfig `json:"provider_config,omitempty"` + PurgeTime string `json:"purge_time,omitempty"` Spec *DataSourcePostgresProjectSpec `json:"spec,omitempty"` Status *DataSourcePostgresProjectStatus `json:"status,omitempty"` Uid string `json:"uid,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_postgres_projects.go b/bundle/internal/tf/schema/data_source_postgres_projects.go index ca5aa51cf85..a9b1f1b7965 100644 --- a/bundle/internal/tf/schema/data_source_postgres_projects.go +++ b/bundle/internal/tf/schema/data_source_postgres_projects.go @@ -13,7 +13,7 @@ type DataSourcePostgresProjectsProjectsInitialEndpointSpec struct { } type DataSourcePostgresProjectsProjectsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresProjectsProjectsSpecCustomTags struct { @@ -64,14 +64,17 @@ type DataSourcePostgresProjectsProjectsStatus struct { HistoryRetentionDuration string `json:"history_retention_duration,omitempty"` Owner string `json:"owner,omitempty"` PgVersion int `json:"pg_version,omitempty"` + ProjectId string `json:"project_id,omitempty"` SyntheticStorageSizeBytes int `json:"synthetic_storage_size_bytes,omitempty"` } type DataSourcePostgresProjectsProjects struct { CreateTime string `json:"create_time,omitempty"` + DeleteTime string `json:"delete_time,omitempty"` InitialEndpointSpec *DataSourcePostgresProjectsProjectsInitialEndpointSpec `json:"initial_endpoint_spec,omitempty"` Name string `json:"name"` ProviderConfig *DataSourcePostgresProjectsProjectsProviderConfig `json:"provider_config,omitempty"` + PurgeTime string `json:"purge_time,omitempty"` Spec *DataSourcePostgresProjectsProjectsSpec `json:"spec,omitempty"` Status *DataSourcePostgresProjectsProjectsStatus `json:"status,omitempty"` Uid string `json:"uid,omitempty"` @@ -79,11 +82,12 @@ type DataSourcePostgresProjectsProjects struct { } type DataSourcePostgresProjectsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresProjects struct { PageSize int `json:"page_size,omitempty"` Projects []DataSourcePostgresProjectsProjects `json:"projects,omitempty"` ProviderConfig *DataSourcePostgresProjectsProviderConfig `json:"provider_config,omitempty"` + ShowDeleted bool `json:"show_deleted,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_postgres_role.go b/bundle/internal/tf/schema/data_source_postgres_role.go index 2c012a8d605..b14d3f18db4 100644 --- a/bundle/internal/tf/schema/data_source_postgres_role.go +++ b/bundle/internal/tf/schema/data_source_postgres_role.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresRoleProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresRoleSpecAttributes struct { @@ -32,6 +32,7 @@ type DataSourcePostgresRoleStatus struct { IdentityType string `json:"identity_type,omitempty"` MembershipRoles []string `json:"membership_roles,omitempty"` PostgresRole string `json:"postgres_role,omitempty"` + RoleId string `json:"role_id,omitempty"` } type DataSourcePostgresRole struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_roles.go b/bundle/internal/tf/schema/data_source_postgres_roles.go index 828e472d383..910b143ef27 100644 --- a/bundle/internal/tf/schema/data_source_postgres_roles.go +++ b/bundle/internal/tf/schema/data_source_postgres_roles.go @@ -3,11 +3,11 @@ package schema type DataSourcePostgresRolesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresRolesRolesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresRolesRolesSpecAttributes struct { @@ -36,6 +36,7 @@ type DataSourcePostgresRolesRolesStatus struct { IdentityType string `json:"identity_type,omitempty"` MembershipRoles []string `json:"membership_roles,omitempty"` PostgresRole string `json:"postgres_role,omitempty"` + RoleId string `json:"role_id,omitempty"` } type DataSourcePostgresRolesRoles struct { diff --git a/bundle/internal/tf/schema/data_source_postgres_synced_table.go b/bundle/internal/tf/schema/data_source_postgres_synced_table.go index ea8899ba077..dbdfac6c985 100644 --- a/bundle/internal/tf/schema/data_source_postgres_synced_table.go +++ b/bundle/internal/tf/schema/data_source_postgres_synced_table.go @@ -3,7 +3,7 @@ package schema type DataSourcePostgresSyncedTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourcePostgresSyncedTableSpecNewPipelineSpec struct { @@ -51,6 +51,7 @@ type DataSourcePostgresSyncedTableStatus struct { Message string `json:"message,omitempty"` OngoingSyncProgress *DataSourcePostgresSyncedTableStatusOngoingSyncProgress `json:"ongoing_sync_progress,omitempty"` PipelineId string `json:"pipeline_id,omitempty"` + Project string `json:"project,omitempty"` ProvisioningPhase string `json:"provisioning_phase,omitempty"` UnityCatalogProvisioningState string `json:"unity_catalog_provisioning_state,omitempty"` } diff --git a/bundle/internal/tf/schema/data_source_quality_monitor_v2.go b/bundle/internal/tf/schema/data_source_quality_monitor_v2.go index 14a56c76121..7e11fcdcddd 100644 --- a/bundle/internal/tf/schema/data_source_quality_monitor_v2.go +++ b/bundle/internal/tf/schema/data_source_quality_monitor_v2.go @@ -9,7 +9,7 @@ type DataSourceQualityMonitorV2AnomalyDetectionConfig struct { } type DataSourceQualityMonitorV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceQualityMonitorV2ValidityCheckConfigurationsPercentNullValidityCheck struct { diff --git a/bundle/internal/tf/schema/data_source_quality_monitors_v2.go b/bundle/internal/tf/schema/data_source_quality_monitors_v2.go index b0bd64c2fba..515a7625a83 100644 --- a/bundle/internal/tf/schema/data_source_quality_monitors_v2.go +++ b/bundle/internal/tf/schema/data_source_quality_monitors_v2.go @@ -3,7 +3,7 @@ package schema type DataSourceQualityMonitorsV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceQualityMonitorsV2QualityMonitorsAnomalyDetectionConfig struct { @@ -13,7 +13,7 @@ type DataSourceQualityMonitorsV2QualityMonitorsAnomalyDetectionConfig struct { } type DataSourceQualityMonitorsV2QualityMonitorsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceQualityMonitorsV2QualityMonitorsValidityCheckConfigurationsPercentNullValidityCheck struct { diff --git a/bundle/internal/tf/schema/data_source_registered_model.go b/bundle/internal/tf/schema/data_source_registered_model.go index afa3841d5ed..e77b9a613ac 100644 --- a/bundle/internal/tf/schema/data_source_registered_model.go +++ b/bundle/internal/tf/schema/data_source_registered_model.go @@ -29,7 +29,7 @@ type DataSourceRegisteredModelModelInfo struct { } type DataSourceRegisteredModelProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceRegisteredModel struct { diff --git a/bundle/internal/tf/schema/data_source_registered_model_versions.go b/bundle/internal/tf/schema/data_source_registered_model_versions.go index fa923bddaa6..ab389ec0ef6 100644 --- a/bundle/internal/tf/schema/data_source_registered_model_versions.go +++ b/bundle/internal/tf/schema/data_source_registered_model_versions.go @@ -60,7 +60,7 @@ type DataSourceRegisteredModelVersionsModelVersions struct { } type DataSourceRegisteredModelVersionsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceRegisteredModelVersions struct { diff --git a/bundle/internal/tf/schema/data_source_rfa_access_request_destinations.go b/bundle/internal/tf/schema/data_source_rfa_access_request_destinations.go index 9bb1cee7b73..b7f61e735fe 100644 --- a/bundle/internal/tf/schema/data_source_rfa_access_request_destinations.go +++ b/bundle/internal/tf/schema/data_source_rfa_access_request_destinations.go @@ -15,7 +15,7 @@ type DataSourceRfaAccessRequestDestinationsDestinations struct { } type DataSourceRfaAccessRequestDestinationsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceRfaAccessRequestDestinationsSecurable struct { diff --git a/bundle/internal/tf/schema/data_source_schema.go b/bundle/internal/tf/schema/data_source_schema.go index f6ec4455af4..72f74167340 100644 --- a/bundle/internal/tf/schema/data_source_schema.go +++ b/bundle/internal/tf/schema/data_source_schema.go @@ -3,7 +3,7 @@ package schema type DataSourceSchemaProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceSchemaSchemaInfoEffectivePredictiveOptimizationFlag struct { diff --git a/bundle/internal/tf/schema/data_source_schemas.go b/bundle/internal/tf/schema/data_source_schemas.go index d18f9e59023..0e726243c9e 100644 --- a/bundle/internal/tf/schema/data_source_schemas.go +++ b/bundle/internal/tf/schema/data_source_schemas.go @@ -3,7 +3,7 @@ package schema type DataSourceSchemasProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceSchemas struct { diff --git a/bundle/internal/tf/schema/data_source_secret_uc.go b/bundle/internal/tf/schema/data_source_secret_uc.go new file mode 100644 index 00000000000..b9a7a57fe6a --- /dev/null +++ b/bundle/internal/tf/schema/data_source_secret_uc.go @@ -0,0 +1,28 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSecretUcProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSecretUc struct { + BrowseOnly bool `json:"browse_only,omitempty"` + CatalogName string `json:"catalog_name,omitempty"` + Comment string `json:"comment,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + EffectiveOwner string `json:"effective_owner,omitempty"` + EffectiveValue string `json:"effective_value,omitempty"` + ExpireTime string `json:"expire_time,omitempty"` + ExternalSecretId string `json:"external_secret_id,omitempty"` + FullName string `json:"full_name"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + ProviderConfig *DataSourceSecretUcProviderConfig `json:"provider_config,omitempty"` + SchemaName string `json:"schema_name,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Value string `json:"value,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_secret_ucs.go b/bundle/internal/tf/schema/data_source_secret_ucs.go new file mode 100644 index 00000000000..e9701e92706 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_secret_ucs.go @@ -0,0 +1,41 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSecretUcsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSecretUcsSecretsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSecretUcsSecrets struct { + BrowseOnly bool `json:"browse_only,omitempty"` + CatalogName string `json:"catalog_name,omitempty"` + Comment string `json:"comment,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + EffectiveOwner string `json:"effective_owner,omitempty"` + EffectiveValue string `json:"effective_value,omitempty"` + ExpireTime string `json:"expire_time,omitempty"` + ExternalSecretId string `json:"external_secret_id,omitempty"` + FullName string `json:"full_name"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + ProviderConfig *DataSourceSecretUcsSecretsProviderConfig `json:"provider_config,omitempty"` + SchemaName string `json:"schema_name,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Value string `json:"value,omitempty"` +} + +type DataSourceSecretUcs struct { + CatalogName string `json:"catalog_name,omitempty"` + IncludeBrowse bool `json:"include_browse,omitempty"` + PageSize int `json:"page_size,omitempty"` + ProviderConfig *DataSourceSecretUcsProviderConfig `json:"provider_config,omitempty"` + SchemaName string `json:"schema_name,omitempty"` + Secrets []DataSourceSecretUcsSecrets `json:"secrets,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_service_principal.go b/bundle/internal/tf/schema/data_source_service_principal.go index 86024a87e9c..a0c4e53835c 100644 --- a/bundle/internal/tf/schema/data_source_service_principal.go +++ b/bundle/internal/tf/schema/data_source_service_principal.go @@ -3,12 +3,13 @@ package schema type DataSourceServicePrincipalProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceServicePrincipal struct { AclPrincipalId string `json:"acl_principal_id,omitempty"` Active bool `json:"active,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_service_principals.go b/bundle/internal/tf/schema/data_source_service_principals.go index 7a84f68b07d..fa6ad18c673 100644 --- a/bundle/internal/tf/schema/data_source_service_principals.go +++ b/bundle/internal/tf/schema/data_source_service_principals.go @@ -3,7 +3,7 @@ package schema type DataSourceServicePrincipalsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceServicePrincipalsServicePrincipals struct { @@ -20,6 +20,7 @@ type DataSourceServicePrincipalsServicePrincipals struct { } type DataSourceServicePrincipals struct { + Api string `json:"api,omitempty"` ApplicationIds []string `json:"application_ids,omitempty"` DisplayNameContains string `json:"display_name_contains,omitempty"` Id string `json:"id,omitempty"` diff --git a/bundle/internal/tf/schema/data_source_serving_endpoints.go b/bundle/internal/tf/schema/data_source_serving_endpoints.go index 6f312c6a75c..c22bd7ae7f3 100644 --- a/bundle/internal/tf/schema/data_source_serving_endpoints.go +++ b/bundle/internal/tf/schema/data_source_serving_endpoints.go @@ -203,7 +203,7 @@ type DataSourceServingEndpointsEndpoints struct { } type DataSourceServingEndpointsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceServingEndpoints struct { diff --git a/bundle/internal/tf/schema/data_source_share.go b/bundle/internal/tf/schema/data_source_share.go index 91a5afff537..669dba15daa 100644 --- a/bundle/internal/tf/schema/data_source_share.go +++ b/bundle/internal/tf/schema/data_source_share.go @@ -35,7 +35,7 @@ type DataSourceShareObject struct { } type DataSourceShareProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceShare struct { diff --git a/bundle/internal/tf/schema/data_source_shares.go b/bundle/internal/tf/schema/data_source_shares.go index a4d8d6c29b7..09e7aad6db7 100644 --- a/bundle/internal/tf/schema/data_source_shares.go +++ b/bundle/internal/tf/schema/data_source_shares.go @@ -3,7 +3,7 @@ package schema type DataSourceSharesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceShares struct { diff --git a/bundle/internal/tf/schema/data_source_spark_version.go b/bundle/internal/tf/schema/data_source_spark_version.go index fdffbb94e82..9a228d683b5 100644 --- a/bundle/internal/tf/schema/data_source_spark_version.go +++ b/bundle/internal/tf/schema/data_source_spark_version.go @@ -3,7 +3,7 @@ package schema type DataSourceSparkVersionProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceSparkVersion struct { diff --git a/bundle/internal/tf/schema/data_source_sql_warehouse.go b/bundle/internal/tf/schema/data_source_sql_warehouse.go index 107d1d0a068..761149790fd 100644 --- a/bundle/internal/tf/schema/data_source_sql_warehouse.go +++ b/bundle/internal/tf/schema/data_source_sql_warehouse.go @@ -29,7 +29,7 @@ type DataSourceSqlWarehouseOdbcParams struct { } type DataSourceSqlWarehouseProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceSqlWarehouseTagsCustomTags struct { diff --git a/bundle/internal/tf/schema/data_source_sql_warehouses.go b/bundle/internal/tf/schema/data_source_sql_warehouses.go index b7ccd9d84db..1f852f10054 100644 --- a/bundle/internal/tf/schema/data_source_sql_warehouses.go +++ b/bundle/internal/tf/schema/data_source_sql_warehouses.go @@ -3,7 +3,7 @@ package schema type DataSourceSqlWarehousesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceSqlWarehouses struct { diff --git a/bundle/internal/tf/schema/data_source_storage_credential.go b/bundle/internal/tf/schema/data_source_storage_credential.go index 4940c4b83c9..55697905960 100644 --- a/bundle/internal/tf/schema/data_source_storage_credential.go +++ b/bundle/internal/tf/schema/data_source_storage_credential.go @@ -3,7 +3,7 @@ package schema type DataSourceStorageCredentialProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceStorageCredentialStorageCredentialInfoAwsIamRole struct { diff --git a/bundle/internal/tf/schema/data_source_storage_credentials.go b/bundle/internal/tf/schema/data_source_storage_credentials.go index a1b068e333e..6d40e6aaefe 100644 --- a/bundle/internal/tf/schema/data_source_storage_credentials.go +++ b/bundle/internal/tf/schema/data_source_storage_credentials.go @@ -3,7 +3,7 @@ package schema type DataSourceStorageCredentialsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceStorageCredentials struct { diff --git a/bundle/internal/tf/schema/data_source_supervisor_agent.go b/bundle/internal/tf/schema/data_source_supervisor_agent.go new file mode 100644 index 00000000000..8d4988118a6 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_supervisor_agent.go @@ -0,0 +1,21 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSupervisorAgentProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgent struct { + CreateTime string `json:"create_time,omitempty"` + Creator string `json:"creator,omitempty"` + Description string `json:"description,omitempty"` + DisplayName string `json:"display_name,omitempty"` + EndpointName string `json:"endpoint_name,omitempty"` + ExperimentId string `json:"experiment_id,omitempty"` + Id string `json:"id,omitempty"` + Instructions string `json:"instructions,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceSupervisorAgentProviderConfig `json:"provider_config,omitempty"` + SupervisorAgentId string `json:"supervisor_agent_id,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_supervisor_agent_tool.go b/bundle/internal/tf/schema/data_source_supervisor_agent_tool.go new file mode 100644 index 00000000000..8b888d97c39 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_supervisor_agent_tool.go @@ -0,0 +1,47 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSupervisorAgentToolApp struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolGenieSpace struct { + Id string `json:"id"` +} + +type DataSourceSupervisorAgentToolKnowledgeAssistant struct { + KnowledgeAssistantId string `json:"knowledge_assistant_id"` + ServingEndpointName string `json:"serving_endpoint_name,omitempty"` +} + +type DataSourceSupervisorAgentToolProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgentToolUcConnection struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolUcFunction struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolVolume struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentTool struct { + App *DataSourceSupervisorAgentToolApp `json:"app,omitempty"` + Description string `json:"description,omitempty"` + GenieSpace *DataSourceSupervisorAgentToolGenieSpace `json:"genie_space,omitempty"` + Id string `json:"id,omitempty"` + KnowledgeAssistant *DataSourceSupervisorAgentToolKnowledgeAssistant `json:"knowledge_assistant,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceSupervisorAgentToolProviderConfig `json:"provider_config,omitempty"` + ToolId string `json:"tool_id,omitempty"` + ToolType string `json:"tool_type,omitempty"` + UcConnection *DataSourceSupervisorAgentToolUcConnection `json:"uc_connection,omitempty"` + UcFunction *DataSourceSupervisorAgentToolUcFunction `json:"uc_function,omitempty"` + Volume *DataSourceSupervisorAgentToolVolume `json:"volume,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_supervisor_agent_tools.go b/bundle/internal/tf/schema/data_source_supervisor_agent_tools.go new file mode 100644 index 00000000000..fd6b9198f3d --- /dev/null +++ b/bundle/internal/tf/schema/data_source_supervisor_agent_tools.go @@ -0,0 +1,58 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSupervisorAgentToolsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgentToolsToolsApp struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolsToolsGenieSpace struct { + Id string `json:"id"` +} + +type DataSourceSupervisorAgentToolsToolsKnowledgeAssistant struct { + KnowledgeAssistantId string `json:"knowledge_assistant_id"` + ServingEndpointName string `json:"serving_endpoint_name,omitempty"` +} + +type DataSourceSupervisorAgentToolsToolsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgentToolsToolsUcConnection struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolsToolsUcFunction struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolsToolsVolume struct { + Name string `json:"name"` +} + +type DataSourceSupervisorAgentToolsTools struct { + App *DataSourceSupervisorAgentToolsToolsApp `json:"app,omitempty"` + Description string `json:"description,omitempty"` + GenieSpace *DataSourceSupervisorAgentToolsToolsGenieSpace `json:"genie_space,omitempty"` + Id string `json:"id,omitempty"` + KnowledgeAssistant *DataSourceSupervisorAgentToolsToolsKnowledgeAssistant `json:"knowledge_assistant,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceSupervisorAgentToolsToolsProviderConfig `json:"provider_config,omitempty"` + ToolId string `json:"tool_id,omitempty"` + ToolType string `json:"tool_type,omitempty"` + UcConnection *DataSourceSupervisorAgentToolsToolsUcConnection `json:"uc_connection,omitempty"` + UcFunction *DataSourceSupervisorAgentToolsToolsUcFunction `json:"uc_function,omitempty"` + Volume *DataSourceSupervisorAgentToolsToolsVolume `json:"volume,omitempty"` +} + +type DataSourceSupervisorAgentTools struct { + PageSize int `json:"page_size,omitempty"` + Parent string `json:"parent"` + ProviderConfig *DataSourceSupervisorAgentToolsProviderConfig `json:"provider_config,omitempty"` + Tools []DataSourceSupervisorAgentToolsTools `json:"tools,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_supervisor_agents.go b/bundle/internal/tf/schema/data_source_supervisor_agents.go new file mode 100644 index 00000000000..52ad02c3fd2 --- /dev/null +++ b/bundle/internal/tf/schema/data_source_supervisor_agents.go @@ -0,0 +1,31 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type DataSourceSupervisorAgentsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgentsSupervisorAgentsProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type DataSourceSupervisorAgentsSupervisorAgents struct { + CreateTime string `json:"create_time,omitempty"` + Creator string `json:"creator,omitempty"` + Description string `json:"description,omitempty"` + DisplayName string `json:"display_name,omitempty"` + EndpointName string `json:"endpoint_name,omitempty"` + ExperimentId string `json:"experiment_id,omitempty"` + Id string `json:"id,omitempty"` + Instructions string `json:"instructions,omitempty"` + Name string `json:"name"` + ProviderConfig *DataSourceSupervisorAgentsSupervisorAgentsProviderConfig `json:"provider_config,omitempty"` + SupervisorAgentId string `json:"supervisor_agent_id,omitempty"` +} + +type DataSourceSupervisorAgents struct { + PageSize int `json:"page_size,omitempty"` + ProviderConfig *DataSourceSupervisorAgentsProviderConfig `json:"provider_config,omitempty"` + SupervisorAgents []DataSourceSupervisorAgentsSupervisorAgents `json:"supervisor_agents,omitempty"` +} diff --git a/bundle/internal/tf/schema/data_source_table.go b/bundle/internal/tf/schema/data_source_table.go index e3511d87e33..f75abfd4fc5 100644 --- a/bundle/internal/tf/schema/data_source_table.go +++ b/bundle/internal/tf/schema/data_source_table.go @@ -3,7 +3,7 @@ package schema type DataSourceTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceTableTableInfoColumnsMaskUsingArguments struct { diff --git a/bundle/internal/tf/schema/data_source_tables.go b/bundle/internal/tf/schema/data_source_tables.go index 1f692434633..a6bce641453 100644 --- a/bundle/internal/tf/schema/data_source_tables.go +++ b/bundle/internal/tf/schema/data_source_tables.go @@ -3,7 +3,7 @@ package schema type DataSourceTablesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceTables struct { diff --git a/bundle/internal/tf/schema/data_source_tag_policies.go b/bundle/internal/tf/schema/data_source_tag_policies.go index d5dbe527b8d..005348a243c 100644 --- a/bundle/internal/tf/schema/data_source_tag_policies.go +++ b/bundle/internal/tf/schema/data_source_tag_policies.go @@ -3,11 +3,11 @@ package schema type DataSourceTagPoliciesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceTagPoliciesTagPoliciesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceTagPoliciesTagPoliciesValues struct { diff --git a/bundle/internal/tf/schema/data_source_tag_policy.go b/bundle/internal/tf/schema/data_source_tag_policy.go index d9e84e86dc9..2d804be4124 100644 --- a/bundle/internal/tf/schema/data_source_tag_policy.go +++ b/bundle/internal/tf/schema/data_source_tag_policy.go @@ -3,7 +3,7 @@ package schema type DataSourceTagPolicyProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceTagPolicyValues struct { diff --git a/bundle/internal/tf/schema/data_source_user.go b/bundle/internal/tf/schema/data_source_user.go index 4fc7924aae9..fa042c38d30 100644 --- a/bundle/internal/tf/schema/data_source_user.go +++ b/bundle/internal/tf/schema/data_source_user.go @@ -3,7 +3,7 @@ package schema type DataSourceUserProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceUser struct { diff --git a/bundle/internal/tf/schema/data_source_users.go b/bundle/internal/tf/schema/data_source_users.go index 96326f7a291..6fea188b6f6 100644 --- a/bundle/internal/tf/schema/data_source_users.go +++ b/bundle/internal/tf/schema/data_source_users.go @@ -3,7 +3,7 @@ package schema type DataSourceUsersProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceUsersUsersEmails struct { diff --git a/bundle/internal/tf/schema/data_source_views.go b/bundle/internal/tf/schema/data_source_views.go index abdbc60b865..06cd2d38b4d 100644 --- a/bundle/internal/tf/schema/data_source_views.go +++ b/bundle/internal/tf/schema/data_source_views.go @@ -3,7 +3,7 @@ package schema type DataSourceViewsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceViews struct { diff --git a/bundle/internal/tf/schema/data_source_volume.go b/bundle/internal/tf/schema/data_source_volume.go index f62f6fdcbff..3b16a05aac8 100644 --- a/bundle/internal/tf/schema/data_source_volume.go +++ b/bundle/internal/tf/schema/data_source_volume.go @@ -3,7 +3,7 @@ package schema type DataSourceVolumeProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceVolumeVolumeInfoEncryptionDetailsSseEncryptionDetails struct { diff --git a/bundle/internal/tf/schema/data_source_volumes.go b/bundle/internal/tf/schema/data_source_volumes.go index 3618957c957..74ff63435cb 100644 --- a/bundle/internal/tf/schema/data_source_volumes.go +++ b/bundle/internal/tf/schema/data_source_volumes.go @@ -3,7 +3,7 @@ package schema type DataSourceVolumesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceVolumes struct { diff --git a/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_override.go b/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_override.go index 0521521bf0f..9ea9f27a0a8 100644 --- a/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_override.go +++ b/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_override.go @@ -3,7 +3,7 @@ package schema type DataSourceWarehousesDefaultWarehouseOverrideProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWarehousesDefaultWarehouseOverride struct { diff --git a/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_overrides.go b/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_overrides.go index 7939c1444e4..01828617814 100644 --- a/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_overrides.go +++ b/bundle/internal/tf/schema/data_source_warehouses_default_warehouse_overrides.go @@ -3,7 +3,7 @@ package schema type DataSourceWarehousesDefaultWarehouseOverridesDefaultWarehouseOverridesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWarehousesDefaultWarehouseOverridesDefaultWarehouseOverrides struct { @@ -15,7 +15,7 @@ type DataSourceWarehousesDefaultWarehouseOverridesDefaultWarehouseOverrides stru } type DataSourceWarehousesDefaultWarehouseOverridesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWarehousesDefaultWarehouseOverrides struct { diff --git a/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignment.go b/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignment.go index 9fa9a358354..0d11833151a 100644 --- a/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignment.go +++ b/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignment.go @@ -3,7 +3,7 @@ package schema type DataSourceWorkspaceEntityTagAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWorkspaceEntityTagAssignment struct { diff --git a/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignments.go b/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignments.go index 7fa301dbed4..3bc41128f7a 100644 --- a/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignments.go +++ b/bundle/internal/tf/schema/data_source_workspace_entity_tag_assignments.go @@ -3,11 +3,11 @@ package schema type DataSourceWorkspaceEntityTagAssignmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWorkspaceEntityTagAssignmentsTagAssignmentsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWorkspaceEntityTagAssignmentsTagAssignments struct { 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 f65716e9af8..fcca8a1ce4d 100644 --- a/bundle/internal/tf/schema/data_source_workspace_setting_v2.go +++ b/bundle/internal/tf/schema/data_source_workspace_setting_v2.go @@ -110,7 +110,7 @@ type DataSourceWorkspaceSettingV2PersonalCompute struct { } type DataSourceWorkspaceSettingV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceWorkspaceSettingV2RestrictWorkspaceAdmins struct { diff --git a/bundle/internal/tf/schema/data_source_zones.go b/bundle/internal/tf/schema/data_source_zones.go index fdb8459adb1..44560facdc3 100644 --- a/bundle/internal/tf/schema/data_source_zones.go +++ b/bundle/internal/tf/schema/data_source_zones.go @@ -3,7 +3,7 @@ package schema type DataSourceZonesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type DataSourceZones struct { diff --git a/bundle/internal/tf/schema/data_sources.go b/bundle/internal/tf/schema/data_sources.go index 8dde049fe16..676a09a6584 100644 --- a/bundle/internal/tf/schema/data_sources.go +++ b/bundle/internal/tf/schema/data_sources.go @@ -47,6 +47,10 @@ type DataSources struct { 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"` + DisasterRecoveryFailoverGroup map[string]any `json:"databricks_disaster_recovery_failover_group,omitempty"` + DisasterRecoveryFailoverGroups map[string]any `json:"databricks_disaster_recovery_failover_groups,omitempty"` + DisasterRecoveryStableUrl map[string]any `json:"databricks_disaster_recovery_stable_url,omitempty"` + DisasterRecoveryStableUrls map[string]any `json:"databricks_disaster_recovery_stable_urls,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"` @@ -113,6 +117,8 @@ type DataSources struct { 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"` + SecretUc map[string]any `json:"databricks_secret_uc,omitempty"` + SecretUcs map[string]any `json:"databricks_secret_ucs,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"` @@ -125,6 +131,10 @@ type DataSources struct { 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"` + SupervisorAgent map[string]any `json:"databricks_supervisor_agent,omitempty"` + SupervisorAgentTool map[string]any `json:"databricks_supervisor_agent_tool,omitempty"` + SupervisorAgentTools map[string]any `json:"databricks_supervisor_agent_tools,omitempty"` + SupervisorAgents map[string]any `json:"databricks_supervisor_agents,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"` @@ -189,6 +199,10 @@ func NewDataSources() *DataSources { DbfsFile: make(map[string]any), DbfsFilePaths: make(map[string]any), Directory: make(map[string]any), + DisasterRecoveryFailoverGroup: make(map[string]any), + DisasterRecoveryFailoverGroups: make(map[string]any), + DisasterRecoveryStableUrl: make(map[string]any), + DisasterRecoveryStableUrls: make(map[string]any), Endpoint: make(map[string]any), Endpoints: make(map[string]any), EntityTagAssignment: make(map[string]any), @@ -255,6 +269,8 @@ func NewDataSources() *DataSources { RfaAccessRequestDestinations: make(map[string]any), Schema: make(map[string]any), Schemas: make(map[string]any), + SecretUc: make(map[string]any), + SecretUcs: make(map[string]any), ServicePrincipal: make(map[string]any), ServicePrincipalFederationPolicies: make(map[string]any), ServicePrincipalFederationPolicy: make(map[string]any), @@ -267,6 +283,10 @@ func NewDataSources() *DataSources { SqlWarehouses: make(map[string]any), StorageCredential: make(map[string]any), StorageCredentials: make(map[string]any), + SupervisorAgent: make(map[string]any), + SupervisorAgentTool: make(map[string]any), + SupervisorAgentTools: make(map[string]any), + SupervisorAgents: make(map[string]any), Table: make(map[string]any), Tables: make(map[string]any), TagPolicies: 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 10f84ef066f..62fd8ba29e0 100644 --- a/bundle/internal/tf/schema/resource_access_control_rule_set.go +++ b/bundle/internal/tf/schema/resource_access_control_rule_set.go @@ -8,7 +8,7 @@ type ResourceAccessControlRuleSetGrantRules struct { } type ResourceAccessControlRuleSetProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAccessControlRuleSet struct { diff --git a/bundle/internal/tf/schema/resource_account_network_policy.go b/bundle/internal/tf/schema/resource_account_network_policy.go index 9cb5c8524dc..294997a5d64 100644 --- a/bundle/internal/tf/schema/resource_account_network_policy.go +++ b/bundle/internal/tf/schema/resource_account_network_policy.go @@ -15,6 +15,11 @@ type ResourceAccountNetworkPolicyEgressNetworkAccessAllowedStorageDestinations s StorageDestinationType string `json:"storage_destination_type,omitempty"` } +type ResourceAccountNetworkPolicyEgressNetworkAccessBlockedInternetDestinations struct { + Destination string `json:"destination,omitempty"` + InternetDestinationType string `json:"internet_destination_type,omitempty"` +} + type ResourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement struct { DryRunModeProductFilter []string `json:"dry_run_mode_product_filter,omitempty"` EnforcementMode string `json:"enforcement_mode,omitempty"` @@ -23,6 +28,7 @@ type ResourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement struct { type ResourceAccountNetworkPolicyEgressNetworkAccess struct { AllowedInternetDestinations []ResourceAccountNetworkPolicyEgressNetworkAccessAllowedInternetDestinations `json:"allowed_internet_destinations,omitempty"` AllowedStorageDestinations []ResourceAccountNetworkPolicyEgressNetworkAccessAllowedStorageDestinations `json:"allowed_storage_destinations,omitempty"` + BlockedInternetDestinations []ResourceAccountNetworkPolicyEgressNetworkAccessBlockedInternetDestinations `json:"blocked_internet_destinations,omitempty"` PolicyEnforcement *ResourceAccountNetworkPolicyEgressNetworkAccessPolicyEnforcement `json:"policy_enforcement,omitempty"` RestrictionMode string `json:"restriction_mode"` } @@ -31,6 +37,150 @@ type ResourceAccountNetworkPolicyEgress struct { NetworkAccess *ResourceAccountNetworkPolicyEgressNetworkAccess `json:"network_access,omitempty"` } +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestination struct { + AccountApi *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessAllowRules struct { + Authentication *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestination struct { + AccountApi *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccessDenyRules struct { + Authentication *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPrivateAccess struct { + AllowRules []ResourceAccountNetworkPolicyIngressPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []ResourceAccountNetworkPolicyIngressPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` +} + type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthenticationIdentities struct { PrincipalId int `json:"principal_id,omitempty"` PrincipalType string `json:"principal_type,omitempty"` @@ -41,8 +191,30 @@ type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesAuthentication str IdentityType string `json:"identity_type,omitempty"` } +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -50,9 +222,14 @@ type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspa } type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type ResourceAccountNetworkPolicyIngressPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -86,8 +263,30 @@ type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesAuthentication stru IdentityType string `json:"identity_type,omitempty"` } +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -95,9 +294,14 @@ type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspac } type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type ResourceAccountNetworkPolicyIngressPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -128,7 +332,152 @@ type ResourceAccountNetworkPolicyIngressPublicAccess struct { } type ResourceAccountNetworkPolicyIngress struct { - PublicAccess *ResourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` + PrivateAccess *ResourceAccountNetworkPolicyIngressPrivateAccess `json:"private_access,omitempty"` + PublicAccess *ResourceAccountNetworkPolicyIngressPublicAccess `json:"public_access,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestination struct { + AccountApi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRules struct { + Authentication *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities struct { + PrincipalId int `json:"principal_id,omitempty"` + PrincipalType string `json:"principal_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthentication struct { + Identities []ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthenticationIdentities `json:"identities,omitempty"` + IdentityType string `json:"identity_type,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestination struct { + AccountApi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOriginEndpoints struct { + EndpointIds []string `json:"endpoint_ids,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOrigin struct { + AllPrivateAccess bool `json:"all_private_access,omitempty"` + AllRegisteredEndpoints bool `json:"all_registered_endpoints,omitempty"` + AzureWorkspacePrivateLink bool `json:"azure_workspace_private_link,omitempty"` + Endpoints *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOriginEndpoints `json:"endpoints,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRules struct { + Authentication *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesAuthentication `json:"authentication,omitempty"` + Destination *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesDestination `json:"destination,omitempty"` + Label string `json:"label,omitempty"` + Origin *ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRulesOrigin `json:"origin,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPrivateAccess struct { + AllowRules []ResourceAccountNetworkPolicyIngressDryRunPrivateAccessAllowRules `json:"allow_rules,omitempty"` + DenyRules []ResourceAccountNetworkPolicyIngressDryRunPrivateAccessDenyRules `json:"deny_rules,omitempty"` + RestrictionMode string `json:"restriction_mode"` } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticationIdentities struct { @@ -141,8 +490,30 @@ type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesAuthenticati IdentityType string `json:"identity_type,omitempty"` } +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi struct { @@ -150,9 +521,14 @@ type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationW } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessAllowRulesOriginExcludedIpRanges struct { @@ -186,8 +562,30 @@ type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesAuthenticatio IdentityType string `json:"identity_type,omitempty"` } +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountApi struct { + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountUi struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + +type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime struct { + AllDestinations bool `json:"all_destinations,omitempty"` +} + type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi struct { - Scopes []string `json:"scopes,omitempty"` + ScopeQualifier string `json:"scope_qualifier,omitempty"` + Scopes []string `json:"scopes,omitempty"` } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi struct { @@ -195,9 +593,14 @@ type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWo } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestination struct { - AllDestinations bool `json:"all_destinations,omitempty"` - WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` - WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` + AccountApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountApi `json:"account_api,omitempty"` + AccountDatabricksOne *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountDatabricksOne `json:"account_databricks_one,omitempty"` + AccountUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAccountUi `json:"account_ui,omitempty"` + AllDestinations bool `json:"all_destinations,omitempty"` + AppsRuntime *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationAppsRuntime `json:"apps_runtime,omitempty"` + LakebaseRuntime *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationLakebaseRuntime `json:"lakebase_runtime,omitempty"` + WorkspaceApi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceApi `json:"workspace_api,omitempty"` + WorkspaceUi *ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesDestinationWorkspaceUi `json:"workspace_ui,omitempty"` } type ResourceAccountNetworkPolicyIngressDryRunPublicAccessDenyRulesOriginExcludedIpRanges struct { @@ -228,7 +631,8 @@ type ResourceAccountNetworkPolicyIngressDryRunPublicAccess struct { } type ResourceAccountNetworkPolicyIngressDryRun struct { - PublicAccess *ResourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` + PrivateAccess *ResourceAccountNetworkPolicyIngressDryRunPrivateAccess `json:"private_access,omitempty"` + PublicAccess *ResourceAccountNetworkPolicyIngressDryRunPublicAccess `json:"public_access,omitempty"` } type ResourceAccountNetworkPolicy struct { diff --git a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go index d249da26d49..4e67f685b59 100644 --- a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go +++ b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_access_policy_setting.go @@ -7,7 +7,7 @@ type ResourceAibiDashboardEmbeddingAccessPolicySettingAibiDashboardEmbeddingAcce } type ResourceAibiDashboardEmbeddingAccessPolicySettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAibiDashboardEmbeddingAccessPolicySetting struct { diff --git a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go index 523d622614e..f33690de6d7 100644 --- a/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go +++ b/bundle/internal/tf/schema/resource_aibi_dashboard_embedding_approved_domains_setting.go @@ -7,7 +7,7 @@ type ResourceAibiDashboardEmbeddingApprovedDomainsSettingAibiDashboardEmbeddingA } type ResourceAibiDashboardEmbeddingApprovedDomainsSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAibiDashboardEmbeddingApprovedDomainsSetting struct { diff --git a/bundle/internal/tf/schema/resource_alert.go b/bundle/internal/tf/schema/resource_alert.go index bfd7acc6cbd..043ae3773e9 100644 --- a/bundle/internal/tf/schema/resource_alert.go +++ b/bundle/internal/tf/schema/resource_alert.go @@ -28,7 +28,7 @@ type ResourceAlertCondition struct { } type ResourceAlertProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAlert struct { diff --git a/bundle/internal/tf/schema/resource_alert_v2.go b/bundle/internal/tf/schema/resource_alert_v2.go index 147c4f5c79f..b9cdec84717 100644 --- a/bundle/internal/tf/schema/resource_alert_v2.go +++ b/bundle/internal/tf/schema/resource_alert_v2.go @@ -54,7 +54,7 @@ type ResourceAlertV2Evaluation struct { } type ResourceAlertV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAlertV2RunAs struct { diff --git a/bundle/internal/tf/schema/resource_app.go b/bundle/internal/tf/schema/resource_app.go index 02a1c2c3a6f..c4c1fce02cc 100644 --- a/bundle/internal/tf/schema/resource_app.go +++ b/bundle/internal/tf/schema/resource_app.go @@ -216,6 +216,7 @@ type ResourceApp struct { ServicePrincipalName string `json:"service_principal_name,omitempty"` Space string `json:"space,omitempty"` TelemetryExportDestinations []ResourceAppTelemetryExportDestinations `json:"telemetry_export_destinations,omitempty"` + ThumbnailUrl string `json:"thumbnail_url,omitempty"` UpdateTime string `json:"update_time,omitempty"` Updater string `json:"updater,omitempty"` Url string `json:"url,omitempty"` diff --git a/bundle/internal/tf/schema/resource_app_space.go b/bundle/internal/tf/schema/resource_app_space.go index a05ee123585..0b066cfa658 100644 --- a/bundle/internal/tf/schema/resource_app_space.go +++ b/bundle/internal/tf/schema/resource_app_space.go @@ -3,7 +3,7 @@ package schema type ResourceAppSpaceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAppSpaceResourcesApp struct { diff --git a/bundle/internal/tf/schema/resource_apps_settings_custom_template.go b/bundle/internal/tf/schema/resource_apps_settings_custom_template.go index 3c89f06fd8a..91177cd48cd 100644 --- a/bundle/internal/tf/schema/resource_apps_settings_custom_template.go +++ b/bundle/internal/tf/schema/resource_apps_settings_custom_template.go @@ -46,7 +46,7 @@ type ResourceAppsSettingsCustomTemplateManifest struct { } type ResourceAppsSettingsCustomTemplateProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAppsSettingsCustomTemplate struct { diff --git a/bundle/internal/tf/schema/resource_artifact_allowlist.go b/bundle/internal/tf/schema/resource_artifact_allowlist.go index a66c720a76a..a3fce99add4 100644 --- a/bundle/internal/tf/schema/resource_artifact_allowlist.go +++ b/bundle/internal/tf/schema/resource_artifact_allowlist.go @@ -8,7 +8,7 @@ type ResourceArtifactAllowlistArtifactMatcher struct { } type ResourceArtifactAllowlistProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceArtifactAllowlist struct { diff --git a/bundle/internal/tf/schema/resource_automatic_cluster_update_workspace_setting.go b/bundle/internal/tf/schema/resource_automatic_cluster_update_workspace_setting.go index 3ce38b1d028..dc42791e089 100644 --- a/bundle/internal/tf/schema/resource_automatic_cluster_update_workspace_setting.go +++ b/bundle/internal/tf/schema/resource_automatic_cluster_update_workspace_setting.go @@ -26,7 +26,7 @@ type ResourceAutomaticClusterUpdateWorkspaceSettingAutomaticClusterUpdateWorkspa } type ResourceAutomaticClusterUpdateWorkspaceSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceAutomaticClusterUpdateWorkspaceSetting struct { diff --git a/bundle/internal/tf/schema/resource_catalog.go b/bundle/internal/tf/schema/resource_catalog.go index 1245cded7a6..51b1735b38f 100644 --- a/bundle/internal/tf/schema/resource_catalog.go +++ b/bundle/internal/tf/schema/resource_catalog.go @@ -21,7 +21,7 @@ type ResourceCatalogManagedEncryptionSettings struct { } type ResourceCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceCatalogProvisioningInfo struct { diff --git a/bundle/internal/tf/schema/resource_catalog_workspace_binding.go b/bundle/internal/tf/schema/resource_catalog_workspace_binding.go index dd805772802..55d46fb787e 100644 --- a/bundle/internal/tf/schema/resource_catalog_workspace_binding.go +++ b/bundle/internal/tf/schema/resource_catalog_workspace_binding.go @@ -3,7 +3,7 @@ package schema type ResourceCatalogWorkspaceBindingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceCatalogWorkspaceBinding struct { diff --git a/bundle/internal/tf/schema/resource_cluster.go b/bundle/internal/tf/schema/resource_cluster.go index 35070eb8577..3c3aeed7470 100644 --- a/bundle/internal/tf/schema/resource_cluster.go +++ b/bundle/internal/tf/schema/resource_cluster.go @@ -84,6 +84,7 @@ type ResourceClusterDriverNodeTypeFlexibility struct { type ResourceClusterGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -162,7 +163,7 @@ type ResourceClusterLibrary struct { } type ResourceClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceClusterWorkerNodeTypeFlexibility struct { diff --git a/bundle/internal/tf/schema/resource_cluster_policy.go b/bundle/internal/tf/schema/resource_cluster_policy.go index e1c7c414641..b82a185fd57 100644 --- a/bundle/internal/tf/schema/resource_cluster_policy.go +++ b/bundle/internal/tf/schema/resource_cluster_policy.go @@ -14,7 +14,7 @@ type ResourceClusterPolicyLibrariesMaven struct { } type ResourceClusterPolicyLibrariesProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceClusterPolicyLibrariesPypi struct { @@ -34,7 +34,7 @@ type ResourceClusterPolicyLibraries struct { } type ResourceClusterPolicyProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceClusterPolicy struct { diff --git a/bundle/internal/tf/schema/resource_compliance_security_profile_workspace_setting.go b/bundle/internal/tf/schema/resource_compliance_security_profile_workspace_setting.go index 0e3ecc332c6..2361f8598f8 100644 --- a/bundle/internal/tf/schema/resource_compliance_security_profile_workspace_setting.go +++ b/bundle/internal/tf/schema/resource_compliance_security_profile_workspace_setting.go @@ -8,7 +8,7 @@ type ResourceComplianceSecurityProfileWorkspaceSettingComplianceSecurityProfileW } type ResourceComplianceSecurityProfileWorkspaceSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceComplianceSecurityProfileWorkspaceSetting struct { diff --git a/bundle/internal/tf/schema/resource_connection.go b/bundle/internal/tf/schema/resource_connection.go index c27293c455e..b92aa1e51f3 100644 --- a/bundle/internal/tf/schema/resource_connection.go +++ b/bundle/internal/tf/schema/resource_connection.go @@ -3,7 +3,7 @@ package schema type ResourceConnectionProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceConnection struct { diff --git a/bundle/internal/tf/schema/resource_credential.go b/bundle/internal/tf/schema/resource_credential.go index bd8aae572c6..aa16b9c4d5e 100644 --- a/bundle/internal/tf/schema/resource_credential.go +++ b/bundle/internal/tf/schema/resource_credential.go @@ -27,7 +27,7 @@ type ResourceCredentialDatabricksGcpServiceAccount struct { } type ResourceCredentialProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceCredential struct { diff --git a/bundle/internal/tf/schema/resource_dashboard.go b/bundle/internal/tf/schema/resource_dashboard.go index 41ed9641eb8..686bdfaaf50 100644 --- a/bundle/internal/tf/schema/resource_dashboard.go +++ b/bundle/internal/tf/schema/resource_dashboard.go @@ -3,7 +3,7 @@ package schema type ResourceDashboardProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDashboard struct { diff --git a/bundle/internal/tf/schema/resource_data_classification_catalog_config.go b/bundle/internal/tf/schema/resource_data_classification_catalog_config.go index 14546faf18f..4fedc74410a 100644 --- a/bundle/internal/tf/schema/resource_data_classification_catalog_config.go +++ b/bundle/internal/tf/schema/resource_data_classification_catalog_config.go @@ -12,7 +12,7 @@ type ResourceDataClassificationCatalogConfigIncludedSchemas struct { } type ResourceDataClassificationCatalogConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDataClassificationCatalogConfig struct { diff --git a/bundle/internal/tf/schema/resource_data_quality_monitor.go b/bundle/internal/tf/schema/resource_data_quality_monitor.go index d6d9af24466..1aca6fdf281 100644 --- a/bundle/internal/tf/schema/resource_data_quality_monitor.go +++ b/bundle/internal/tf/schema/resource_data_quality_monitor.go @@ -69,7 +69,7 @@ type ResourceDataQualityMonitorDataProfilingConfig struct { } type ResourceDataQualityMonitorProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDataQualityMonitor struct { diff --git a/bundle/internal/tf/schema/resource_data_quality_refresh.go b/bundle/internal/tf/schema/resource_data_quality_refresh.go index 8a31a27d0b1..b753b3fe7d9 100644 --- a/bundle/internal/tf/schema/resource_data_quality_refresh.go +++ b/bundle/internal/tf/schema/resource_data_quality_refresh.go @@ -3,7 +3,7 @@ package schema type ResourceDataQualityRefreshProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDataQualityRefresh struct { diff --git a/bundle/internal/tf/schema/resource_database_database_catalog.go b/bundle/internal/tf/schema/resource_database_database_catalog.go index 56b2d2811fb..0fd350a6f84 100644 --- a/bundle/internal/tf/schema/resource_database_database_catalog.go +++ b/bundle/internal/tf/schema/resource_database_database_catalog.go @@ -3,7 +3,7 @@ package schema type ResourceDatabaseDatabaseCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDatabaseDatabaseCatalog struct { diff --git a/bundle/internal/tf/schema/resource_database_instance.go b/bundle/internal/tf/schema/resource_database_instance.go index d464813de63..abddd511ba7 100644 --- a/bundle/internal/tf/schema/resource_database_instance.go +++ b/bundle/internal/tf/schema/resource_database_instance.go @@ -29,7 +29,7 @@ type ResourceDatabaseInstanceParentInstanceRef struct { } type ResourceDatabaseInstanceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDatabaseInstance struct { diff --git a/bundle/internal/tf/schema/resource_database_synced_database_table.go b/bundle/internal/tf/schema/resource_database_synced_database_table.go index 1156775f039..32e3154084c 100644 --- a/bundle/internal/tf/schema/resource_database_synced_database_table.go +++ b/bundle/internal/tf/schema/resource_database_synced_database_table.go @@ -73,7 +73,7 @@ type ResourceDatabaseSyncedDatabaseTableDataSynchronizationStatus struct { } type ResourceDatabaseSyncedDatabaseTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDatabaseSyncedDatabaseTableSpecNewPipelineSpec struct { diff --git a/bundle/internal/tf/schema/resource_dbfs_file.go b/bundle/internal/tf/schema/resource_dbfs_file.go index 4af4f426a06..1204eb17672 100644 --- a/bundle/internal/tf/schema/resource_dbfs_file.go +++ b/bundle/internal/tf/schema/resource_dbfs_file.go @@ -3,7 +3,7 @@ package schema type ResourceDbfsFileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDbfsFile struct { diff --git a/bundle/internal/tf/schema/resource_default_namespace_setting.go b/bundle/internal/tf/schema/resource_default_namespace_setting.go index 959cb9f3b18..a115d2aea1f 100644 --- a/bundle/internal/tf/schema/resource_default_namespace_setting.go +++ b/bundle/internal/tf/schema/resource_default_namespace_setting.go @@ -7,7 +7,7 @@ type ResourceDefaultNamespaceSettingNamespace struct { } type ResourceDefaultNamespaceSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDefaultNamespaceSetting struct { diff --git a/bundle/internal/tf/schema/resource_directory.go b/bundle/internal/tf/schema/resource_directory.go index a7f5ece708c..033f16b3c9c 100644 --- a/bundle/internal/tf/schema/resource_directory.go +++ b/bundle/internal/tf/schema/resource_directory.go @@ -3,7 +3,7 @@ package schema type ResourceDirectoryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDirectory struct { diff --git a/bundle/internal/tf/schema/resource_disable_legacy_access_setting.go b/bundle/internal/tf/schema/resource_disable_legacy_access_setting.go index bd15f13b6fa..67675a526b2 100644 --- a/bundle/internal/tf/schema/resource_disable_legacy_access_setting.go +++ b/bundle/internal/tf/schema/resource_disable_legacy_access_setting.go @@ -7,7 +7,7 @@ type ResourceDisableLegacyAccessSettingDisableLegacyAccess struct { } type ResourceDisableLegacyAccessSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDisableLegacyAccessSetting struct { diff --git a/bundle/internal/tf/schema/resource_disable_legacy_dbfs_setting.go b/bundle/internal/tf/schema/resource_disable_legacy_dbfs_setting.go index 810dc275640..63e06b97b5a 100644 --- a/bundle/internal/tf/schema/resource_disable_legacy_dbfs_setting.go +++ b/bundle/internal/tf/schema/resource_disable_legacy_dbfs_setting.go @@ -7,7 +7,7 @@ type ResourceDisableLegacyDbfsSettingDisableLegacyDbfs struct { } type ResourceDisableLegacyDbfsSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDisableLegacyDbfsSetting struct { diff --git a/bundle/internal/tf/schema/resource_disable_legacy_features_setting.go b/bundle/internal/tf/schema/resource_disable_legacy_features_setting.go index fbd66beb8ee..57806e66df3 100644 --- a/bundle/internal/tf/schema/resource_disable_legacy_features_setting.go +++ b/bundle/internal/tf/schema/resource_disable_legacy_features_setting.go @@ -7,7 +7,7 @@ type ResourceDisableLegacyFeaturesSettingDisableLegacyFeatures struct { } type ResourceDisableLegacyFeaturesSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceDisableLegacyFeaturesSetting struct { diff --git a/bundle/internal/tf/schema/resource_disaster_recovery_failover_group.go b/bundle/internal/tf/schema/resource_disaster_recovery_failover_group.go new file mode 100644 index 00000000000..3097ba2aff7 --- /dev/null +++ b/bundle/internal/tf/schema/resource_disaster_recovery_failover_group.go @@ -0,0 +1,46 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsCatalogs struct { + Name string `json:"name"` +} + +type ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappingsUriByRegion struct { + Region string `json:"region"` + Uri string `json:"uri"` +} + +type ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappings struct { + Name string `json:"name"` + UriByRegion []ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappingsUriByRegion `json:"uri_by_region,omitempty"` +} + +type ResourceDisasterRecoveryFailoverGroupUnityCatalogAssets struct { + Catalogs []ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsCatalogs `json:"catalogs,omitempty"` + DataReplicationWorkspaceSet string `json:"data_replication_workspace_set"` + LocationMappings []ResourceDisasterRecoveryFailoverGroupUnityCatalogAssetsLocationMappings `json:"location_mappings,omitempty"` +} + +type ResourceDisasterRecoveryFailoverGroupWorkspaceSets struct { + Name string `json:"name"` + ReplicateWorkspaceAssets bool `json:"replicate_workspace_assets"` + StableUrlNames []string `json:"stable_url_names,omitempty"` + WorkspaceIds []string `json:"workspace_ids"` +} + +type ResourceDisasterRecoveryFailoverGroup struct { + CreateTime string `json:"create_time,omitempty"` + EffectivePrimaryRegion string `json:"effective_primary_region,omitempty"` + Etag string `json:"etag,omitempty"` + FailoverGroupId string `json:"failover_group_id"` + InitialPrimaryRegion string `json:"initial_primary_region"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + Regions []string `json:"regions"` + ReplicationPoint string `json:"replication_point,omitempty"` + State string `json:"state,omitempty"` + UnityCatalogAssets *ResourceDisasterRecoveryFailoverGroupUnityCatalogAssets `json:"unity_catalog_assets,omitempty"` + UpdateTime string `json:"update_time,omitempty"` + WorkspaceSets []ResourceDisasterRecoveryFailoverGroupWorkspaceSets `json:"workspace_sets,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_disaster_recovery_stable_url.go b/bundle/internal/tf/schema/resource_disaster_recovery_stable_url.go new file mode 100644 index 00000000000..35c19781c01 --- /dev/null +++ b/bundle/internal/tf/schema/resource_disaster_recovery_stable_url.go @@ -0,0 +1,11 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceDisasterRecoveryStableUrl struct { + InitialWorkspaceId string `json:"initial_workspace_id"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + StableUrlId string `json:"stable_url_id"` + Url string `json:"url,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_enhanced_security_monitoring_workspace_setting.go b/bundle/internal/tf/schema/resource_enhanced_security_monitoring_workspace_setting.go index d5add6fbb8a..ff625cff2a5 100644 --- a/bundle/internal/tf/schema/resource_enhanced_security_monitoring_workspace_setting.go +++ b/bundle/internal/tf/schema/resource_enhanced_security_monitoring_workspace_setting.go @@ -7,7 +7,7 @@ type ResourceEnhancedSecurityMonitoringWorkspaceSettingEnhancedSecurityMonitorin } type ResourceEnhancedSecurityMonitoringWorkspaceSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceEnhancedSecurityMonitoringWorkspaceSetting struct { diff --git a/bundle/internal/tf/schema/resource_entitlements.go b/bundle/internal/tf/schema/resource_entitlements.go index b59277f725f..887dfd95ff6 100644 --- a/bundle/internal/tf/schema/resource_entitlements.go +++ b/bundle/internal/tf/schema/resource_entitlements.go @@ -3,7 +3,7 @@ package schema type ResourceEntitlementsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceEntitlements struct { diff --git a/bundle/internal/tf/schema/resource_entity_tag_assignment.go b/bundle/internal/tf/schema/resource_entity_tag_assignment.go index 3b7da58ffec..d56127d42d7 100644 --- a/bundle/internal/tf/schema/resource_entity_tag_assignment.go +++ b/bundle/internal/tf/schema/resource_entity_tag_assignment.go @@ -3,7 +3,7 @@ package schema type ResourceEntityTagAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceEntityTagAssignment struct { 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 index 4ea78fddc95..6c2a791519b 100644 --- a/bundle/internal/tf/schema/resource_environments_default_workspace_base_environment.go +++ b/bundle/internal/tf/schema/resource_environments_default_workspace_base_environment.go @@ -3,7 +3,7 @@ package schema type ResourceEnvironmentsDefaultWorkspaceBaseEnvironmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceEnvironmentsDefaultWorkspaceBaseEnvironment struct { diff --git a/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go b/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go index 59b497b77ba..0da712fe871 100644 --- a/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go +++ b/bundle/internal/tf/schema/resource_environments_workspace_base_environment.go @@ -3,7 +3,7 @@ package schema type ResourceEnvironmentsWorkspaceBaseEnvironmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceEnvironmentsWorkspaceBaseEnvironment struct { diff --git a/bundle/internal/tf/schema/resource_external_location.go b/bundle/internal/tf/schema/resource_external_location.go index ef4fb962aef..e2f08b4172c 100644 --- a/bundle/internal/tf/schema/resource_external_location.go +++ b/bundle/internal/tf/schema/resource_external_location.go @@ -98,7 +98,7 @@ type ResourceExternalLocationFileEventQueue struct { } type ResourceExternalLocationProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceExternalLocation struct { diff --git a/bundle/internal/tf/schema/resource_external_metadata.go b/bundle/internal/tf/schema/resource_external_metadata.go index 99108f27158..59b3c080d71 100644 --- a/bundle/internal/tf/schema/resource_external_metadata.go +++ b/bundle/internal/tf/schema/resource_external_metadata.go @@ -3,7 +3,7 @@ package schema type ResourceExternalMetadataProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceExternalMetadata struct { diff --git a/bundle/internal/tf/schema/resource_feature_engineering_feature.go b/bundle/internal/tf/schema/resource_feature_engineering_feature.go index 1a99477f647..05d6f7d7e1d 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_feature.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_feature.go @@ -125,7 +125,7 @@ type ResourceFeatureEngineeringFeatureLineageContext struct { } type ResourceFeatureEngineeringFeatureProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceFeatureEngineeringFeatureSourceDeltaTableSource struct { 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 864741b6132..a82d0605917 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_kafka_config.go @@ -16,6 +16,7 @@ type ResourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource struct } type ResourceFeatureEngineeringKafkaConfigBackfillSource struct { + DeltaTableName string `json:"delta_table_name,omitempty"` DeltaTableSource *ResourceFeatureEngineeringKafkaConfigBackfillSourceDeltaTableSource `json:"delta_table_source,omitempty"` } @@ -24,7 +25,7 @@ type ResourceFeatureEngineeringKafkaConfigKeySchema struct { } type ResourceFeatureEngineeringKafkaConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceFeatureEngineeringKafkaConfigSubscriptionMode struct { 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 ddc305f0aa6..fa98704107a 100644 --- a/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go +++ b/bundle/internal/tf/schema/resource_feature_engineering_materialized_feature.go @@ -16,7 +16,7 @@ type ResourceFeatureEngineeringMaterializedFeatureOnlineStoreConfig struct { } type ResourceFeatureEngineeringMaterializedFeatureProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceFeatureEngineeringMaterializedFeature struct { diff --git a/bundle/internal/tf/schema/resource_file.go b/bundle/internal/tf/schema/resource_file.go index 9d5e7708cb9..37878338cf7 100644 --- a/bundle/internal/tf/schema/resource_file.go +++ b/bundle/internal/tf/schema/resource_file.go @@ -3,7 +3,7 @@ package schema type ResourceFileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceFile struct { diff --git a/bundle/internal/tf/schema/resource_git_credential.go b/bundle/internal/tf/schema/resource_git_credential.go index 1384282b60b..cf4c2f7f5b5 100644 --- a/bundle/internal/tf/schema/resource_git_credential.go +++ b/bundle/internal/tf/schema/resource_git_credential.go @@ -3,7 +3,7 @@ package schema type ResourceGitCredentialProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGitCredential struct { diff --git a/bundle/internal/tf/schema/resource_global_init_script.go b/bundle/internal/tf/schema/resource_global_init_script.go index 0c28e4d62bb..59c1b669df7 100644 --- a/bundle/internal/tf/schema/resource_global_init_script.go +++ b/bundle/internal/tf/schema/resource_global_init_script.go @@ -3,7 +3,7 @@ package schema type ResourceGlobalInitScriptProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGlobalInitScript struct { diff --git a/bundle/internal/tf/schema/resource_grant.go b/bundle/internal/tf/schema/resource_grant.go index 0edc341139d..6c78e87f32e 100644 --- a/bundle/internal/tf/schema/resource_grant.go +++ b/bundle/internal/tf/schema/resource_grant.go @@ -3,7 +3,7 @@ package schema type ResourceGrantProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGrant struct { diff --git a/bundle/internal/tf/schema/resource_grants.go b/bundle/internal/tf/schema/resource_grants.go index d28af8fa0a8..1d61b78b5f7 100644 --- a/bundle/internal/tf/schema/resource_grants.go +++ b/bundle/internal/tf/schema/resource_grants.go @@ -8,7 +8,7 @@ type ResourceGrantsGrant struct { } type ResourceGrantsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGrants struct { diff --git a/bundle/internal/tf/schema/resource_group.go b/bundle/internal/tf/schema/resource_group.go index 4a9eb9285ce..522ca24eabc 100644 --- a/bundle/internal/tf/schema/resource_group.go +++ b/bundle/internal/tf/schema/resource_group.go @@ -3,7 +3,7 @@ package schema type ResourceGroupProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGroup struct { diff --git a/bundle/internal/tf/schema/resource_group_instance_profile.go b/bundle/internal/tf/schema/resource_group_instance_profile.go index 3ea5402c12b..4d6e44be96f 100644 --- a/bundle/internal/tf/schema/resource_group_instance_profile.go +++ b/bundle/internal/tf/schema/resource_group_instance_profile.go @@ -3,7 +3,7 @@ package schema type ResourceGroupInstanceProfileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGroupInstanceProfile struct { diff --git a/bundle/internal/tf/schema/resource_group_member.go b/bundle/internal/tf/schema/resource_group_member.go index d849082ee2d..d6d311b17c6 100644 --- a/bundle/internal/tf/schema/resource_group_member.go +++ b/bundle/internal/tf/schema/resource_group_member.go @@ -3,7 +3,7 @@ package schema type ResourceGroupMemberProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGroupMember struct { diff --git a/bundle/internal/tf/schema/resource_group_role.go b/bundle/internal/tf/schema/resource_group_role.go index 9f2ec718685..5582f935b4d 100644 --- a/bundle/internal/tf/schema/resource_group_role.go +++ b/bundle/internal/tf/schema/resource_group_role.go @@ -3,7 +3,7 @@ package schema type ResourceGroupRoleProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceGroupRole struct { diff --git a/bundle/internal/tf/schema/resource_instance_pool.go b/bundle/internal/tf/schema/resource_instance_pool.go index aed4cf775db..a50fc463ada 100644 --- a/bundle/internal/tf/schema/resource_instance_pool.go +++ b/bundle/internal/tf/schema/resource_instance_pool.go @@ -67,7 +67,7 @@ type ResourceInstancePoolPreloadedDockerImage struct { } type ResourceInstancePoolProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceInstancePool struct { diff --git a/bundle/internal/tf/schema/resource_instance_profile.go b/bundle/internal/tf/schema/resource_instance_profile.go index c80e0f11188..5221e67576f 100644 --- a/bundle/internal/tf/schema/resource_instance_profile.go +++ b/bundle/internal/tf/schema/resource_instance_profile.go @@ -3,7 +3,7 @@ package schema type ResourceInstanceProfileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceInstanceProfile struct { diff --git a/bundle/internal/tf/schema/resource_ip_access_list.go b/bundle/internal/tf/schema/resource_ip_access_list.go index 51c720aae72..8a6cb80af9d 100644 --- a/bundle/internal/tf/schema/resource_ip_access_list.go +++ b/bundle/internal/tf/schema/resource_ip_access_list.go @@ -3,7 +3,7 @@ package schema type ResourceIpAccessListProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceIpAccessList struct { diff --git a/bundle/internal/tf/schema/resource_job.go b/bundle/internal/tf/schema/resource_job.go index d6603c62fba..25e6f9407fb 100644 --- a/bundle/internal/tf/schema/resource_job.go +++ b/bundle/internal/tf/schema/resource_job.go @@ -161,6 +161,7 @@ type ResourceJobJobClusterNewClusterDriverNodeTypeFlexibility struct { type ResourceJobJobClusterNewClusterGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -224,7 +225,7 @@ type ResourceJobJobClusterNewClusterLibraryMaven struct { } type ResourceJobJobClusterNewClusterLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobJobClusterNewClusterLibraryPypi struct { @@ -244,7 +245,7 @@ type ResourceJobJobClusterNewClusterLibrary struct { } type ResourceJobJobClusterNewClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobJobClusterNewClusterWorkerNodeTypeFlexibility struct { @@ -319,7 +320,7 @@ type ResourceJobLibraryMaven struct { } type ResourceJobLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobLibraryPypi struct { @@ -420,6 +421,7 @@ type ResourceJobNewClusterDriverNodeTypeFlexibility struct { type ResourceJobNewClusterGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -483,7 +485,7 @@ type ResourceJobNewClusterLibraryMaven struct { } type ResourceJobNewClusterLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobNewClusterLibraryPypi struct { @@ -503,7 +505,7 @@ type ResourceJobNewClusterLibrary struct { } type ResourceJobNewClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobNewClusterWorkerNodeTypeFlexibility struct { @@ -583,7 +585,7 @@ type ResourceJobPipelineTask struct { } type ResourceJobProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobPythonWheelTask struct { @@ -831,7 +833,7 @@ type ResourceJobTaskForEachTaskTaskLibraryMaven struct { } type ResourceJobTaskForEachTaskTaskLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskForEachTaskTaskLibraryPypi struct { @@ -932,6 +934,7 @@ type ResourceJobTaskForEachTaskTaskNewClusterDriverNodeTypeFlexibility struct { type ResourceJobTaskForEachTaskTaskNewClusterGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -995,7 +998,7 @@ type ResourceJobTaskForEachTaskTaskNewClusterLibraryMaven struct { } type ResourceJobTaskForEachTaskTaskNewClusterLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskForEachTaskTaskNewClusterLibraryPypi struct { @@ -1015,7 +1018,7 @@ type ResourceJobTaskForEachTaskTaskNewClusterLibrary struct { } type ResourceJobTaskForEachTaskTaskNewClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskForEachTaskTaskNewClusterWorkerNodeTypeFlexibility struct { @@ -1308,7 +1311,7 @@ type ResourceJobTaskLibraryMaven struct { } type ResourceJobTaskLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskLibraryPypi struct { @@ -1409,6 +1412,7 @@ type ResourceJobTaskNewClusterDriverNodeTypeFlexibility struct { type ResourceJobTaskNewClusterGcpAttributes struct { Availability string `json:"availability,omitempty"` BootDiskSize int `json:"boot_disk_size,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` FirstOnDemand int `json:"first_on_demand,omitempty"` GoogleServiceAccount string `json:"google_service_account,omitempty"` LocalSsdCount int `json:"local_ssd_count,omitempty"` @@ -1472,7 +1476,7 @@ type ResourceJobTaskNewClusterLibraryMaven struct { } type ResourceJobTaskNewClusterLibraryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskNewClusterLibraryPypi struct { @@ -1492,7 +1496,7 @@ type ResourceJobTaskNewClusterLibrary struct { } type ResourceJobTaskNewClusterProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceJobTaskNewClusterWorkerNodeTypeFlexibility struct { diff --git a/bundle/internal/tf/schema/resource_knowledge_assistant.go b/bundle/internal/tf/schema/resource_knowledge_assistant.go index 70da6ed0746..d03b721fe6b 100644 --- a/bundle/internal/tf/schema/resource_knowledge_assistant.go +++ b/bundle/internal/tf/schema/resource_knowledge_assistant.go @@ -3,7 +3,7 @@ package schema type ResourceKnowledgeAssistantProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceKnowledgeAssistant struct { diff --git a/bundle/internal/tf/schema/resource_knowledge_assistant_knowledge_source.go b/bundle/internal/tf/schema/resource_knowledge_assistant_knowledge_source.go index e907554aa61..fcdeca20b99 100644 --- a/bundle/internal/tf/schema/resource_knowledge_assistant_knowledge_source.go +++ b/bundle/internal/tf/schema/resource_knowledge_assistant_knowledge_source.go @@ -18,7 +18,7 @@ type ResourceKnowledgeAssistantKnowledgeSourceIndex struct { } type ResourceKnowledgeAssistantKnowledgeSourceProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceKnowledgeAssistantKnowledgeSource struct { diff --git a/bundle/internal/tf/schema/resource_lakehouse_monitor.go b/bundle/internal/tf/schema/resource_lakehouse_monitor.go index 1c1c92edcdb..af702885f2c 100644 --- a/bundle/internal/tf/schema/resource_lakehouse_monitor.go +++ b/bundle/internal/tf/schema/resource_lakehouse_monitor.go @@ -38,7 +38,7 @@ type ResourceLakehouseMonitorNotifications struct { } type ResourceLakehouseMonitorProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceLakehouseMonitorSchedule struct { diff --git a/bundle/internal/tf/schema/resource_materialized_features_feature_tag.go b/bundle/internal/tf/schema/resource_materialized_features_feature_tag.go index 482fcdf0658..f5fb0153bfb 100644 --- a/bundle/internal/tf/schema/resource_materialized_features_feature_tag.go +++ b/bundle/internal/tf/schema/resource_materialized_features_feature_tag.go @@ -3,7 +3,7 @@ package schema type ResourceMaterializedFeaturesFeatureTagProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMaterializedFeaturesFeatureTag struct { diff --git a/bundle/internal/tf/schema/resource_metastore.go b/bundle/internal/tf/schema/resource_metastore.go index 1fe6064fabe..e3bb7401f03 100644 --- a/bundle/internal/tf/schema/resource_metastore.go +++ b/bundle/internal/tf/schema/resource_metastore.go @@ -3,7 +3,7 @@ package schema type ResourceMetastoreProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMetastore struct { diff --git a/bundle/internal/tf/schema/resource_metastore_assignment.go b/bundle/internal/tf/schema/resource_metastore_assignment.go index 2127c1cec01..4460c418748 100644 --- a/bundle/internal/tf/schema/resource_metastore_assignment.go +++ b/bundle/internal/tf/schema/resource_metastore_assignment.go @@ -3,7 +3,7 @@ package schema type ResourceMetastoreAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMetastoreAssignment struct { diff --git a/bundle/internal/tf/schema/resource_metastore_data_access.go b/bundle/internal/tf/schema/resource_metastore_data_access.go index 7c79c665ef5..e49bb4a8c49 100644 --- a/bundle/internal/tf/schema/resource_metastore_data_access.go +++ b/bundle/internal/tf/schema/resource_metastore_data_access.go @@ -38,7 +38,7 @@ type ResourceMetastoreDataAccessGcpServiceAccountKey struct { } type ResourceMetastoreDataAccessProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMetastoreDataAccess struct { diff --git a/bundle/internal/tf/schema/resource_mlflow_experiment.go b/bundle/internal/tf/schema/resource_mlflow_experiment.go index b6a20dd7ad5..26ca94ba632 100644 --- a/bundle/internal/tf/schema/resource_mlflow_experiment.go +++ b/bundle/internal/tf/schema/resource_mlflow_experiment.go @@ -3,7 +3,7 @@ package schema type ResourceMlflowExperimentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMlflowExperimentTags struct { diff --git a/bundle/internal/tf/schema/resource_mlflow_model.go b/bundle/internal/tf/schema/resource_mlflow_model.go index 44d9f557c2f..72b371fec7b 100644 --- a/bundle/internal/tf/schema/resource_mlflow_model.go +++ b/bundle/internal/tf/schema/resource_mlflow_model.go @@ -3,7 +3,7 @@ package schema type ResourceMlflowModelProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMlflowModelTags struct { diff --git a/bundle/internal/tf/schema/resource_mlflow_webhook.go b/bundle/internal/tf/schema/resource_mlflow_webhook.go index bd69b1aba7a..437de2b5a39 100644 --- a/bundle/internal/tf/schema/resource_mlflow_webhook.go +++ b/bundle/internal/tf/schema/resource_mlflow_webhook.go @@ -16,7 +16,7 @@ type ResourceMlflowWebhookJobSpec struct { } type ResourceMlflowWebhookProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMlflowWebhook struct { diff --git a/bundle/internal/tf/schema/resource_model_serving.go b/bundle/internal/tf/schema/resource_model_serving.go index 58ad4b0c977..b2e136bf07c 100644 --- a/bundle/internal/tf/schema/resource_model_serving.go +++ b/bundle/internal/tf/schema/resource_model_serving.go @@ -215,7 +215,7 @@ type ResourceModelServingEmailNotifications struct { } type ResourceModelServingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceModelServingRateLimits struct { diff --git a/bundle/internal/tf/schema/resource_model_serving_provisioned_throughput.go b/bundle/internal/tf/schema/resource_model_serving_provisioned_throughput.go index 0b226efdd67..65f663ab924 100644 --- a/bundle/internal/tf/schema/resource_model_serving_provisioned_throughput.go +++ b/bundle/internal/tf/schema/resource_model_serving_provisioned_throughput.go @@ -89,7 +89,7 @@ type ResourceModelServingProvisionedThroughputEmailNotifications struct { } type ResourceModelServingProvisionedThroughputProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceModelServingProvisionedThroughputTags struct { diff --git a/bundle/internal/tf/schema/resource_mount.go b/bundle/internal/tf/schema/resource_mount.go index d1220b03b37..4a4187c320f 100644 --- a/bundle/internal/tf/schema/resource_mount.go +++ b/bundle/internal/tf/schema/resource_mount.go @@ -29,7 +29,7 @@ type ResourceMountGs struct { } type ResourceMountProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceMountS3 struct { diff --git a/bundle/internal/tf/schema/resource_notebook.go b/bundle/internal/tf/schema/resource_notebook.go index d7470f0a6a5..4f756131591 100644 --- a/bundle/internal/tf/schema/resource_notebook.go +++ b/bundle/internal/tf/schema/resource_notebook.go @@ -3,7 +3,7 @@ package schema type ResourceNotebookProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceNotebook struct { diff --git a/bundle/internal/tf/schema/resource_notification_destination.go b/bundle/internal/tf/schema/resource_notification_destination.go index 541e1561df7..b164d226167 100644 --- a/bundle/internal/tf/schema/resource_notification_destination.go +++ b/bundle/internal/tf/schema/resource_notification_destination.go @@ -51,7 +51,7 @@ type ResourceNotificationDestinationConfig struct { } type ResourceNotificationDestinationProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceNotificationDestination struct { diff --git a/bundle/internal/tf/schema/resource_obo_token.go b/bundle/internal/tf/schema/resource_obo_token.go index 5a603d2d7d4..154d760a43b 100644 --- a/bundle/internal/tf/schema/resource_obo_token.go +++ b/bundle/internal/tf/schema/resource_obo_token.go @@ -3,7 +3,7 @@ package schema type ResourceOboTokenProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceOboToken struct { diff --git a/bundle/internal/tf/schema/resource_online_store.go b/bundle/internal/tf/schema/resource_online_store.go index 804ee9071f0..5cb419f7853 100644 --- a/bundle/internal/tf/schema/resource_online_store.go +++ b/bundle/internal/tf/schema/resource_online_store.go @@ -3,7 +3,7 @@ package schema type ResourceOnlineStoreProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceOnlineStore struct { diff --git a/bundle/internal/tf/schema/resource_online_table.go b/bundle/internal/tf/schema/resource_online_table.go index b3dc27d4a7a..b2d515290fd 100644 --- a/bundle/internal/tf/schema/resource_online_table.go +++ b/bundle/internal/tf/schema/resource_online_table.go @@ -3,7 +3,7 @@ package schema type ResourceOnlineTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceOnlineTableSpecRunContinuously struct { diff --git a/bundle/internal/tf/schema/resource_permission_assignment.go b/bundle/internal/tf/schema/resource_permission_assignment.go index beb9b16f742..2a4f812ecd7 100644 --- a/bundle/internal/tf/schema/resource_permission_assignment.go +++ b/bundle/internal/tf/schema/resource_permission_assignment.go @@ -3,7 +3,7 @@ package schema type ResourcePermissionAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePermissionAssignment struct { diff --git a/bundle/internal/tf/schema/resource_permissions.go b/bundle/internal/tf/schema/resource_permissions.go index 1f9d8a38b84..65f6e4c60a1 100644 --- a/bundle/internal/tf/schema/resource_permissions.go +++ b/bundle/internal/tf/schema/resource_permissions.go @@ -10,7 +10,7 @@ type ResourcePermissionsAccessControl struct { } type ResourcePermissionsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePermissions struct { diff --git a/bundle/internal/tf/schema/resource_pipeline.go b/bundle/internal/tf/schema/resource_pipeline.go index e7bf6bb91c9..19c46ea9cf0 100644 --- a/bundle/internal/tf/schema/resource_pipeline.go +++ b/bundle/internal/tf/schema/resource_pipeline.go @@ -58,11 +58,12 @@ type ResourcePipelineClusterClusterLogConf struct { } type ResourcePipelineClusterGcpAttributes struct { - Availability string `json:"availability,omitempty"` - FirstOnDemand int `json:"first_on_demand,omitempty"` - GoogleServiceAccount string `json:"google_service_account,omitempty"` - LocalSsdCount int `json:"local_ssd_count,omitempty"` - ZoneId string `json:"zone_id,omitempty"` + Availability string `json:"availability,omitempty"` + ConfidentialComputeType string `json:"confidential_compute_type,omitempty"` + FirstOnDemand int `json:"first_on_demand,omitempty"` + GoogleServiceAccount string `json:"google_service_account,omitempty"` + LocalSsdCount int `json:"local_ssd_count,omitempty"` + ZoneId string `json:"zone_id,omitempty"` } type ResourcePipelineClusterInitScriptsAbfss struct { @@ -220,6 +221,10 @@ type ResourcePipelineIngestionDefinitionObjectsReport struct { TableConfiguration *ResourcePipelineIngestionDefinitionObjectsReportTableConfiguration `json:"table_configuration,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsConfluenceOptions struct { + IncludeConfluenceSpaces []string `json:"include_confluence_spaces,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters struct { ModifiedAfter string `json:"modified_after,omitempty"` ModifiedBefore string `json:"modified_before,omitempty"` @@ -252,6 +257,34 @@ type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGoogleAdsOp SyncStartDate string `json:"sync_start_date,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsJiraOptions struct { + IncludeJiraSpaces []string `json:"include_jira_spaces,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsMetaAdsOptions struct { + ActionAttributionWindows []string `json:"action_attribution_windows,omitempty"` + ActionBreakdowns []string `json:"action_breakdowns,omitempty"` + ActionReportTime string `json:"action_report_time,omitempty"` + Breakdowns []string `json:"breakdowns,omitempty"` + CustomInsightsLookbackWindow int `json:"custom_insights_lookback_window,omitempty"` + Level string `json:"level,omitempty"` + StartDate string `json:"start_date,omitempty"` + TimeIncrement string `json:"time_increment,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsOutlookOptions struct { + AttachmentMode string `json:"attachment_mode,omitempty"` + BodyFormat string `json:"body_format,omitempty"` + FolderFilter []string `json:"folder_filter,omitempty"` + IncludeFolders []string `json:"include_folders,omitempty"` + IncludeMailboxes []string `json:"include_mailboxes,omitempty"` + IncludeSenders []string `json:"include_senders,omitempty"` + IncludeSubjects []string `json:"include_subjects,omitempty"` + SenderFilter []string `json:"sender_filter,omitempty"` + StartDate string `json:"start_date,omitempty"` + SubjectFilter []string `json:"subject_filter,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters struct { ModifiedAfter string `json:"modified_after,omitempty"` ModifiedBefore string `json:"modified_before,omitempty"` @@ -278,6 +311,10 @@ type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointO FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSmartsheetOptions struct { + EnforceSchema bool `json:"enforce_schema,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsTiktokAdsOptions struct { DataLevel string `json:"data_level,omitempty"` Dimensions []string `json:"dimensions,omitempty"` @@ -288,11 +325,21 @@ type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsTiktokAdsOp SyncStartDate string `json:"sync_start_date,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsZendeskSupportOptions struct { + StartDate string `json:"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"` + ConfluenceOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsConfluenceOptions `json:"confluence_options,omitempty"` + GdriveOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGdriveOptions `json:"gdrive_options,omitempty"` + GoogleAdsOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsGoogleAdsOptions `json:"google_ads_options,omitempty"` + JiraOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsJiraOptions `json:"jira_options,omitempty"` + MetaAdsOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsMetaAdsOptions `json:"meta_ads_options,omitempty"` + OutlookOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsOutlookOptions `json:"outlook_options,omitempty"` + SharepointOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSharepointOptions `json:"sharepoint_options,omitempty"` + SmartsheetOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsSmartsheetOptions `json:"smartsheet_options,omitempty"` + TiktokAdsOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsTiktokAdsOptions `json:"tiktok_ads_options,omitempty"` + ZendeskSupportOptions *ResourcePipelineIngestionDefinitionObjectsSchemaConnectorOptionsZendeskSupportOptions `json:"zendesk_support_options,omitempty"` } type ResourcePipelineIngestionDefinitionObjectsSchemaTableConfigurationAutoFullRefreshPolicy struct { @@ -339,6 +386,10 @@ type ResourcePipelineIngestionDefinitionObjectsSchema struct { TableConfiguration *ResourcePipelineIngestionDefinitionObjectsSchemaTableConfiguration `json:"table_configuration,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsConfluenceOptions struct { + IncludeConfluenceSpaces []string `json:"include_confluence_spaces,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptionsFileIngestionOptionsFileFilters struct { ModifiedAfter string `json:"modified_after,omitempty"` ModifiedBefore string `json:"modified_before,omitempty"` @@ -371,6 +422,34 @@ type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGoogleAdsOpt SyncStartDate string `json:"sync_start_date,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsJiraOptions struct { + IncludeJiraSpaces []string `json:"include_jira_spaces,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsMetaAdsOptions struct { + ActionAttributionWindows []string `json:"action_attribution_windows,omitempty"` + ActionBreakdowns []string `json:"action_breakdowns,omitempty"` + ActionReportTime string `json:"action_report_time,omitempty"` + Breakdowns []string `json:"breakdowns,omitempty"` + CustomInsightsLookbackWindow int `json:"custom_insights_lookback_window,omitempty"` + Level string `json:"level,omitempty"` + StartDate string `json:"start_date,omitempty"` + TimeIncrement string `json:"time_increment,omitempty"` +} + +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsOutlookOptions struct { + AttachmentMode string `json:"attachment_mode,omitempty"` + BodyFormat string `json:"body_format,omitempty"` + FolderFilter []string `json:"folder_filter,omitempty"` + IncludeFolders []string `json:"include_folders,omitempty"` + IncludeMailboxes []string `json:"include_mailboxes,omitempty"` + IncludeSenders []string `json:"include_senders,omitempty"` + IncludeSubjects []string `json:"include_subjects,omitempty"` + SenderFilter []string `json:"sender_filter,omitempty"` + StartDate string `json:"start_date,omitempty"` + SubjectFilter []string `json:"subject_filter,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptionsFileFilters struct { ModifiedAfter string `json:"modified_after,omitempty"` ModifiedBefore string `json:"modified_before,omitempty"` @@ -397,6 +476,10 @@ type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOp FileIngestionOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptionsFileIngestionOptions `json:"file_ingestion_options,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSmartsheetOptions struct { + EnforceSchema bool `json:"enforce_schema,omitempty"` +} + type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsTiktokAdsOptions struct { DataLevel string `json:"data_level,omitempty"` Dimensions []string `json:"dimensions,omitempty"` @@ -407,11 +490,21 @@ type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsTiktokAdsOpt SyncStartDate string `json:"sync_start_date,omitempty"` } +type ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsZendeskSupportOptions struct { + StartDate string `json:"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"` + ConfluenceOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsConfluenceOptions `json:"confluence_options,omitempty"` + GdriveOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGdriveOptions `json:"gdrive_options,omitempty"` + GoogleAdsOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsGoogleAdsOptions `json:"google_ads_options,omitempty"` + JiraOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsJiraOptions `json:"jira_options,omitempty"` + MetaAdsOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsMetaAdsOptions `json:"meta_ads_options,omitempty"` + OutlookOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsOutlookOptions `json:"outlook_options,omitempty"` + SharepointOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSharepointOptions `json:"sharepoint_options,omitempty"` + SmartsheetOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsSmartsheetOptions `json:"smartsheet_options,omitempty"` + TiktokAdsOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsTiktokAdsOptions `json:"tiktok_ads_options,omitempty"` + ZendeskSupportOptions *ResourcePipelineIngestionDefinitionObjectsTableConnectorOptionsZendeskSupportOptions `json:"zendesk_support_options,omitempty"` } type ResourcePipelineIngestionDefinitionObjectsTableTableConfigurationAutoFullRefreshPolicy struct { @@ -480,8 +573,13 @@ type ResourcePipelineIngestionDefinitionSourceConfigurationsCatalog struct { Postgres *ResourcePipelineIngestionDefinitionSourceConfigurationsCatalogPostgres `json:"postgres,omitempty"` } +type ResourcePipelineIngestionDefinitionSourceConfigurationsGoogleAdsConfig struct { + ManagerAccountId string `json:"manager_account_id,omitempty"` +} + type ResourcePipelineIngestionDefinitionSourceConfigurations struct { - Catalog *ResourcePipelineIngestionDefinitionSourceConfigurationsCatalog `json:"catalog,omitempty"` + Catalog *ResourcePipelineIngestionDefinitionSourceConfigurationsCatalog `json:"catalog,omitempty"` + GoogleAdsConfig *ResourcePipelineIngestionDefinitionSourceConfigurationsGoogleAdsConfig `json:"google_ads_config,omitempty"` } type ResourcePipelineIngestionDefinitionTableConfigurationAutoFullRefreshPolicy struct { @@ -572,7 +670,7 @@ type ResourcePipelineNotification struct { } type ResourcePipelineProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePipelineRestartWindow struct { diff --git a/bundle/internal/tf/schema/resource_policy_info.go b/bundle/internal/tf/schema/resource_policy_info.go index 27a6600e4a7..2b6cd9653aa 100644 --- a/bundle/internal/tf/schema/resource_policy_info.go +++ b/bundle/internal/tf/schema/resource_policy_info.go @@ -19,7 +19,7 @@ type ResourcePolicyInfoMatchColumns struct { } type ResourcePolicyInfoProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePolicyInfoRowFilterUsing struct { diff --git a/bundle/internal/tf/schema/resource_postgres_branch.go b/bundle/internal/tf/schema/resource_postgres_branch.go index 1ccfaeb1818..e28810fe331 100644 --- a/bundle/internal/tf/schema/resource_postgres_branch.go +++ b/bundle/internal/tf/schema/resource_postgres_branch.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresBranchProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresBranchSpec struct { @@ -17,6 +17,7 @@ type ResourcePostgresBranchSpec struct { } type ResourcePostgresBranchStatus struct { + BranchId string `json:"branch_id,omitempty"` CurrentState string `json:"current_state,omitempty"` Default bool `json:"default,omitempty"` ExpireTime string `json:"expire_time,omitempty"` @@ -30,13 +31,14 @@ type ResourcePostgresBranchStatus struct { } type ResourcePostgresBranch struct { - BranchId string `json:"branch_id"` - CreateTime string `json:"create_time,omitempty"` - Name string `json:"name,omitempty"` - Parent string `json:"parent"` - ProviderConfig *ResourcePostgresBranchProviderConfig `json:"provider_config,omitempty"` - Spec *ResourcePostgresBranchSpec `json:"spec,omitempty"` - Status *ResourcePostgresBranchStatus `json:"status,omitempty"` - Uid string `json:"uid,omitempty"` - UpdateTime string `json:"update_time,omitempty"` + BranchId string `json:"branch_id"` + CreateTime string `json:"create_time,omitempty"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + ProviderConfig *ResourcePostgresBranchProviderConfig `json:"provider_config,omitempty"` + ReplaceExisting bool `json:"replace_existing,omitempty"` + Spec *ResourcePostgresBranchSpec `json:"spec,omitempty"` + Status *ResourcePostgresBranchStatus `json:"status,omitempty"` + Uid string `json:"uid,omitempty"` + UpdateTime string `json:"update_time,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_postgres_catalog.go b/bundle/internal/tf/schema/resource_postgres_catalog.go index 637bdaad2c9..3ba05f22545 100644 --- a/bundle/internal/tf/schema/resource_postgres_catalog.go +++ b/bundle/internal/tf/schema/resource_postgres_catalog.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresCatalogProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresCatalogSpec struct { @@ -14,6 +14,7 @@ type ResourcePostgresCatalogSpec struct { type ResourcePostgresCatalogStatus struct { Branch string `json:"branch,omitempty"` + CatalogId string `json:"catalog_id,omitempty"` PostgresDatabase string `json:"postgres_database,omitempty"` Project string `json:"project,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_postgres_database.go b/bundle/internal/tf/schema/resource_postgres_database.go index a12086665d0..37bfa4d84dc 100644 --- a/bundle/internal/tf/schema/resource_postgres_database.go +++ b/bundle/internal/tf/schema/resource_postgres_database.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresDatabaseProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresDatabaseSpec struct { @@ -12,6 +12,7 @@ type ResourcePostgresDatabaseSpec struct { } type ResourcePostgresDatabaseStatus struct { + DatabaseId string `json:"database_id,omitempty"` PostgresDatabase string `json:"postgres_database,omitempty"` Role string `json:"role,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_postgres_endpoint.go b/bundle/internal/tf/schema/resource_postgres_endpoint.go index 8de10dd9787..af17d5f73bc 100644 --- a/bundle/internal/tf/schema/resource_postgres_endpoint.go +++ b/bundle/internal/tf/schema/resource_postgres_endpoint.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresEndpointProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresEndpointSpecGroup struct { @@ -47,6 +47,7 @@ type ResourcePostgresEndpointStatus struct { AutoscalingLimitMinCu float64 `json:"autoscaling_limit_min_cu,omitempty"` CurrentState string `json:"current_state,omitempty"` Disabled bool `json:"disabled,omitempty"` + EndpointId string `json:"endpoint_id,omitempty"` EndpointType string `json:"endpoint_type,omitempty"` Group *ResourcePostgresEndpointStatusGroup `json:"group,omitempty"` Hosts *ResourcePostgresEndpointStatusHosts `json:"hosts,omitempty"` @@ -56,13 +57,14 @@ type ResourcePostgresEndpointStatus struct { } type ResourcePostgresEndpoint struct { - CreateTime string `json:"create_time,omitempty"` - EndpointId string `json:"endpoint_id"` - Name string `json:"name,omitempty"` - Parent string `json:"parent"` - ProviderConfig *ResourcePostgresEndpointProviderConfig `json:"provider_config,omitempty"` - Spec *ResourcePostgresEndpointSpec `json:"spec,omitempty"` - Status *ResourcePostgresEndpointStatus `json:"status,omitempty"` - Uid string `json:"uid,omitempty"` - UpdateTime string `json:"update_time,omitempty"` + CreateTime string `json:"create_time,omitempty"` + EndpointId string `json:"endpoint_id"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + ProviderConfig *ResourcePostgresEndpointProviderConfig `json:"provider_config,omitempty"` + ReplaceExisting bool `json:"replace_existing,omitempty"` + Spec *ResourcePostgresEndpointSpec `json:"spec,omitempty"` + Status *ResourcePostgresEndpointStatus `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 1df7ebf5115..23df7a5552a 100644 --- a/bundle/internal/tf/schema/resource_postgres_project.go +++ b/bundle/internal/tf/schema/resource_postgres_project.go @@ -13,7 +13,7 @@ type ResourcePostgresProjectInitialEndpointSpec struct { } type ResourcePostgresProjectProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresProjectSpecCustomTags struct { @@ -64,15 +64,19 @@ type ResourcePostgresProjectStatus struct { HistoryRetentionDuration string `json:"history_retention_duration,omitempty"` Owner string `json:"owner,omitempty"` PgVersion int `json:"pg_version,omitempty"` + ProjectId string `json:"project_id,omitempty"` SyntheticStorageSizeBytes int `json:"synthetic_storage_size_bytes,omitempty"` } type ResourcePostgresProject struct { CreateTime string `json:"create_time,omitempty"` + DeleteTime string `json:"delete_time,omitempty"` InitialEndpointSpec *ResourcePostgresProjectInitialEndpointSpec `json:"initial_endpoint_spec,omitempty"` Name string `json:"name,omitempty"` ProjectId string `json:"project_id"` ProviderConfig *ResourcePostgresProjectProviderConfig `json:"provider_config,omitempty"` + PurgeOnDelete bool `json:"purge_on_delete,omitempty"` + PurgeTime string `json:"purge_time,omitempty"` Spec *ResourcePostgresProjectSpec `json:"spec,omitempty"` Status *ResourcePostgresProjectStatus `json:"status,omitempty"` Uid string `json:"uid,omitempty"` diff --git a/bundle/internal/tf/schema/resource_postgres_role.go b/bundle/internal/tf/schema/resource_postgres_role.go index a7dce0a9bdc..5308bbda4f1 100644 --- a/bundle/internal/tf/schema/resource_postgres_role.go +++ b/bundle/internal/tf/schema/resource_postgres_role.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresRoleProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresRoleSpecAttributes struct { @@ -32,6 +32,7 @@ type ResourcePostgresRoleStatus struct { IdentityType string `json:"identity_type,omitempty"` MembershipRoles []string `json:"membership_roles,omitempty"` PostgresRole string `json:"postgres_role,omitempty"` + RoleId string `json:"role_id,omitempty"` } type ResourcePostgresRole struct { diff --git a/bundle/internal/tf/schema/resource_postgres_synced_table.go b/bundle/internal/tf/schema/resource_postgres_synced_table.go index eed810c301a..affde4ce55d 100644 --- a/bundle/internal/tf/schema/resource_postgres_synced_table.go +++ b/bundle/internal/tf/schema/resource_postgres_synced_table.go @@ -3,7 +3,7 @@ package schema type ResourcePostgresSyncedTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourcePostgresSyncedTableSpecNewPipelineSpec struct { @@ -51,6 +51,7 @@ type ResourcePostgresSyncedTableStatus struct { Message string `json:"message,omitempty"` OngoingSyncProgress *ResourcePostgresSyncedTableStatusOngoingSyncProgress `json:"ongoing_sync_progress,omitempty"` PipelineId string `json:"pipeline_id,omitempty"` + Project string `json:"project,omitempty"` ProvisioningPhase string `json:"provisioning_phase,omitempty"` UnityCatalogProvisioningState string `json:"unity_catalog_provisioning_state,omitempty"` } diff --git a/bundle/internal/tf/schema/resource_provider.go b/bundle/internal/tf/schema/resource_provider.go index 16934efa7f4..ff50bef7a4d 100644 --- a/bundle/internal/tf/schema/resource_provider.go +++ b/bundle/internal/tf/schema/resource_provider.go @@ -3,7 +3,7 @@ package schema type ResourceProviderProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceProvider struct { diff --git a/bundle/internal/tf/schema/resource_quality_monitor_v2.go b/bundle/internal/tf/schema/resource_quality_monitor_v2.go index 80e73326f4a..474c289b44d 100644 --- a/bundle/internal/tf/schema/resource_quality_monitor_v2.go +++ b/bundle/internal/tf/schema/resource_quality_monitor_v2.go @@ -9,7 +9,7 @@ type ResourceQualityMonitorV2AnomalyDetectionConfig struct { } type ResourceQualityMonitorV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceQualityMonitorV2ValidityCheckConfigurationsPercentNullValidityCheck struct { diff --git a/bundle/internal/tf/schema/resource_query.go b/bundle/internal/tf/schema/resource_query.go index d5ccd6bb590..78dfe3b9652 100644 --- a/bundle/internal/tf/schema/resource_query.go +++ b/bundle/internal/tf/schema/resource_query.go @@ -64,7 +64,7 @@ type ResourceQueryParameter struct { } type ResourceQueryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceQuery struct { diff --git a/bundle/internal/tf/schema/resource_recipient.go b/bundle/internal/tf/schema/resource_recipient.go index 58dc1512653..2216974af1f 100644 --- a/bundle/internal/tf/schema/resource_recipient.go +++ b/bundle/internal/tf/schema/resource_recipient.go @@ -11,7 +11,7 @@ type ResourceRecipientPropertiesKvpairs struct { } type ResourceRecipientProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceRecipientTokens struct { diff --git a/bundle/internal/tf/schema/resource_registered_model.go b/bundle/internal/tf/schema/resource_registered_model.go index 542a1b0c585..3d111f46adc 100644 --- a/bundle/internal/tf/schema/resource_registered_model.go +++ b/bundle/internal/tf/schema/resource_registered_model.go @@ -12,7 +12,7 @@ type ResourceRegisteredModelAliases struct { } type ResourceRegisteredModelProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceRegisteredModel struct { diff --git a/bundle/internal/tf/schema/resource_repo.go b/bundle/internal/tf/schema/resource_repo.go index e1a5ff8fef3..ec9b65809f2 100644 --- a/bundle/internal/tf/schema/resource_repo.go +++ b/bundle/internal/tf/schema/resource_repo.go @@ -3,7 +3,7 @@ package schema type ResourceRepoProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceRepoSparseCheckout struct { 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 b539201216e..0263ce87375 100644 --- a/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go +++ b/bundle/internal/tf/schema/resource_restrict_workspace_admins_setting.go @@ -3,7 +3,7 @@ package schema type ResourceRestrictWorkspaceAdminsSettingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceRestrictWorkspaceAdminsSettingRestrictWorkspaceAdmins struct { diff --git a/bundle/internal/tf/schema/resource_rfa_access_request_destinations.go b/bundle/internal/tf/schema/resource_rfa_access_request_destinations.go index bafb8d49faf..c887f9206f4 100644 --- a/bundle/internal/tf/schema/resource_rfa_access_request_destinations.go +++ b/bundle/internal/tf/schema/resource_rfa_access_request_destinations.go @@ -15,7 +15,7 @@ type ResourceRfaAccessRequestDestinationsDestinations struct { } type ResourceRfaAccessRequestDestinationsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceRfaAccessRequestDestinationsSecurable struct { diff --git a/bundle/internal/tf/schema/resource_schema.go b/bundle/internal/tf/schema/resource_schema.go index efb4c9bf44d..9d3f4c73446 100644 --- a/bundle/internal/tf/schema/resource_schema.go +++ b/bundle/internal/tf/schema/resource_schema.go @@ -3,7 +3,7 @@ package schema type ResourceSchemaProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSchema struct { diff --git a/bundle/internal/tf/schema/resource_secret.go b/bundle/internal/tf/schema/resource_secret.go index 02dddfa232a..30a5278fef9 100644 --- a/bundle/internal/tf/schema/resource_secret.go +++ b/bundle/internal/tf/schema/resource_secret.go @@ -3,7 +3,7 @@ package schema type ResourceSecretProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSecret struct { diff --git a/bundle/internal/tf/schema/resource_secret_acl.go b/bundle/internal/tf/schema/resource_secret_acl.go index 873e82f5d11..fb30cb013b6 100644 --- a/bundle/internal/tf/schema/resource_secret_acl.go +++ b/bundle/internal/tf/schema/resource_secret_acl.go @@ -3,7 +3,7 @@ package schema type ResourceSecretAclProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSecretAcl struct { diff --git a/bundle/internal/tf/schema/resource_secret_scope.go b/bundle/internal/tf/schema/resource_secret_scope.go index ca539d8d027..b6a96552f68 100644 --- a/bundle/internal/tf/schema/resource_secret_scope.go +++ b/bundle/internal/tf/schema/resource_secret_scope.go @@ -8,7 +8,7 @@ type ResourceSecretScopeKeyvaultMetadata struct { } type ResourceSecretScopeProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSecretScope struct { diff --git a/bundle/internal/tf/schema/resource_secret_uc.go b/bundle/internal/tf/schema/resource_secret_uc.go new file mode 100644 index 00000000000..84e199be6f2 --- /dev/null +++ b/bundle/internal/tf/schema/resource_secret_uc.go @@ -0,0 +1,28 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceSecretUcProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type ResourceSecretUc struct { + BrowseOnly bool `json:"browse_only,omitempty"` + CatalogName string `json:"catalog_name"` + Comment string `json:"comment,omitempty"` + CreateTime string `json:"create_time,omitempty"` + CreatedBy string `json:"created_by,omitempty"` + EffectiveOwner string `json:"effective_owner,omitempty"` + EffectiveValue string `json:"effective_value,omitempty"` + ExpireTime string `json:"expire_time,omitempty"` + ExternalSecretId string `json:"external_secret_id,omitempty"` + FullName string `json:"full_name,omitempty"` + MetastoreId string `json:"metastore_id,omitempty"` + Name string `json:"name"` + Owner string `json:"owner,omitempty"` + ProviderConfig *ResourceSecretUcProviderConfig `json:"provider_config,omitempty"` + SchemaName string `json:"schema_name"` + UpdateTime string `json:"update_time,omitempty"` + UpdatedBy string `json:"updated_by,omitempty"` + Value string `json:"value"` +} diff --git a/bundle/internal/tf/schema/resource_service_principal.go b/bundle/internal/tf/schema/resource_service_principal.go index 231eccf3b15..7686bf43e67 100644 --- a/bundle/internal/tf/schema/resource_service_principal.go +++ b/bundle/internal/tf/schema/resource_service_principal.go @@ -3,7 +3,7 @@ package schema type ResourceServicePrincipalProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceServicePrincipal struct { diff --git a/bundle/internal/tf/schema/resource_service_principal_role.go b/bundle/internal/tf/schema/resource_service_principal_role.go index 3da75ea081e..c010612a564 100644 --- a/bundle/internal/tf/schema/resource_service_principal_role.go +++ b/bundle/internal/tf/schema/resource_service_principal_role.go @@ -3,7 +3,7 @@ package schema type ResourceServicePrincipalRoleProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceServicePrincipalRole struct { diff --git a/bundle/internal/tf/schema/resource_service_principal_secret.go b/bundle/internal/tf/schema/resource_service_principal_secret.go index 7e6559cf908..c28b82caaed 100644 --- a/bundle/internal/tf/schema/resource_service_principal_secret.go +++ b/bundle/internal/tf/schema/resource_service_principal_secret.go @@ -3,7 +3,7 @@ package schema type ResourceServicePrincipalSecretProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceServicePrincipalSecret struct { diff --git a/bundle/internal/tf/schema/resource_sql_alert.go b/bundle/internal/tf/schema/resource_sql_alert.go index 3871dc49377..e8fd5aed247 100644 --- a/bundle/internal/tf/schema/resource_sql_alert.go +++ b/bundle/internal/tf/schema/resource_sql_alert.go @@ -13,7 +13,7 @@ type ResourceSqlAlertOptions struct { } type ResourceSqlAlertProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlAlert struct { diff --git a/bundle/internal/tf/schema/resource_sql_dashboard.go b/bundle/internal/tf/schema/resource_sql_dashboard.go index fc37c98d1d4..20af546d633 100644 --- a/bundle/internal/tf/schema/resource_sql_dashboard.go +++ b/bundle/internal/tf/schema/resource_sql_dashboard.go @@ -3,7 +3,7 @@ package schema type ResourceSqlDashboardProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlDashboard struct { diff --git a/bundle/internal/tf/schema/resource_sql_endpoint.go b/bundle/internal/tf/schema/resource_sql_endpoint.go index b8fe078261f..e2f91e7d328 100644 --- a/bundle/internal/tf/schema/resource_sql_endpoint.go +++ b/bundle/internal/tf/schema/resource_sql_endpoint.go @@ -8,7 +8,7 @@ type ResourceSqlEndpointChannel struct { } type ResourceSqlEndpointProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlEndpointTagsCustomTags struct { diff --git a/bundle/internal/tf/schema/resource_sql_global_config.go b/bundle/internal/tf/schema/resource_sql_global_config.go index b17c91275a1..b50bd578eed 100644 --- a/bundle/internal/tf/schema/resource_sql_global_config.go +++ b/bundle/internal/tf/schema/resource_sql_global_config.go @@ -3,7 +3,7 @@ package schema type ResourceSqlGlobalConfigProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlGlobalConfig struct { diff --git a/bundle/internal/tf/schema/resource_sql_permissions.go b/bundle/internal/tf/schema/resource_sql_permissions.go index 8a931793806..d81b7b5d16c 100644 --- a/bundle/internal/tf/schema/resource_sql_permissions.go +++ b/bundle/internal/tf/schema/resource_sql_permissions.go @@ -8,7 +8,7 @@ type ResourceSqlPermissionsPrivilegeAssignments struct { } type ResourceSqlPermissionsProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlPermissions struct { diff --git a/bundle/internal/tf/schema/resource_sql_query.go b/bundle/internal/tf/schema/resource_sql_query.go index 445ec90aac5..4086e344a67 100644 --- a/bundle/internal/tf/schema/resource_sql_query.go +++ b/bundle/internal/tf/schema/resource_sql_query.go @@ -94,7 +94,7 @@ type ResourceSqlQueryParameter struct { } type ResourceSqlQueryProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlQueryScheduleContinuous struct { diff --git a/bundle/internal/tf/schema/resource_sql_table.go b/bundle/internal/tf/schema/resource_sql_table.go index 1971e86f803..98442f3a705 100644 --- a/bundle/internal/tf/schema/resource_sql_table.go +++ b/bundle/internal/tf/schema/resource_sql_table.go @@ -12,7 +12,7 @@ type ResourceSqlTableColumn struct { } type ResourceSqlTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlTable struct { diff --git a/bundle/internal/tf/schema/resource_sql_visualization.go b/bundle/internal/tf/schema/resource_sql_visualization.go index 11f5e9060ce..ea17434be33 100644 --- a/bundle/internal/tf/schema/resource_sql_visualization.go +++ b/bundle/internal/tf/schema/resource_sql_visualization.go @@ -3,7 +3,7 @@ package schema type ResourceSqlVisualizationProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlVisualization struct { diff --git a/bundle/internal/tf/schema/resource_sql_widget.go b/bundle/internal/tf/schema/resource_sql_widget.go index bb024d90245..c5c21aea24b 100644 --- a/bundle/internal/tf/schema/resource_sql_widget.go +++ b/bundle/internal/tf/schema/resource_sql_widget.go @@ -20,7 +20,7 @@ type ResourceSqlWidgetPosition struct { } type ResourceSqlWidgetProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSqlWidget struct { diff --git a/bundle/internal/tf/schema/resource_storage_credential.go b/bundle/internal/tf/schema/resource_storage_credential.go index 5f0485eff04..e49c4d00970 100644 --- a/bundle/internal/tf/schema/resource_storage_credential.go +++ b/bundle/internal/tf/schema/resource_storage_credential.go @@ -38,7 +38,7 @@ type ResourceStorageCredentialGcpServiceAccountKey struct { } type ResourceStorageCredentialProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceStorageCredential struct { diff --git a/bundle/internal/tf/schema/resource_supervisor_agent.go b/bundle/internal/tf/schema/resource_supervisor_agent.go new file mode 100644 index 00000000000..4b80037eab9 --- /dev/null +++ b/bundle/internal/tf/schema/resource_supervisor_agent.go @@ -0,0 +1,21 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceSupervisorAgentProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type ResourceSupervisorAgent struct { + CreateTime string `json:"create_time,omitempty"` + Creator string `json:"creator,omitempty"` + Description string `json:"description,omitempty"` + DisplayName string `json:"display_name"` + EndpointName string `json:"endpoint_name,omitempty"` + ExperimentId string `json:"experiment_id,omitempty"` + Id string `json:"id,omitempty"` + Instructions string `json:"instructions,omitempty"` + Name string `json:"name,omitempty"` + ProviderConfig *ResourceSupervisorAgentProviderConfig `json:"provider_config,omitempty"` + SupervisorAgentId string `json:"supervisor_agent_id,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_supervisor_agent_tool.go b/bundle/internal/tf/schema/resource_supervisor_agent_tool.go new file mode 100644 index 00000000000..bb398edc2e8 --- /dev/null +++ b/bundle/internal/tf/schema/resource_supervisor_agent_tool.go @@ -0,0 +1,48 @@ +// Generated from Databricks Terraform provider schema. DO NOT EDIT. + +package schema + +type ResourceSupervisorAgentToolApp struct { + Name string `json:"name"` +} + +type ResourceSupervisorAgentToolGenieSpace struct { + Id string `json:"id"` +} + +type ResourceSupervisorAgentToolKnowledgeAssistant struct { + KnowledgeAssistantId string `json:"knowledge_assistant_id"` + ServingEndpointName string `json:"serving_endpoint_name,omitempty"` +} + +type ResourceSupervisorAgentToolProviderConfig struct { + WorkspaceId string `json:"workspace_id,omitempty"` +} + +type ResourceSupervisorAgentToolUcConnection struct { + Name string `json:"name"` +} + +type ResourceSupervisorAgentToolUcFunction struct { + Name string `json:"name"` +} + +type ResourceSupervisorAgentToolVolume struct { + Name string `json:"name"` +} + +type ResourceSupervisorAgentTool struct { + App *ResourceSupervisorAgentToolApp `json:"app,omitempty"` + Description string `json:"description,omitempty"` + GenieSpace *ResourceSupervisorAgentToolGenieSpace `json:"genie_space,omitempty"` + Id string `json:"id,omitempty"` + KnowledgeAssistant *ResourceSupervisorAgentToolKnowledgeAssistant `json:"knowledge_assistant,omitempty"` + Name string `json:"name,omitempty"` + Parent string `json:"parent"` + ProviderConfig *ResourceSupervisorAgentToolProviderConfig `json:"provider_config,omitempty"` + ToolId string `json:"tool_id"` + ToolType string `json:"tool_type"` + UcConnection *ResourceSupervisorAgentToolUcConnection `json:"uc_connection,omitempty"` + UcFunction *ResourceSupervisorAgentToolUcFunction `json:"uc_function,omitempty"` + Volume *ResourceSupervisorAgentToolVolume `json:"volume,omitempty"` +} diff --git a/bundle/internal/tf/schema/resource_system_schema.go b/bundle/internal/tf/schema/resource_system_schema.go index cf5efdde522..f7e6e7559e2 100644 --- a/bundle/internal/tf/schema/resource_system_schema.go +++ b/bundle/internal/tf/schema/resource_system_schema.go @@ -3,7 +3,7 @@ package schema type ResourceSystemSchemaProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceSystemSchema struct { diff --git a/bundle/internal/tf/schema/resource_table.go b/bundle/internal/tf/schema/resource_table.go index 9d3fba13418..2960a11da5f 100644 --- a/bundle/internal/tf/schema/resource_table.go +++ b/bundle/internal/tf/schema/resource_table.go @@ -17,7 +17,7 @@ type ResourceTableColumn struct { } type ResourceTableProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceTable struct { diff --git a/bundle/internal/tf/schema/resource_tag_policy.go b/bundle/internal/tf/schema/resource_tag_policy.go index 490b7020b55..549777c6b24 100644 --- a/bundle/internal/tf/schema/resource_tag_policy.go +++ b/bundle/internal/tf/schema/resource_tag_policy.go @@ -3,7 +3,7 @@ package schema type ResourceTagPolicyProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceTagPolicyValues struct { diff --git a/bundle/internal/tf/schema/resource_token.go b/bundle/internal/tf/schema/resource_token.go index 648cd6d3c82..5473c7c48d9 100644 --- a/bundle/internal/tf/schema/resource_token.go +++ b/bundle/internal/tf/schema/resource_token.go @@ -3,7 +3,7 @@ package schema type ResourceTokenProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceToken struct { diff --git a/bundle/internal/tf/schema/resource_user.go b/bundle/internal/tf/schema/resource_user.go index 0ad0aa5ca51..9d3c4f0dae3 100644 --- a/bundle/internal/tf/schema/resource_user.go +++ b/bundle/internal/tf/schema/resource_user.go @@ -3,7 +3,7 @@ package schema type ResourceUserProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceUser struct { diff --git a/bundle/internal/tf/schema/resource_user_instance_profile.go b/bundle/internal/tf/schema/resource_user_instance_profile.go index 59b27276fe5..b6c20658c6b 100644 --- a/bundle/internal/tf/schema/resource_user_instance_profile.go +++ b/bundle/internal/tf/schema/resource_user_instance_profile.go @@ -3,7 +3,7 @@ package schema type ResourceUserInstanceProfileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceUserInstanceProfile struct { diff --git a/bundle/internal/tf/schema/resource_user_role.go b/bundle/internal/tf/schema/resource_user_role.go index 75b88ba1358..ac2276013c9 100644 --- a/bundle/internal/tf/schema/resource_user_role.go +++ b/bundle/internal/tf/schema/resource_user_role.go @@ -3,7 +3,7 @@ package schema type ResourceUserRoleProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceUserRole struct { diff --git a/bundle/internal/tf/schema/resource_vector_search_endpoint.go b/bundle/internal/tf/schema/resource_vector_search_endpoint.go index 9d30aa82562..02bdeceae4f 100644 --- a/bundle/internal/tf/schema/resource_vector_search_endpoint.go +++ b/bundle/internal/tf/schema/resource_vector_search_endpoint.go @@ -3,12 +3,12 @@ package schema type ResourceVectorSearchEndpointProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceVectorSearchEndpointScalingInfo struct { - RequestedMinQps int `json:"requested_min_qps,omitempty"` - State string `json:"state,omitempty"` + RequestedTargetQps int `json:"requested_target_qps,omitempty"` + State string `json:"state,omitempty"` } type ResourceVectorSearchEndpoint struct { diff --git a/bundle/internal/tf/schema/resource_vector_search_index.go b/bundle/internal/tf/schema/resource_vector_search_index.go index bd45fe44c07..2872f059663 100644 --- a/bundle/internal/tf/schema/resource_vector_search_index.go +++ b/bundle/internal/tf/schema/resource_vector_search_index.go @@ -14,6 +14,8 @@ type ResourceVectorSearchIndexDeltaSyncIndexSpecEmbeddingVectorColumns struct { } type ResourceVectorSearchIndexDeltaSyncIndexSpec struct { + ColumnsToIndex []string `json:"columns_to_index,omitempty"` + ColumnsToSync []string `json:"columns_to_sync,omitempty"` EmbeddingWritebackTable string `json:"embedding_writeback_table,omitempty"` PipelineId string `json:"pipeline_id,omitempty"` PipelineType string `json:"pipeline_type,omitempty"` @@ -40,7 +42,7 @@ type ResourceVectorSearchIndexDirectAccessIndexSpec struct { } type ResourceVectorSearchIndexProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceVectorSearchIndex struct { diff --git a/bundle/internal/tf/schema/resource_volume.go b/bundle/internal/tf/schema/resource_volume.go index e540ef3b2cf..f8f3cd739c2 100644 --- a/bundle/internal/tf/schema/resource_volume.go +++ b/bundle/internal/tf/schema/resource_volume.go @@ -3,7 +3,7 @@ package schema type ResourceVolumeProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceVolume struct { diff --git a/bundle/internal/tf/schema/resource_warehouses_default_warehouse_override.go b/bundle/internal/tf/schema/resource_warehouses_default_warehouse_override.go index 09433190733..a5fd1c4a5cf 100644 --- a/bundle/internal/tf/schema/resource_warehouses_default_warehouse_override.go +++ b/bundle/internal/tf/schema/resource_warehouses_default_warehouse_override.go @@ -3,7 +3,7 @@ package schema type ResourceWarehousesDefaultWarehouseOverrideProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWarehousesDefaultWarehouseOverride struct { diff --git a/bundle/internal/tf/schema/resource_workspace_binding.go b/bundle/internal/tf/schema/resource_workspace_binding.go index 1d0ebe980f4..39fea132edf 100644 --- a/bundle/internal/tf/schema/resource_workspace_binding.go +++ b/bundle/internal/tf/schema/resource_workspace_binding.go @@ -3,7 +3,7 @@ package schema type ResourceWorkspaceBindingProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWorkspaceBinding struct { diff --git a/bundle/internal/tf/schema/resource_workspace_conf.go b/bundle/internal/tf/schema/resource_workspace_conf.go index 9c3a94c850b..f0cd41c1127 100644 --- a/bundle/internal/tf/schema/resource_workspace_conf.go +++ b/bundle/internal/tf/schema/resource_workspace_conf.go @@ -3,7 +3,7 @@ package schema type ResourceWorkspaceConfProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWorkspaceConf struct { diff --git a/bundle/internal/tf/schema/resource_workspace_entity_tag_assignment.go b/bundle/internal/tf/schema/resource_workspace_entity_tag_assignment.go index a22d7d0d4fe..21babfcd168 100644 --- a/bundle/internal/tf/schema/resource_workspace_entity_tag_assignment.go +++ b/bundle/internal/tf/schema/resource_workspace_entity_tag_assignment.go @@ -3,7 +3,7 @@ package schema type ResourceWorkspaceEntityTagAssignmentProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWorkspaceEntityTagAssignment struct { diff --git a/bundle/internal/tf/schema/resource_workspace_file.go b/bundle/internal/tf/schema/resource_workspace_file.go index dfb1085271d..f233f6e5911 100644 --- a/bundle/internal/tf/schema/resource_workspace_file.go +++ b/bundle/internal/tf/schema/resource_workspace_file.go @@ -3,7 +3,7 @@ package schema type ResourceWorkspaceFileProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWorkspaceFile struct { diff --git a/bundle/internal/tf/schema/resource_workspace_setting_v2.go b/bundle/internal/tf/schema/resource_workspace_setting_v2.go index 6384c00ae46..076e9d9b803 100644 --- a/bundle/internal/tf/schema/resource_workspace_setting_v2.go +++ b/bundle/internal/tf/schema/resource_workspace_setting_v2.go @@ -110,7 +110,7 @@ type ResourceWorkspaceSettingV2PersonalCompute struct { } type ResourceWorkspaceSettingV2ProviderConfig struct { - WorkspaceId string `json:"workspace_id"` + WorkspaceId string `json:"workspace_id,omitempty"` } type ResourceWorkspaceSettingV2RestrictWorkspaceAdmins struct { diff --git a/bundle/internal/tf/schema/resources.go b/bundle/internal/tf/schema/resources.go index a12b555092f..b0827c5b727 100644 --- a/bundle/internal/tf/schema/resources.go +++ b/bundle/internal/tf/schema/resources.go @@ -44,6 +44,8 @@ type Resources struct { DisableLegacyAccessSetting map[string]any `json:"databricks_disable_legacy_access_setting,omitempty"` DisableLegacyDbfsSetting map[string]any `json:"databricks_disable_legacy_dbfs_setting,omitempty"` DisableLegacyFeaturesSetting map[string]any `json:"databricks_disable_legacy_features_setting,omitempty"` + DisasterRecoveryFailoverGroup map[string]any `json:"databricks_disaster_recovery_failover_group,omitempty"` + DisasterRecoveryStableUrl map[string]any `json:"databricks_disaster_recovery_stable_url,omitempty"` Endpoint map[string]any `json:"databricks_endpoint,omitempty"` EnhancedSecurityMonitoringWorkspaceSetting map[string]any `json:"databricks_enhanced_security_monitoring_workspace_setting,omitempty"` Entitlements map[string]any `json:"databricks_entitlements,omitempty"` @@ -123,6 +125,7 @@ type Resources struct { Secret map[string]any `json:"databricks_secret,omitempty"` SecretAcl map[string]any `json:"databricks_secret_acl,omitempty"` SecretScope map[string]any `json:"databricks_secret_scope,omitempty"` + SecretUc map[string]any `json:"databricks_secret_uc,omitempty"` ServicePrincipal map[string]any `json:"databricks_service_principal,omitempty"` ServicePrincipalFederationPolicy map[string]any `json:"databricks_service_principal_federation_policy,omitempty"` ServicePrincipalRole map[string]any `json:"databricks_service_principal_role,omitempty"` @@ -138,6 +141,8 @@ type Resources struct { SqlVisualization map[string]any `json:"databricks_sql_visualization,omitempty"` SqlWidget map[string]any `json:"databricks_sql_widget,omitempty"` StorageCredential map[string]any `json:"databricks_storage_credential,omitempty"` + SupervisorAgent map[string]any `json:"databricks_supervisor_agent,omitempty"` + SupervisorAgentTool map[string]any `json:"databricks_supervisor_agent_tool,omitempty"` SystemSchema map[string]any `json:"databricks_system_schema,omitempty"` Table map[string]any `json:"databricks_table,omitempty"` TagPolicy map[string]any `json:"databricks_tag_policy,omitempty"` @@ -184,26 +189,28 @@ func NewResources() *Resources { Cluster: make(map[string]any), ClusterPolicy: make(map[string]any), ComplianceSecurityProfileWorkspaceSetting: make(map[string]any), - Connection: make(map[string]any), - Credential: make(map[string]any), - CustomAppIntegration: make(map[string]any), - Dashboard: make(map[string]any), - DataClassificationCatalogConfig: make(map[string]any), - DataQualityMonitor: make(map[string]any), - DataQualityRefresh: make(map[string]any), - DatabaseDatabaseCatalog: make(map[string]any), - DatabaseInstance: make(map[string]any), - DatabaseSyncedDatabaseTable: make(map[string]any), - DbfsFile: make(map[string]any), - DefaultNamespaceSetting: make(map[string]any), - Directory: make(map[string]any), - DisableLegacyAccessSetting: make(map[string]any), - DisableLegacyDbfsSetting: make(map[string]any), - 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), + Connection: make(map[string]any), + Credential: make(map[string]any), + CustomAppIntegration: make(map[string]any), + Dashboard: make(map[string]any), + DataClassificationCatalogConfig: make(map[string]any), + DataQualityMonitor: make(map[string]any), + DataQualityRefresh: make(map[string]any), + DatabaseDatabaseCatalog: make(map[string]any), + DatabaseInstance: make(map[string]any), + DatabaseSyncedDatabaseTable: make(map[string]any), + DbfsFile: make(map[string]any), + DefaultNamespaceSetting: make(map[string]any), + Directory: make(map[string]any), + DisableLegacyAccessSetting: make(map[string]any), + DisableLegacyDbfsSetting: make(map[string]any), + DisableLegacyFeaturesSetting: make(map[string]any), + DisasterRecoveryFailoverGroup: make(map[string]any), + DisasterRecoveryStableUrl: make(map[string]any), + Endpoint: make(map[string]any), + EnhancedSecurityMonitoringWorkspaceSetting: 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), @@ -279,6 +286,7 @@ func NewResources() *Resources { Secret: make(map[string]any), SecretAcl: make(map[string]any), SecretScope: make(map[string]any), + SecretUc: make(map[string]any), ServicePrincipal: make(map[string]any), ServicePrincipalFederationPolicy: make(map[string]any), ServicePrincipalRole: make(map[string]any), @@ -294,6 +302,8 @@ func NewResources() *Resources { SqlVisualization: make(map[string]any), SqlWidget: make(map[string]any), StorageCredential: make(map[string]any), + SupervisorAgent: make(map[string]any), + SupervisorAgentTool: make(map[string]any), SystemSchema: make(map[string]any), Table: make(map[string]any), TagPolicy: make(map[string]any), diff --git a/bundle/internal/tf/schema/root.go b/bundle/internal/tf/schema/root.go index c0d0ad067cc..111a2bb59e0 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.113.0" -const ProviderChecksumLinuxAmd64 = "4f5caaf7bea4c435ae97c28c45086c213e182b67d1fe9b13f4e91b9e0b6ad7be" -const ProviderChecksumLinuxArm64 = "69693b0bcbab3a184deb2744e8b90d5a9d1f7e19cdc414bc54a87280e37d65a9" +const ProviderVersion = "1.115.0" +const ProviderChecksumLinuxAmd64 = "eb2d130871f6fb8cfd1b86be2f66cdf724ec08625e60d9d9947c36979b412547" +const ProviderChecksumLinuxArm64 = "6401e75be47b98f1a807bbd17d5904f58d90c2b2ac0da483847efdecfa962c0f" func NewRoot() *Root { return &Root{ diff --git a/bundle/internal/validation/generated/enum_fields.go b/bundle/internal/validation/generated/enum_fields.go index 33632c268f8..850eb8eedfe 100644 --- a/bundle/internal/validation/generated/enum_fields.go +++ b/bundle/internal/validation/generated/enum_fields.go @@ -41,14 +41,15 @@ var EnumFields = map[string][]string{ "resources.catalogs.*.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.clusters.*.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, - "resources.clusters.*.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, - "resources.clusters.*.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, - "resources.clusters.*.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, - "resources.clusters.*.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, - "resources.clusters.*.kind": {"CLASSIC_PREVIEW"}, - "resources.clusters.*.permissions[*].level": {"CAN_ATTACH_TO", "CAN_MANAGE", "CAN_RESTART"}, - "resources.clusters.*.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, + "resources.clusters.*.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, + "resources.clusters.*.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, + "resources.clusters.*.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, + "resources.clusters.*.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, + "resources.clusters.*.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, + "resources.clusters.*.gcp_attributes.confidential_compute_type": {"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"}, + "resources.clusters.*.kind": {"CLASSIC_PREVIEW"}, + "resources.clusters.*.permissions[*].level": {"CAN_ATTACH_TO", "CAN_MANAGE", "CAN_RESTART"}, + "resources.clusters.*.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, "resources.dashboards.*.lifecycle_state": {"ACTIVE", "TRASHED"}, "resources.dashboards.*.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"}, @@ -61,69 +62,72 @@ var EnumFields = map[string][]string{ "resources.external_locations.*.encryption_details.sse_encryption_details.algorithm": {"AWS_SSE_KMS", "AWS_SSE_S3"}, "resources.external_locations.*.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.jobs.*.continuous.pause_status": {"PAUSED", "UNPAUSED"}, - "resources.jobs.*.continuous.task_retry_mode": {"NEVER", "ON_FAILURE"}, - "resources.jobs.*.deployment.kind": {"BUNDLE", "SYSTEM_MANAGED"}, - "resources.jobs.*.edit_mode": {"EDITABLE", "UI_LOCKED"}, - "resources.jobs.*.format": {"MULTI_TASK", "SINGLE_TASK"}, - "resources.jobs.*.git_source.git_provider": {"awsCodeCommit", "azureDevOpsServices", "bitbucketCloud", "bitbucketServer", "gitHub", "gitHubEnterprise", "gitLab", "gitLabEnterpriseEdition"}, - "resources.jobs.*.git_source.job_source.dirty_state": {"DISCONNECTED", "NOT_SYNCED"}, - "resources.jobs.*.health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, - "resources.jobs.*.health.rules[*].op": {"GREATER_THAN"}, - "resources.jobs.*.job_clusters[*].new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, - "resources.jobs.*.job_clusters[*].new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, - "resources.jobs.*.job_clusters[*].new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, - "resources.jobs.*.job_clusters[*].new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, - "resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, - "resources.jobs.*.job_clusters[*].new_cluster.kind": {"CLASSIC_PREVIEW"}, - "resources.jobs.*.job_clusters[*].new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, - "resources.jobs.*.performance_target": {"PERFORMANCE_OPTIMIZED", "STANDARD"}, - "resources.jobs.*.permissions[*].level": {"CAN_MANAGE", "CAN_MANAGE_RUN", "CAN_VIEW", "IS_OWNER"}, - "resources.jobs.*.schedule.pause_status": {"PAUSED", "UNPAUSED"}, - "resources.jobs.*.tasks[*].compute.hardware_accelerator": {"GPU_1xA10", "GPU_8xH100"}, - "resources.jobs.*.tasks[*].condition_task.op": {"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"}, - "resources.jobs.*.tasks[*].dbt_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].for_each_task.task.compute.hardware_accelerator": {"GPU_1xA10", "GPU_8xH100"}, - "resources.jobs.*.tasks[*].for_each_task.task.condition_task.op": {"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"}, - "resources.jobs.*.tasks[*].for_each_task.task.dbt_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].for_each_task.task.gen_ai_compute_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].for_each_task.task.health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, - "resources.jobs.*.tasks[*].for_each_task.task.health.rules[*].op": {"GREATER_THAN"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.kind": {"CLASSIC_PREVIEW"}, - "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, - "resources.jobs.*.tasks[*].for_each_task.task.notebook_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method": {"OAUTH", "PAT"}, - "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, - "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, - "resources.jobs.*.tasks[*].for_each_task.task.run_if": {"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"}, - "resources.jobs.*.tasks[*].for_each_task.task.spark_python_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].for_each_task.task.sql_task.file.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].gen_ai_compute_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, - "resources.jobs.*.tasks[*].health.rules[*].op": {"GREATER_THAN"}, - "resources.jobs.*.tasks[*].new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, - "resources.jobs.*.tasks[*].new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, - "resources.jobs.*.tasks[*].new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, - "resources.jobs.*.tasks[*].new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, - "resources.jobs.*.tasks[*].new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, - "resources.jobs.*.tasks[*].new_cluster.kind": {"CLASSIC_PREVIEW"}, - "resources.jobs.*.tasks[*].new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, - "resources.jobs.*.tasks[*].notebook_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].power_bi_task.power_bi_model.authentication_method": {"OAUTH", "PAT"}, - "resources.jobs.*.tasks[*].power_bi_task.power_bi_model.storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, - "resources.jobs.*.tasks[*].power_bi_task.tables[*].storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, - "resources.jobs.*.tasks[*].run_if": {"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"}, - "resources.jobs.*.tasks[*].spark_python_task.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.tasks[*].sql_task.file.source": {"GIT", "WORKSPACE"}, - "resources.jobs.*.trigger.model.condition": {"MODEL_ALIAS_SET", "MODEL_CREATED", "MODEL_VERSION_READY"}, - "resources.jobs.*.trigger.pause_status": {"PAUSED", "UNPAUSED"}, - "resources.jobs.*.trigger.periodic.unit": {"DAYS", "HOURS", "WEEKS"}, - "resources.jobs.*.trigger.table_update.condition": {"ALL_UPDATED", "ANY_UPDATED"}, + "resources.jobs.*.continuous.pause_status": {"PAUSED", "UNPAUSED"}, + "resources.jobs.*.continuous.task_retry_mode": {"NEVER", "ON_FAILURE"}, + "resources.jobs.*.deployment.kind": {"BUNDLE", "SYSTEM_MANAGED"}, + "resources.jobs.*.edit_mode": {"EDITABLE", "UI_LOCKED"}, + "resources.jobs.*.format": {"MULTI_TASK", "SINGLE_TASK"}, + "resources.jobs.*.git_source.git_provider": {"awsCodeCommit", "azureDevOpsServices", "bitbucketCloud", "bitbucketServer", "gitHub", "gitHubEnterprise", "gitLab", "gitLabEnterpriseEdition"}, + "resources.jobs.*.git_source.job_source.dirty_state": {"DISCONNECTED", "NOT_SYNCED"}, + "resources.jobs.*.health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, + "resources.jobs.*.health.rules[*].op": {"GREATER_THAN"}, + "resources.jobs.*.job_clusters[*].new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, + "resources.jobs.*.job_clusters[*].new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, + "resources.jobs.*.job_clusters[*].new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, + "resources.jobs.*.job_clusters[*].new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, + "resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, + "resources.jobs.*.job_clusters[*].new_cluster.gcp_attributes.confidential_compute_type": {"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"}, + "resources.jobs.*.job_clusters[*].new_cluster.kind": {"CLASSIC_PREVIEW"}, + "resources.jobs.*.job_clusters[*].new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, + "resources.jobs.*.performance_target": {"PERFORMANCE_OPTIMIZED", "STANDARD"}, + "resources.jobs.*.permissions[*].level": {"CAN_MANAGE", "CAN_MANAGE_RUN", "CAN_VIEW", "IS_OWNER"}, + "resources.jobs.*.schedule.pause_status": {"PAUSED", "UNPAUSED"}, + "resources.jobs.*.tasks[*].compute.hardware_accelerator": {"GPU_1xA10", "GPU_8xH100"}, + "resources.jobs.*.tasks[*].condition_task.op": {"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"}, + "resources.jobs.*.tasks[*].dbt_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].for_each_task.task.compute.hardware_accelerator": {"GPU_1xA10", "GPU_8xH100"}, + "resources.jobs.*.tasks[*].for_each_task.task.condition_task.op": {"EQUAL_TO", "GREATER_THAN", "GREATER_THAN_OR_EQUAL", "LESS_THAN", "LESS_THAN_OR_EQUAL", "NOT_EQUAL"}, + "resources.jobs.*.tasks[*].for_each_task.task.dbt_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].for_each_task.task.gen_ai_compute_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].for_each_task.task.health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, + "resources.jobs.*.tasks[*].for_each_task.task.health.rules[*].op": {"GREATER_THAN"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.gcp_attributes.confidential_compute_type": {"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.kind": {"CLASSIC_PREVIEW"}, + "resources.jobs.*.tasks[*].for_each_task.task.new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, + "resources.jobs.*.tasks[*].for_each_task.task.notebook_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.power_bi_model.authentication_method": {"OAUTH", "PAT"}, + "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.power_bi_model.storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, + "resources.jobs.*.tasks[*].for_each_task.task.power_bi_task.tables[*].storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, + "resources.jobs.*.tasks[*].for_each_task.task.run_if": {"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"}, + "resources.jobs.*.tasks[*].for_each_task.task.spark_python_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].for_each_task.task.sql_task.file.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].gen_ai_compute_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].health.rules[*].metric": {"RUN_DURATION_SECONDS", "STREAMING_BACKLOG_BYTES", "STREAMING_BACKLOG_FILES", "STREAMING_BACKLOG_RECORDS", "STREAMING_BACKLOG_SECONDS"}, + "resources.jobs.*.tasks[*].health.rules[*].op": {"GREATER_THAN"}, + "resources.jobs.*.tasks[*].new_cluster.aws_attributes.availability": {"ON_DEMAND", "SPOT", "SPOT_WITH_FALLBACK"}, + "resources.jobs.*.tasks[*].new_cluster.aws_attributes.ebs_volume_type": {"GENERAL_PURPOSE_SSD", "THROUGHPUT_OPTIMIZED_HDD"}, + "resources.jobs.*.tasks[*].new_cluster.azure_attributes.availability": {"ON_DEMAND_AZURE", "SPOT_AZURE", "SPOT_WITH_FALLBACK_AZURE"}, + "resources.jobs.*.tasks[*].new_cluster.data_security_mode": {"DATA_SECURITY_MODE_AUTO", "DATA_SECURITY_MODE_DEDICATED", "DATA_SECURITY_MODE_STANDARD", "LEGACY_PASSTHROUGH", "LEGACY_SINGLE_USER", "LEGACY_SINGLE_USER_STANDARD", "LEGACY_TABLE_ACL", "NONE", "SINGLE_USER", "USER_ISOLATION"}, + "resources.jobs.*.tasks[*].new_cluster.gcp_attributes.availability": {"ON_DEMAND_GCP", "PREEMPTIBLE_GCP", "PREEMPTIBLE_WITH_FALLBACK_GCP"}, + "resources.jobs.*.tasks[*].new_cluster.gcp_attributes.confidential_compute_type": {"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"}, + "resources.jobs.*.tasks[*].new_cluster.kind": {"CLASSIC_PREVIEW"}, + "resources.jobs.*.tasks[*].new_cluster.runtime_engine": {"NULL", "PHOTON", "STANDARD"}, + "resources.jobs.*.tasks[*].notebook_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].power_bi_task.power_bi_model.authentication_method": {"OAUTH", "PAT"}, + "resources.jobs.*.tasks[*].power_bi_task.power_bi_model.storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, + "resources.jobs.*.tasks[*].power_bi_task.tables[*].storage_mode": {"DIRECT_QUERY", "DUAL", "IMPORT"}, + "resources.jobs.*.tasks[*].run_if": {"ALL_DONE", "ALL_FAILED", "ALL_SUCCESS", "AT_LEAST_ONE_FAILED", "AT_LEAST_ONE_SUCCESS", "NONE_FAILED"}, + "resources.jobs.*.tasks[*].spark_python_task.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.tasks[*].sql_task.file.source": {"GIT", "WORKSPACE"}, + "resources.jobs.*.trigger.model.condition": {"MODEL_ALIAS_SET", "MODEL_CREATED", "MODEL_VERSION_READY"}, + "resources.jobs.*.trigger.pause_status": {"PAUSED", "UNPAUSED"}, + "resources.jobs.*.trigger.periodic.unit": {"DAYS", "HOURS", "WEEKS"}, + "resources.jobs.*.trigger.table_update.condition": {"ALL_UPDATED", "ANY_UPDATED"}, "resources.model_serving_endpoints.*.ai_gateway.guardrails.input.pii.behavior": {"BLOCK", "MASK", "NONE"}, "resources.model_serving_endpoints.*.ai_gateway.guardrails.output.pii.behavior": {"BLOCK", "MASK", "NONE"}, @@ -131,50 +135,65 @@ var EnumFields = map[string][]string{ "resources.model_serving_endpoints.*.ai_gateway.rate_limits[*].renewal_period": {"minute"}, "resources.model_serving_endpoints.*.config.served_entities[*].external_model.amazon_bedrock_config.bedrock_provider": {"ai21labs", "amazon", "anthropic", "cohere"}, "resources.model_serving_endpoints.*.config.served_entities[*].external_model.provider": {"ai21labs", "amazon-bedrock", "anthropic", "cohere", "custom", "databricks-model-serving", "google-cloud-vertex-ai", "openai", "palm"}, - "resources.model_serving_endpoints.*.config.served_entities[*].workload_type": {"CPU", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "MULTIGPU_MEDIUM"}, - "resources.model_serving_endpoints.*.config.served_models[*].workload_type": {"CPU", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "MULTIGPU_MEDIUM"}, + "resources.model_serving_endpoints.*.config.served_entities[*].workload_type": {"CPU", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "GPU_XLARGE", "MULTIGPU_MEDIUM"}, + "resources.model_serving_endpoints.*.config.served_models[*].workload_type": {"CPU", "GPU_LARGE", "GPU_MEDIUM", "GPU_SMALL", "GPU_XLARGE", "MULTIGPU_MEDIUM"}, "resources.model_serving_endpoints.*.permissions[*].level": {"CAN_MANAGE", "CAN_QUERY", "CAN_VIEW"}, "resources.model_serving_endpoints.*.rate_limits[*].key": {"endpoint", "user"}, "resources.model_serving_endpoints.*.rate_limits[*].renewal_period": {"minute"}, "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.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.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.*.clusters[*].gcp_attributes.confidential_compute_type": {"CONFIDENTIAL_COMPUTE_TYPE_NONE", "SEV_SNP"}, + "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.kafka_options.key_transformer.format": {"JSON", "STRING"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.key_transformer.json_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.kafka_options.value_transformer.format": {"JSON", "STRING"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.kafka_options.value_transformer.json_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.outlook_options.attachment_mode": {"ALL", "INLINE_ONLY", "NONE", "NON_INLINE_ONLY"}, + "resources.pipelines.*.ingestion_definition.objects[*].schema.connector_options.outlook_options.body_format": {"TEXT_HTML", "TEXT_PLAIN"}, + "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.kafka_options.key_transformer.format": {"JSON", "STRING"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.key_transformer.json_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.kafka_options.value_transformer.format": {"JSON", "STRING"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.kafka_options.value_transformer.json_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.outlook_options.attachment_mode": {"ALL", "INLINE_ONLY", "NONE", "NON_INLINE_ONLY"}, + "resources.pipelines.*.ingestion_definition.objects[*].table.connector_options.outlook_options.body_format": {"TEXT_HTML", "TEXT_PLAIN"}, + "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", "CONFLUENCE", "DYNAMICS365", "FOREIGN_CATALOG", "GA4_RAW_DATA", "GOOGLE_DRIVE", "JIRA", "MANAGED_POSTGRESQL", "META_MARKETING", "MYSQL", "NETSUITE", "ORACLE", "POSTGRESQL", "SALESFORCE", "SERVICENOW", "SHAREPOINT", "SQLSERVER", "TERADATA", "WORKDAY_RAAS", "ZENDESK"}, + "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"}, "resources.postgres_projects.*.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.postgres_synced_tables.*.scheduling_policy": {"CONTINUOUS", "SNAPSHOT", "TRIGGERED"}, + "resources.quality_monitors.*.custom_metrics[*].type": {"CUSTOM_METRIC_TYPE_AGGREGATE", "CUSTOM_METRIC_TYPE_DERIVED", "CUSTOM_METRIC_TYPE_DRIFT"}, "resources.quality_monitors.*.inference_log.problem_type": {"PROBLEM_TYPE_CLASSIFICATION", "PROBLEM_TYPE_REGRESSION"}, "resources.quality_monitors.*.schedule.pause_status": {"PAUSED", "UNPAUSED", "UNSPECIFIED"}, diff --git a/bundle/internal/validation/generated/required_fields.go b/bundle/internal/validation/generated/required_fields.go index db86398accb..ae6da95317f 100644 --- a/bundle/internal/validation/generated/required_fields.go +++ b/bundle/internal/validation/generated/required_fields.go @@ -219,12 +219,16 @@ var RequiredFields = map[string][]string{ "resources.postgres_branches.*": {"branch_id", "parent"}, + "resources.postgres_catalogs.*": {"postgres_database", "catalog_id"}, + "resources.postgres_endpoints.*": {"endpoint_type", "endpoint_id", "parent"}, "resources.postgres_endpoints.*.group": {"max", "min"}, "resources.postgres_projects.*": {"project_id"}, "resources.postgres_projects.*.permissions[*]": {"level"}, + "resources.postgres_synced_tables.*": {"synced_table_id"}, + "resources.quality_monitors.*": {"assets_dir", "output_schema_name", "table_name"}, "resources.quality_monitors.*.custom_metrics[*]": {"definition", "input_columns", "name", "output_data_type", "type"}, "resources.quality_monitors.*.inference_log": {"granularities", "model_id_col", "prediction_col", "problem_type", "timestamp_col"}, 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.go b/bundle/mutator.go index 854d5703ac4..90fdba28c9b 100644 --- a/bundle/mutator.go +++ b/bundle/mutator.go @@ -32,7 +32,7 @@ func safeMutatorName(m Mutator) string { t := reflect.TypeOf(m) // Handle pointer types by getting the element type - if t.Kind() == reflect.Ptr { + if t.Kind() == reflect.Pointer { t = t.Elem() } 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 f0041e91838..48ba7755714 100644 --- a/bundle/phases/bind.go +++ b/bundle/phases/bind.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/bundle/deploy/terraform" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/statemgmt" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" @@ -68,7 +69,7 @@ func Bind(ctx context.Context, b *bundle.Bundle, opts *terraform.BindOptions, en if !cmdio.IsPromptSupported(ctx) { result.Cancel() - logdiag.LogError(ctx, errors.New("This bind operation requires user confirmation, but the current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed.")) //nolint + logdiag.LogError(ctx, fmt.Errorf("this bind operation requires user confirmation, but the current console does not support prompting.\nTo proceed, use --auto-approve after reviewing the plan above.%s", agent.AgentNotice())) return } diff --git a/bundle/phases/deploy.go b/bundle/phases/deploy.go index 38389b9adb2..1db4b2e02da 100644 --- a/bundle/phases/deploy.go +++ b/bundle/phases/deploy.go @@ -20,12 +20,24 @@ import ( "github.com/databricks/cli/bundle/permissions" "github.com/databricks/cli/bundle/scripts" "github.com/databricks/cli/bundle/statemgmt" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "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,105 +46,24 @@ 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...) - pipelineActions := filterGroup(actions, "pipelines", types...) - volumeActions := filterGroup(actions, "volumes", types...) - dashboardActions := filterGroup(actions, "dashboards", types...) - databaseInstanceActions := filterGroup(actions, "database_instances", types...) - syncedDatabaseTableActions := filterGroup(actions, "synced_database_tables", types...) - postgresProjectActions := filterGroup(actions, "postgres_projects", types...) - postgresBranchActions := filterGroup(actions, "postgres_branches", types...) - - // We don't need to display any prompts in this case. - if len(schemaActions) == 0 && len(pipelineActions) == 0 && len(volumeActions) == 0 && len(dashboardActions) == 0 && - len(databaseInstanceActions) == 0 && len(syncedDatabaseTableActions) == 0 && - len(postgresProjectActions) == 0 && len(postgresBranchActions) == 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 pipelines is being recreated. - if len(pipelineActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreatePipelineMessage) - for _, action := range pipelineActions { - 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) - } - } - - // One or more database instances is being deleted or recreated. - if len(databaseInstanceActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreateDatabaseInstanceMessage) - for _, action := range databaseInstanceActions { - cmdio.Log(ctx, action) - } - } - - // One or more synced database tables is being deleted or recreated. - if len(syncedDatabaseTableActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreateSyncedDatabaseTableMessage) - for _, action := range syncedDatabaseTableActions { - cmdio.Log(ctx, action) - } - } - - // One or more Lakebase projects is being deleted or recreated. - if len(postgresProjectActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreatePostgresProjectMessage) - for _, action := range postgresProjectActions { - cmdio.Log(ctx, action) - } - } - - // One or more Lakebase branches is being deleted or recreated. - if len(postgresBranchActions) != 0 { - cmdio.LogString(ctx, deleteOrRecreatePostgresBranchMessage) - for _, action := range postgresBranchActions { - cmdio.Log(ctx, action) - } - } - if b.AutoApprove { return true, nil } if !cmdio.IsPromptSupported(ctx) { - return false, errors.New("the deployment requires destructive actions, but current console does not support prompting. Please specify --auto-approve if you would like to skip prompts and proceed") + return false, errors.New("the deployment requires destructive actions, but the current console does not support prompting.\n" + + DataLossWarning + "\n" + + "To proceed, use --auto-approve after reviewing the plan above." + agent.AgentNotice()) } 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) { @@ -140,17 +71,23 @@ func deployCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, ta // mutators need informed consent if they are potentially destructive. cmdio.LogString(ctx, "Deploying resources...") + // Apply resources and capture post-apply state. + // For direct: Finalize flushes the WAL to disk and returns the state; + // called even if Apply failed so partial progress is saved. + // For terraform: ParseResourcesState reads the file written by terraform.Apply. + var ( + state statemgmt.ExportedResourcesMap + err error + ) if targetEngine.IsDirect() { 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 { - if err := b.DeploymentBundle.StateDB.Finalize(); err != nil { - logdiag.LogError(ctx, err) - } - } + state, err = b.DeploymentBundle.StateDB.Finalize(ctx) } else { bundle.ApplyContext(ctx, b, terraform.Apply()) + state, err = terraform.ParseResourcesState(ctx, b) + } + if err != nil { + logdiag.LogError(ctx, err) } // Even if deployment failed, there might be updates in states that we need to upload @@ -160,7 +97,7 @@ func deployCore(ctx context.Context, b *bundle.Bundle, plan *deployplan.Plan, ta } bundle.ApplySeqContext(ctx, b, - statemgmt.Load(targetEngine), + statemgmt.Load(state), metadata.Compute(), metadata.Upload(), statemgmt.UploadStateForYamlSync(targetEngine), @@ -221,15 +158,27 @@ func Deploy(ctx context.Context, b *bundle.Bundle, outputHandler sync.OutputHand return } - if plan != nil { + planFromFile := plan != nil + if plan == nil { + // State is already open for read by process.go (for direct engine) + plan = RunPlan(ctx, b, engine) + } + + if engine.IsDirect() { + // Upgrade from read (opened by process.go) to write mode + if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil { + logdiag.LogError(ctx, err) + return + } + } + + if planFromFile { // Initialize DeploymentBundle for applying the loaded plan err := b.DeploymentBundle.InitForApply(ctx, b.WorkspaceClient(ctx), plan) if err != nil { logdiag.LogError(ctx, err) return } - } else { - plan = RunPlan(ctx, b, engine) } if logdiag.HasError(ctx) { diff --git a/bundle/phases/destroy.go b/bundle/phases/destroy.go index 4abc6140e45..98e6f7fee2a 100644 --- a/bundle/phases/destroy.go +++ b/bundle/phases/destroy.go @@ -14,6 +14,7 @@ import ( "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/databricks-sdk-go/apierr" @@ -32,6 +33,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,69 +62,7 @@ func approvalForDestroy(ctx context.Context, b *bundle.Bundle, plan *deployplan. cmdio.LogString(ctx, "") } - schemaActions := filterGroup(deleteActions, "schemas", deployplan.Delete) - pipelineActions := filterGroup(deleteActions, "pipelines", deployplan.Delete) - volumeActions := filterGroup(deleteActions, "volumes", deployplan.Delete) - databaseInstanceActions := filterGroup(deleteActions, "database_instances", deployplan.Delete) - syncedDatabaseTableActions := filterGroup(deleteActions, "synced_database_tables", deployplan.Delete) - postgresProjectActions := filterGroup(deleteActions, "postgres_projects", deployplan.Delete) - postgresBranchActions := filterGroup(deleteActions, "postgres_branches", deployplan.Delete) - - if len(schemaActions) > 0 { - cmdio.LogString(ctx, deleteSchemaMessage) - for _, a := range schemaActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(pipelineActions) > 0 { - cmdio.LogString(ctx, deletePipelineMessage) - for _, a := range pipelineActions { - 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, "") - } - - if len(databaseInstanceActions) > 0 { - cmdio.LogString(ctx, deleteDatabaseInstanceMessage) - for _, a := range databaseInstanceActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(syncedDatabaseTableActions) > 0 { - cmdio.LogString(ctx, deleteSyncedDatabaseTableMessage) - for _, a := range syncedDatabaseTableActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(postgresProjectActions) > 0 { - cmdio.LogString(ctx, deletePostgresProjectMessage) - for _, a := range postgresProjectActions { - cmdio.Log(ctx, a) - } - cmdio.LogString(ctx, "") - } - - if len(postgresBranchActions) > 0 { - cmdio.LogString(ctx, deletePostgresBranchMessage) - for _, a := range postgresBranchActions { - 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, "") @@ -122,28 +71,29 @@ 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(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 { - logdiag.LogError(ctx, err) - } - } } else { // Core destructive mutators for destroy. These require informed user consent. bundle.ApplyContext(ctx, b, terraform.Apply()) } + // Flush WAL to local state file before deleting remote files. + // Warn instead of hard-error: resources are already deleted, so proceed + // with file cleanup regardless of whether state flush succeeds. + if engine.IsDirect() { + if _, err := b.DeploymentBundle.StateDB.Finalize(ctx); err != nil { + diags := diag.WarningFromErr(err) + if len(diags) > 0 { + logdiag.LogDiag(ctx, diags[0]) + } + } + } + if logdiag.HasError(ctx) { return } @@ -225,6 +175,13 @@ func Destroy(ctx context.Context, b *bundle.Bundle, engine engine.EngineType) { } if hasApproval { + if engine.IsDirect() { + // Upgrade from read (opened by process.go) to write mode + if err := b.DeploymentBundle.StateDB.UpgradeToWrite(); err != nil { + logdiag.LogError(ctx, err) + return + } + } destroyCore(ctx, b, plan, engine) } else { cmdio.LogString(ctx, "Destroy cancelled!") diff --git a/bundle/phases/messages.go b/bundle/phases/messages.go index 347df8ece43..4c75879aa41 100644 --- a/bundle/phases/messages.go +++ b/bundle/phases/messages.go @@ -38,6 +38,10 @@ This action will result in the deletion or recreation of the following Lakebase All data stored in them will be permanently lost:` ) +// DataLossWarning is the warning shown when a non-interactive command is about +// to delete data-bearing resources. +const DataLossWarning = "Deleting data assets such as schemas, pipelines, or volumes may cause permanent data loss and should be carefully reviewed." + // Messages for bundle destroy. const ( deleteSchemaMessage = `This action will result in the deletion of the following UC schemas. Any underlying data may be lost:` diff --git a/bundle/phases/telemetry.go b/bundle/phases/telemetry.go index bb9a7d7e6b7..b7df901f867 100644 --- a/bundle/phases/telemetry.go +++ b/bundle/phases/telemetry.go @@ -8,6 +8,7 @@ import ( "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" @@ -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 4b892ff219c..b1f0c6442d1 100644 --- a/bundle/render/render_text_output.go +++ b/bundle/render/render_text_output.go @@ -10,26 +10,11 @@ import ( "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 }} @@ -82,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: @@ -101,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") } } @@ -118,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, @@ -179,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) } @@ -187,7 +172,7 @@ 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 slices.SortFunc(resourceGroups, func(a, b ResourceGroup) int { return cmp.Compare(a.GroupName, b.GroupName) @@ -198,7 +183,7 @@ func renderResourcesTemplate(out io.Writer, resourceGroups []ResourceGroup) erro }) } - 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/job.go b/bundle/run/job.go index 506d45e9175..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" ) @@ -50,9 +49,6 @@ func isSuccess(task jobs.RunTask) bool { func (r *jobRunner) logFailedTasks(ctx context.Context, runId int64) { w := r.bundle.WorkspaceClient(ctx) - red := color.New(color.FgRed).SprintFunc() - green := color.New(color.FgGreen).SprintFunc() - yellow := color.New(color.FgYellow).SprintFunc() 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) } } } 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_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/schema/.gitattributes b/bundle/schema/.gitattributes index 0a03eb531b2..f33e5e02470 100644 --- a/bundle/schema/.gitattributes +++ b/bundle/schema/.gitattributes @@ -1 +1,2 @@ jsonschema.json linguist-generated=true +jsonschema_for_docs.json linguist-generated=true diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index ee105a6f821..2a7f76aac85 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -156,13 +156,13 @@ }, "git_repository": { "description": "Git repository configuration for app deployments. When specified, deployments can\nreference code from this repository by providing only the git reference (branch, tag, or commit).", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitRepository", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitRepository" }, "git_source": { "description": "Git source configuration for app deployments. Specifies which git reference (branch, tag, or commit)\nto use when deploying the app. Used in conjunction with git_repository to deploy code directly from git.\nThe source_code_path within git_source specifies the relative path to the app code within the repository.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true }, "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.", @@ -180,7 +180,9 @@ "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/apps.AppResource" }, "source_code_path": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true }, "space": { "description": "Name of the space this app belongs to.", @@ -303,9 +305,7 @@ }, "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 + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.EncryptionSettings" }, "name": { "$ref": "#/$defs/string" @@ -1207,7 +1207,7 @@ "$ref": "#/$defs/string" }, "channel": { - "description": "DLT Release Channel that specifies which version to use.", + "description": "SDP Release Channel that specifies which version to use.", "$ref": "#/$defs/string" }, "clusters": { @@ -1388,6 +1388,9 @@ "parent": { "$ref": "#/$defs/string" }, + "replace_existing": { + "$ref": "#/$defs/bool" + }, "source_branch": { "$ref": "#/$defs/string" }, @@ -1413,6 +1416,39 @@ } ] }, + "resources.PostgresCatalog": { + "oneOf": [ + { + "type": "object", + "properties": { + "branch": { + "$ref": "#/$defs/string" + }, + "catalog_id": { + "$ref": "#/$defs/string" + }, + "create_database_if_missing": { + "$ref": "#/$defs/bool" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" + }, + "postgres_database": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "catalog_id", + "postgres_database" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.PostgresEndpoint": { "oneOf": [ { @@ -1445,6 +1481,9 @@ "parent": { "$ref": "#/$defs/string" }, + "replace_existing": { + "$ref": "#/$defs/bool" + }, "settings": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.EndpointSettings" }, @@ -1515,6 +1554,56 @@ } ] }, + "resources.PostgresSyncedTable": { + "oneOf": [ + { + "type": "object", + "properties": { + "branch": { + "$ref": "#/$defs/string" + }, + "create_database_objects_if_missing": { + "$ref": "#/$defs/bool" + }, + "existing_pipeline_id": { + "$ref": "#/$defs/string" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" + }, + "new_pipeline_spec": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.NewPipelineSpec" + }, + "postgres_database": { + "$ref": "#/$defs/string" + }, + "primary_key_columns": { + "$ref": "#/$defs/slice/string" + }, + "scheduling_policy": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy" + }, + "source_table_full_name": { + "$ref": "#/$defs/string" + }, + "synced_table_id": { + "$ref": "#/$defs/string" + }, + "timeseries_key": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false, + "required": [ + "synced_table_id" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.QualityMonitor": { "oneOf": [ { @@ -1948,15 +2037,15 @@ "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" }, + "target_qps": { + "$ref": "#/$defs/int64" + }, "usage_policy_id": { "$ref": "#/$defs/string", "x-databricks-preview": "PRIVATE", @@ -2526,12 +2615,20 @@ "postgres_branches": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresBranch" }, + "postgres_catalogs": { + "description": "The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch.", + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresCatalog" + }, "postgres_endpoints": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresEndpoint" }, "postgres_projects": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresProject" }, + "postgres_synced_tables": { + "description": "The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance.", + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresSyncedTable" + }, "quality_monitors": { "description": "The quality monitor definitions for the bundle, where each key is the name of the quality monitor.", "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.QualityMonitor", @@ -2833,9 +2930,7 @@ }, "git_source": { "description": "Git repository to use as the source for the app deployment.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource" }, "mode": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppDeploymentMode" @@ -3447,7 +3542,8 @@ "type": "string", "enum": [ "MEDIUM", - "LARGE" + "LARGE", + "LIQUID" ] }, { @@ -4108,7 +4204,21 @@ "CREATE_CLEAN_ROOM", "MODIFY_CLEAN_ROOM", "EXECUTE_CLEAN_ROOM_TASK", - "EXTERNAL_USE_SCHEMA" + "EXTERNAL_USE_SCHEMA", + "VIEW_OBJECT", + "MANAGE_GRANTS", + "INSERT", + "UPDATE", + "DELETE", + "VIEW_ADMIN_METADATA", + "VIEW_METADATA", + "USE_VOLUME", + "READ_METADATA", + "MANAGE_ACCESS", + "MANAGE_ACCESS_CONTROL", + "CREATE_SERVICE", + "CREATE_FEATURE", + "READ_FEATURE" ] }, { @@ -4600,11 +4710,27 @@ } ] }, + "compute.ConfidentialComputeType": { + "oneOf": [ + { + "type": "string", + "description": "Confidential computing technology for GCP instances.\nAligns with gcloud's --confidential-compute-type flag and the REST API's\nconfidentialInstanceConfig.confidentialInstanceType field.\nSee: https://cloud.google.com/confidential-computing/confidential-vm/docs/create-a-confidential-vm-instance", + "enum": [ + "CONFIDENTIAL_COMPUTE_TYPE_NONE", + "SEV_SNP" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "compute.DataSecurityMode": { "oneOf": [ { "type": "string", - "description": "Data security mode decides what data governance model to use when accessing data\nfrom a cluster.\n\nThe following modes can only be used when `kind = CLASSIC_PREVIEW`.\n* `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration.\n* `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`.\n* `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`.\n\nThe following modes can be used regardless of `kind`.\n* `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode.\n* `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.\n* `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.\n\nThe following modes are deprecated starting with Databricks Runtime 15.0 and\nwill be removed for future Databricks Runtime versions:\n\n* `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters.\n* `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters.\n* `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters.\n* `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled.", + "description": "Data security mode decides what data governance model to use when accessing data\nfrom a cluster.\n\n* `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration.\n* `DATA_SECURITY_MODE_STANDARD`: 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.\n* `DATA_SECURITY_MODE_DEDICATED`: 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.\n\nThe following modes are legacy aliases for the above modes:\n\n* `USER_ISOLATION`: Legacy alias for `DATA_SECURITY_MODE_STANDARD`.\n* `SINGLE_USER`: Legacy alias for `DATA_SECURITY_MODE_DEDICATED`.\n\nThe following modes are deprecated starting with Databricks Runtime 15.0 and\nwill be removed for future Databricks Runtime versions:\n\n* `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters.\n* `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters.\n* `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters.\n* `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled.", "enum": [ "NONE", "SINGLE_USER", @@ -4709,10 +4835,10 @@ "oneOf": [ { "type": "object", - "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.", + "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP'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 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", + "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.\nFor more information about Databricks-provided base environments, see the\n[list workspace base environments](:method:Environments/ListWorkspaceBaseEnvironments) API.\nFor more information, see", "$ref": "#/$defs/string" }, "client": { @@ -4754,6 +4880,12 @@ "description": "Boot disk size in GB", "$ref": "#/$defs/int" }, + "confidential_compute_type": { + "description": "The confidential computing technology for this cluster's instances.\nCurrently only SEV_SNP is supported, and only on N2D instance types.\nWhen not set, no confidential computing is applied.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/compute.ConfidentialComputeType", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "first_on_demand": { "description": "The first `first_on_demand` nodes of the cluster will be placed on on-demand instances.\nThis value should be greater than 0, to make sure the cluster driver node is placed on an\non-demand instance. If this value is greater than or equal to the current cluster size, all\nnodes will be placed on on-demand instances. If this value is less than the current cluster\nsize, `first_on_demand` nodes will be placed on on-demand instances and the remainder will\nbe placed on `availability` instances. Note that this value does not affect\ncluster size and cannot currently be mutated over the lifetime of a cluster.", "$ref": "#/$defs/int" @@ -4831,7 +4963,8 @@ "description": "HardwareAcceleratorType: The type of hardware accelerator to use for compute workloads.\nNOTE: This enum is referenced and is intended to be used by other Databricks services\nthat need to specify hardware accelerator requirements for AI compute workloads.", "enum": [ "GPU_1xA10", - "GPU_8xH100" + "GPU_8xH100", + "GPU_1xH100" ] }, { @@ -5286,7 +5419,8 @@ "DELETING", "STOPPED", "UPDATING", - "FAILING_OVER" + "FAILING_OVER", + "UPGRADING" ] }, { @@ -5586,7 +5720,8 @@ "CAN_MONITOR", "CAN_CREATE", "CAN_MONITOR_ONLY", - "CAN_CREATE_APP" + "CAN_CREATE_APP", + "UNSPECIFIED" ] }, { @@ -6722,8 +6857,24 @@ "type": "object", "properties": { "full_refresh": { - "description": "If true, triggers a full refresh on the delta live table.", + "description": "If true, triggers a full refresh on the spark declarative pipeline.", "$ref": "#/$defs/bool" + }, + "full_refresh_selection": { + "description": "A list of tables to update with fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_flow_selection": { + "description": "Flow names to selectively refresh. These are unioned with other selective refresh\noptions (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_selection": { + "description": "A list of tables to update without fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "reset_checkpoint_selection": { + "description": "A list of streaming flows to reset checkpoints without clearing data.", + "$ref": "#/$defs/slice/string" } }, "additionalProperties": false @@ -6740,12 +6891,28 @@ "type": "object", "properties": { "full_refresh": { - "description": "If true, triggers a full refresh on the delta live table.", + "description": "If true, triggers a full refresh on the spark declarative pipeline.", "$ref": "#/$defs/bool" }, + "full_refresh_selection": { + "description": "A list of tables to update with fullRefresh.", + "$ref": "#/$defs/slice/string" + }, "pipeline_id": { "description": "The full name of the pipeline task to execute.", "$ref": "#/$defs/string" + }, + "refresh_flow_selection": { + "description": "Flow names to selectively refresh. These are unioned with other selective refresh\noptions (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_selection": { + "description": "A list of tables to update without fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "reset_checkpoint_selection": { + "description": "A list of streaming flows to reset checkpoints without clearing data.", + "$ref": "#/$defs/slice/string" } }, "additionalProperties": false, @@ -6857,6 +7024,48 @@ } ] }, + "jobs.PythonOperatorTask": { + "oneOf": [ + { + "type": "object", + "properties": { + "main": { + "description": "Fully qualified name of the main class or function.\nFor example, `my_project.my_function` or `my_project.MyOperator`.", + "$ref": "#/$defs/string" + }, + "parameters": { + "description": "An ordered list of task parameters.\nTODO(JOBS-30885): Add limits for parameters.", + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "jobs.PythonOperatorTaskParameter": { + "oneOf": [ + { + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/string" + }, + "value": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "jobs.PythonWheelTask": { "oneOf": [ { @@ -7452,9 +7661,7 @@ }, "disabled": { "description": "An optional flag to disable the task. If set to true, the task will not run even if it is part of a job.", - "$ref": "#/$defs/bool", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/bool" }, "email_notifications": { "description": "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.", @@ -7516,6 +7723,12 @@ "description": "The task triggers a Power BI semantic model update when the `power_bi_task` field is present.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PowerBiTask" }, + "python_operator_task": { + "description": "The task runs a Python operator task.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTask", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "python_wheel_task": { "description": "The task runs a Python wheel when the `python_wheel_task` field is present.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonWheelTask" @@ -7877,6 +8090,25 @@ } ] }, + "pipelines.ConfluenceConnectorOptions": { + "oneOf": [ + { + "type": "object", + "description": "Confluence specific options for ingestion", + "properties": { + "include_confluence_spaces": { + "description": "(Optional) Spaces to filter Confluence data on", + "$ref": "#/$defs/slice/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.ConnectionParameters": { "oneOf": [ { @@ -7903,6 +8135,10 @@ "type": "object", "description": "Wrapper message for source-specific options to support multiple connector types", "properties": { + "confluence_options": { + "description": "Confluence specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConfluenceConnectorOptions" + }, "gdrive_options": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions", "x-databricks-preview": "PRIVATE", @@ -7914,16 +8150,47 @@ "x-databricks-preview": "PRIVATE", "doNotSuggest": true }, + "jira_options": { + "description": "Jira specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.JiraConnectorOptions" + }, + "kafka_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.KafkaOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "meta_ads_options": { + "description": "Meta Marketing (Meta Ads) specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.MetaMarketingOptions" + }, + "outlook_options": { + "description": "Outlook specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookOptions", + "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 }, + "smartsheet_options": { + "description": "Smartsheet specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SmartsheetOptions", + "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 + }, + "zendesk_support_options": { + "description": "Zendesk Support specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ZendeskSupportOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true } }, "additionalProperties": false @@ -8155,7 +8422,8 @@ "EXCEL", "PARQUET", "AVRO", - "ORC" + "ORC", + "FILE" ] }, { @@ -8223,6 +8491,24 @@ } ] }, + "pipelines.GoogleAdsConfig": { + "oneOf": [ + { + "type": "object", + "properties": { + "manager_account_id": { + "description": "(Required) Manager Account ID (also called MCC Account ID) used to list and access\ncustomer accounts under this manager account. This is required for fetching the list\nof customer accounts during source selection.\nIf the same field is also set in the object-level GoogleAdsOptions (connector_options),\nthe object-level value takes precedence over this top-level config.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.GoogleAdsOptions": { "oneOf": [ { @@ -8284,7 +8570,9 @@ "enum": [ "FILE", "FILE_METADATA", - "PERMISSION" + "PERMISSION", + "FILE_PERMISSION", + "GROUP_MEMBERSHIP" ] }, { @@ -8393,9 +8681,7 @@ }, "ingest_from_uc_foreign_catalog": { "description": "Immutable. If set to true, the pipeline will ingest tables from the\nUC foreign catalogs directly without the need to specify a UC connection or ingestion gateway.\nThe `source_catalog` fields in objects of IngestionConfig are interpreted as\nthe UC foreign catalogs to ingest from.", - "$ref": "#/$defs/bool", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/bool" }, "ingestion_gateway_id": { "description": "Identifier for the gateway that is used by this ingestion pipeline to communicate with the source database.\nThis is used with CDC connectors to databases like SQL Server using a gateway pipeline (connector_type = CDC).\nUnder certain conditions, this can be replaced with connection_name to change the connector to Combined Cdc\nManaged Ingestion Pipeline.", @@ -8435,21 +8721,15 @@ "properties": { "cursor_columns": { "description": "The names of the monotonically increasing columns in the source table that are used to enable\nthe table to be read and ingested incrementally through structured streaming.\nThe columns are allowed to have repeated values but have to be non-decreasing.\nIf the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these\ncolumns will implicitly define the `sequence_by` behavior. You can still explicitly set\n`sequence_by` to override this default.", - "$ref": "#/$defs/slice/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/slice/string" }, "deletion_condition": { "description": "Specifies a SQL WHERE condition that specifies that the source row has been deleted.\nThis is sometimes referred to as \"soft-deletes\".\nFor example: \"Operation = 'DELETE'\" or \"is_deleted = true\".\nThis field is orthogonal to `hard_deletion_sync_interval_in_seconds`,\none for soft-deletes and the other for hard-deletes.\nSee also the hard_deletion_sync_min_interval_in_seconds field for\nhandling of \"hard deletes\" where the source rows are physically removed from the table.", - "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/string" }, "hard_deletion_sync_min_interval_in_seconds": { "description": "Specifies the minimum interval (in seconds) between snapshots on primary keys\nfor detecting and synchronizing hard deletions—i.e., rows that have been\nphysically removed from the source table.\nThis interval acts as a lower bound. If ingestion runs less frequently than\nthis value, hard deletion synchronization will align with the actual ingestion\nfrequency instead of happening more often.\nIf not set, hard deletion synchronization via snapshots is disabled.\nThis field is mutable and can be updated without triggering a full snapshot.", - "$ref": "#/$defs/int64", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/int64" } }, "additionalProperties": false @@ -8519,6 +8799,8 @@ "enum": [ "MYSQL", "POSTGRESQL", + "REDSHIFT", + "SQLDW", "SQLSERVER", "SALESFORCE", "BIGQUERY", @@ -8532,6 +8814,89 @@ "SHAREPOINT", "DYNAMICS365", "GOOGLE_DRIVE", + "JIRA", + "CONFLUENCE", + "META_MARKETING", + "GOOGLE_ADS", + "TIKTOK_ADS", + "SALESFORCE_MARKETING_CLOUD", + "HUBSPOT", + "WORKDAY_HCM", + "GUIDEWIRE", + "ZENDESK", + "COMMUNITY", + "SLACK_AUDIT_LOGS", + "KAFKA", + "CROWDSTRIKE_EVENT_STREAM", + "WORKDAY_ACTIVITY_LOGGING", + "AKAMAI_WAF", + "VEEVA", + "VEEVA_VAULT", + "M365_AUDIT_LOGS", + "OKTA_SYSTEM_LOGS", + "ONE_PASSWORD_EVENT_LOGS", + "PROOFPOINT_SIEM", + "WIZ_AUDIT_LOGS", + "GITHUB", + "OUTLOOK", + "SMARTSHEET", + "MICROSOFT_TEAMS", + "ADOBE_CAMPAIGNS", + "LINKEDIN_ADS", + "X_ADS", + "BING_ADS", + "GOOGLE_SEARCH_CONSOLE", + "PINTEREST_ADS", + "REDDIT_ADS", + "PENDO", + "API_SOURCE", + "SLACK_ACCESS_AND_INTEGRATION_LOGS", + "ORACLE_FUSION_CLOUD", + "RABBITMQ", + "GOOGLE_ANALYTICS", + "AMPLITUDE", + "NOTION", + "ADP_WORKFORCE_NOW", + "SAS", + "GONG", + "SALESLOFT", + "GOOGLE_WORKSPACE", + "SHOPIFY", + "ORACLE_ELOQUA", + "EPIC_CLARITY", + "LINEAR", + "APPLE_APP_STORE", + "SPLUNK", + "GITLAB", + "ZOOM_LOGS", + "MONDAY_COM", + "AIRTABLE", + "MICROSOFT_ENTRA_ID", + "PAGERDUTY", + "APPFIGURES", + "ADOBE_COMMERCE", + "QUICKBOOKS", + "SQUARE", + "ZOHO_BOOKS", + "SNAPCHAT_ADS", + "GENESYS", + "SAP_SUCCESSFACTORS", + "YOUTUBE_ANALYTICS", + "CERIDIAN_DAYFORCE", + "FRESHSERVICE", + "SENDGRID", + "AZURE_MONITOR_LOGS", + "ATLASSIAN_ORGANIZATION", + "HIBOB", + "APPLE_SEARCH_ADS", + "AWIN", + "DELIGHTED", + "FRONT", + "GURU", + "PARTNERSTACK", + "MARKETO", + "AHA", + "NETSKOPE_LOGS", "FOREIGN_CATALOG" ] }, @@ -8541,10 +8906,17 @@ } ] }, - "pipelines.ManualTrigger": { + "pipelines.JiraConnectorOptions": { "oneOf": [ { "type": "object", + "description": "Jira specific options for ingestion", + "properties": { + "include_jira_spaces": { + "description": "(Optional) Projects to filter Jira data on", + "$ref": "#/$defs/slice/string" + } + }, "additionalProperties": false }, { @@ -8553,13 +8925,29 @@ } ] }, - "pipelines.NotebookLibrary": { + "pipelines.JsonTransformerOptions": { "oneOf": [ { "type": "object", "properties": { - "path": { - "description": "The absolute path of the source code.", + "as_variant": { + "description": "Parse the entire value as a single Variant column.", + "$ref": "#/$defs/bool" + }, + "schema": { + "description": "Inline schema string for JSON parsing (Spark DDL format).", + "$ref": "#/$defs/string" + }, + "schema_evolution_mode": { + "description": "(Optional) Schema evolution mode for schema inference.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode" + }, + "schema_file_path": { + "description": "Path to a schema file (.ddl).", + "$ref": "#/$defs/string" + }, + "schema_hints": { + "description": "(Optional) Schema hints as a comma-separated string of \"column_name type\" pairs.", "$ref": "#/$defs/string" } }, @@ -8571,13 +8959,136 @@ } ] }, - "pipelines.Notifications": { + "pipelines.KafkaOptions": { "oneOf": [ { "type": "object", "properties": { - "alerts": { - "description": "A list of alerts that trigger the sending of notifications to the configured\ndestinations. The supported alerts are:\n\n* `on-update-success`: A pipeline update completes successfully.\n* `on-update-failure`: Each time a pipeline update fails.\n* `on-update-fatal-failure`: A pipeline update fails with a non-retryable (fatal) error.\n* `on-flow-failure`: A single data flow fails.", + "client_config": { + "description": "Undocumented backdoor mechanism for overriding parameters\nto pass to the Kafka client.\nThis is not supported and may break at any time.", + "$ref": "#/$defs/map/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "key_transformer": { + "description": "(Optional) Transformer for the message key.\nIf not specified, the key is left as raw bytes.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.Transformer" + }, + "max_offsets_per_trigger": { + "description": "Internal option to control the maximum number of offsets to process per trigger.", + "$ref": "#/$defs/int64", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "starting_offset": { + "description": "(Optional) Where to begin reading when no checkpoint exists.\nValid values: \"latest\" and \"earliest\". Defaults to \"latest\".", + "$ref": "#/$defs/string" + }, + "topic_pattern": { + "description": "Java regex pattern to subscribe to matching topics.\nOnly one of topics or topic_pattern must be specified.", + "$ref": "#/$defs/string" + }, + "topics": { + "description": "Topics to subscribe to.\nOnly one of topics or topic_pattern must be specified.", + "$ref": "#/$defs/slice/string" + }, + "value_transformer": { + "description": "(Optional) Transformer for the message value.\nIf not specified, the value is left as raw bytes.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.Transformer" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.ManualTrigger": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.MetaMarketingOptions": { + "oneOf": [ + { + "type": "object", + "description": "Meta Marketing (Meta Ads) specific options for ingestion", + "properties": { + "action_attribution_windows": { + "description": "(Optional) Action attribution windows for insights reporting (e.g. \"28d_click\", \"1d_view\")", + "$ref": "#/$defs/slice/string" + }, + "action_breakdowns": { + "description": "(Optional) Action breakdowns to configure for data aggregation", + "$ref": "#/$defs/slice/string" + }, + "action_report_time": { + "description": "(Optional) Timing used to report action statistics (impression, conversion, mixed, or lifetime)", + "$ref": "#/$defs/string" + }, + "breakdowns": { + "description": "(Optional) Breakdowns to configure for data aggregation", + "$ref": "#/$defs/slice/string" + }, + "custom_insights_lookback_window": { + "description": "(Optional) Window in days to revisit data during sync to capture\nupdated conversion data from the API.", + "$ref": "#/$defs/int" + }, + "level": { + "description": "(Optional) Granularity of data to pull (account, ad, adset, campaign)", + "$ref": "#/$defs/string" + }, + "start_date": { + "description": "(Optional) Start date in yyyy-MM-dd format (e.g. 2025-01-15). Data added\nafter this date will be ingested", + "$ref": "#/$defs/string" + }, + "time_increment": { + "description": "(Optional) Value in string by which to aggregate statistics (can take all_days, monthly or number of days)", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.NotebookLibrary": { + "oneOf": [ + { + "type": "object", + "properties": { + "path": { + "description": "The absolute path of the source code.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.Notifications": { + "oneOf": [ + { + "type": "object", + "properties": { + "alerts": { + "description": "A list of alerts that trigger the sending of notifications to the configured\ndestinations. The supported alerts are:\n\n* `on-update-success`: A pipeline update completes successfully.\n* `on-update-failure`: Each time a pipeline update fails.\n* `on-update-fatal-failure`: A pipeline update fails with a non-retryable (fatal) error.\n* `on-flow-failure`: A single data flow fails.", "$ref": "#/$defs/slice/string" }, "email_recipients": { @@ -8623,6 +9134,101 @@ } ] }, + "pipelines.OutlookAttachmentMode": { + "oneOf": [ + { + "type": "string", + "description": "Attachment behavior mode for Outlook ingestion", + "enum": [ + "ALL", + "NON_INLINE_ONLY", + "INLINE_ONLY", + "NONE" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.OutlookBodyFormat": { + "oneOf": [ + { + "type": "string", + "description": "Body format for Outlook email content", + "enum": [ + "TEXT_HTML", + "TEXT_PLAIN" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.OutlookOptions": { + "oneOf": [ + { + "type": "object", + "description": "Outlook specific options for ingestion", + "properties": { + "attachment_mode": { + "description": "(Optional) Controls which attachments to ingest.\nIf not specified, defaults to ALL.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookAttachmentMode" + }, + "body_format": { + "description": "(Optional) Defines how the body_content column is populated.\nTEXT_HTML: Preserves full formatting, links, and styling.\nTEXT_PLAIN: Converts body to plain text. Recommended for AI/RAG pipelines to reduce token usage and noise.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookBodyFormat" + }, + "folder_filter": { + "description": "Deprecated. Use include_folders instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "deprecated": true + }, + "include_folders": { + "description": "(Optional) Filter mail folders to include in the sync.\nIf not specified, all folders will be synced.\nExamples: Inbox, Sent Items, Custom_Folder\nFilter semantics: OR between different folders.", + "$ref": "#/$defs/slice/string" + }, + "include_mailboxes": { + "description": "(Optional) List of mailboxes to sync (e.g. mailbox email addresses or identifiers).\nIf not specified, all accessible mailboxes are ingested.\nFilter semantics: OR between different mailboxes.", + "$ref": "#/$defs/slice/string" + }, + "include_senders": { + "description": "(Optional) Filter emails by sender address. Uses exact email match.\nExamples: user@vendor.com, alerts@system.io, noreply@company.com\nIf not specified, emails from all senders will be synced.\nFilter semantics: OR between different senders.", + "$ref": "#/$defs/slice/string" + }, + "include_subjects": { + "description": "(Optional) Filter emails by subject line. Values ending with \"*\" use prefix match (subject starts with\nthe part before \"*\"); otherwise substring match (subject contains the value).\nExamples: \"Invoice\" (substring), \"Re:*\" (prefix), \"Support Ticket\", \"URGENT*\"\nIf not specified, emails with all subjects will be synced.\nFilter semantics: OR between different subjects.", + "$ref": "#/$defs/slice/string" + }, + "sender_filter": { + "description": "Deprecated. Use include_senders instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "deprecated": true + }, + "start_date": { + "description": "(Optional) Start date for the initial sync in YYYY-MM-DD format.\nFormat: YYYY-MM-DD (e.g., 2024-01-01)\nThis determines the earliest date from which to sync historical data.\nIf not specified, complete history is ingested.", + "$ref": "#/$defs/string" + }, + "subject_filter": { + "description": "Deprecated. Use include_subjects instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "deprecated": true + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.PathPattern": { "oneOf": [ { @@ -8888,7 +9494,7 @@ "oneOf": [ { "type": "object", - "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 dependencies are supported.", + "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines.\nIn this minimal environment spec, only pip dependencies are supported.", "properties": { "dependencies": { "description": "List of pip dependencies, as supported by the version of pip in this environment.\nEach dependency is a pip requirement file line https://pip.pypa.io/en/stable/reference/requirements-file-format/\nAllowed dependency could be \u003crequirement specifier\u003e, \u003carchive url/path\u003e, \u003clocal project path\u003e(WSFS or Volumes in Databricks), \u003cvcs project url\u003e", @@ -9049,9 +9655,7 @@ "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 + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions" }, "destination_catalog": { "description": "Required. Destination catalog to store tables.", @@ -9121,7 +9725,9 @@ "FILE", "FILE_METADATA", "PERMISSION", - "LIST" + "LIST", + "FILE_PERMISSION", + "GROUP_MEMBERSHIP" ] }, { @@ -9130,6 +9736,25 @@ } ] }, + "pipelines.SmartsheetOptions": { + "oneOf": [ + { + "type": "object", + "description": "Smartsheet specific options for ingestion", + "properties": { + "enforce_schema": { + "description": "(Optional) When true, maps each column to its Smartsheet-declared type (Text/Number/Date/\nCheckbox/etc.). Cells that do not conform to the declared type are set to NULL.\nWhen false, all columns land as STRING. Use false for sheets with irregular data or columns\nthat frequently violate their own declared type.\nIf not specified, defaults to true.", + "$ref": "#/$defs/bool" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "pipelines.SourceCatalogConfig": { "oneOf": [ { @@ -9161,6 +9786,11 @@ "catalog": { "description": "Catalog-level source configuration parameters", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SourceCatalogConfig" + }, + "google_ads_config": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsConfig", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true } }, "additionalProperties": false @@ -9178,9 +9808,7 @@ "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 + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConnectorOptions" }, "destination_catalog": { "description": "Required. Destination catalog to store table.", @@ -9247,15 +9875,11 @@ }, "query_based_connector_config": { "description": "Configurations that are only applicable for query-based ingestion connectors.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig" }, "row_filter": { "description": "(Optional, Immutable) The row filter condition to be applied to the table.\nIt must not contain the WHERE keyword, only the actual filter condition.\nIt must be in DBSQL format.", - "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/string" }, "salesforce_include_formula_fields": { "description": "If true, formula fields defined in the table are included in the ingestion. This setting is only valid for the Salesforce connector", @@ -9265,9 +9889,7 @@ }, "scd_type": { "description": "The SCD type to use to ingest the table.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfigScdType", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfigScdType" }, "sequence_by": { "description": "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.", @@ -9385,6 +10007,64 @@ } ] }, + "pipelines.Transformer": { + "oneOf": [ + { + "type": "object", + "description": "Specifies how to transform binary data into structured data.", + "properties": { + "format": { + "description": "Required: the wire format of the data.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TransformerFormat" + }, + "json_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.JsonTransformerOptions" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.TransformerFormat": { + "oneOf": [ + { + "type": "string", + "enum": [ + "STRING", + "JSON", + "AVRO", + "PROTOBUF" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, + "pipelines.ZendeskSupportOptions": { + "oneOf": [ + { + "type": "object", + "description": "Zendesk Support specific options for ingestion", + "properties": { + "start_date": { + "description": "(Optional) Start date in YYYY-MM-DD format for the initial sync.\nThis determines the earliest date from which to sync historical data.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "postgres.EndpointGroupSpec": { "oneOf": [ { @@ -9450,6 +10130,32 @@ } ] }, + "postgres.NewPipelineSpec": { + "oneOf": [ + { + "type": "object", + "properties": { + "budget_policy_id": { + "description": "Budget policy to set on the newly created pipeline.", + "$ref": "#/$defs/string" + }, + "storage_catalog": { + "description": "UC catalog for the pipeline to store intermediate files (checkpoints, event logs etc).\nThis needs to be a standard catalog where the user has permissions to create Delta tables.", + "$ref": "#/$defs/string" + }, + "storage_schema": { + "description": "UC schema for the pipeline to store intermediate files (checkpoints, event logs etc).\nThis needs to be in the standard catalog where the user has permissions to create Delta tables.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "postgres.ProjectCustomTag": { "oneOf": [ { @@ -9487,7 +10193,7 @@ "$ref": "#/$defs/float64" }, "no_suspension": { - "description": "When set to true, explicitly disables automatic suspension (never suspend).\nShould be set to true when provided.", + "description": "When set to true, explicitly disables automatic suspension (never suspend).\nShould be set to true when provided.\nMutually exclusive with `suspend_timeout_duration`. When updating, use `spec.project_default_settings.suspension` in the update_mask.", "$ref": "#/$defs/bool" }, "pg_settings": { @@ -9495,7 +10201,7 @@ "$ref": "#/$defs/map/string" }, "suspend_timeout_duration": { - "description": "Duration of inactivity after which the compute endpoint is automatically suspended.\nIf specified should be between 60s and 604800s (1 minute to 1 week).", + "description": "Duration of inactivity after which the compute endpoint is automatically suspended.\nIf specified should be between 60s and 604800s (1 minute to 1 week).\nMutually exclusive with `no_suspension`. When updating, use `spec.project_default_settings.suspension` in the update_mask.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/common/types/duration.Duration" } }, @@ -9507,6 +10213,23 @@ } ] }, + "postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy": { + "oneOf": [ + { + "type": "string", + "description": "Scheduling policy of the synced table's underlying pipeline.", + "enum": [ + "CONTINUOUS", + "TRIGGERED", + "SNAPSHOT" + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "serving.Ai21LabsConfig": { "oneOf": [ { @@ -9887,6 +10610,7 @@ "oneOf": [ { "type": "object", + "description": "Deprecated: legacy inference table configuration. Please use AI Gateway inference tables instead.\nSee https://docs.databricks.com/aws/en/ai-gateway/inference-tables.", "properties": { "catalog_name": { "description": "The name of the catalog in Unity Catalog. NOTE: On update, you cannot change the catalog name if the inference table is already enabled.", @@ -10048,8 +10772,10 @@ "type": "object", "properties": { "auto_capture_config": { - "description": "Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog.\nNote: this field is deprecated for creating new provisioned throughput endpoints,\nor updating existing provisioned throughput endpoints that never have inference table configured;\nin these cases please use AI Gateway to manage inference tables.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/serving.AutoCaptureConfigInput" + "description": "Configuration for legacy Inference Tables which automatically log requests and responses to Unity\nCatalog.\nDeprecated: please use AI Gateway inference tables instead. See\nhttps://docs.databricks.com/aws/en/ai-gateway/inference-tables.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/serving.AutoCaptureConfigInput", + "deprecationMessage": "This field is deprecated", + "deprecated": true }, "served_entities": { "description": "The list of served entities under the serving endpoint config.", @@ -10565,7 +11291,8 @@ "GPU_MEDIUM", "GPU_SMALL", "GPU_LARGE", - "MULTIGPU_MEDIUM" + "MULTIGPU_MEDIUM", + "GPU_XLARGE" ] }, { @@ -10601,7 +11328,8 @@ "GPU_MEDIUM", "GPU_SMALL", "GPU_LARGE", - "MULTIGPU_MEDIUM" + "MULTIGPU_MEDIUM", + "GPU_XLARGE" ] }, { @@ -10922,7 +11650,8 @@ "enum": [ "TYPE_UNSPECIFIED", "CLASSIC", - "PRO" + "PRO", + "REYDEN" ] }, { @@ -11056,7 +11785,8 @@ "description": "Type of endpoint.", "enum": [ "STORAGE_OPTIMIZED", - "STANDARD" + "STANDARD", + "STANDARD_ON_ORION" ] }, { @@ -11369,6 +12099,20 @@ } ] }, + "resources.PostgresCatalog": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresCatalog" + } + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.PostgresEndpoint": { "oneOf": [ { @@ -11397,6 +12141,20 @@ } ] }, + "resources.PostgresSyncedTable": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresSyncedTable" + } + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "resources.QualityMonitor": { "oneOf": [ { @@ -12030,6 +12788,20 @@ } ] }, + "jobs.PythonOperatorTaskParameter": { + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter" + } + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\.[a-zA-Z]+([-_]?[a-zA-Z0-9]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "jobs.SqlTaskSubscription": { "oneOf": [ { diff --git a/bundle/schema/jsonschema_for_docs.json b/bundle/schema/jsonschema_for_docs.json index 3c13ff8c134..e5caee8b64a 100644 --- a/bundle/schema/jsonschema_for_docs.json +++ b/bundle/schema/jsonschema_for_docs.json @@ -108,13 +108,13 @@ "git_repository": { "description": "Git repository configuration for app deployments. When specified, deployments can\nreference code from this repository by providing only the git reference (branch, tag, or commit).", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitRepository", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.283.0" }, "git_source": { "description": "Git source configuration for app deployments. Specifies which git reference (branch, tag, or commit)\nto use when deploying the app. Used in conjunction with git_repository to deploy code directly from git.\nThe source_code_path within git_source specifies the relative path to the app code within the repository.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, "x-since-version": "v0.290.0" }, "lifecycle": { @@ -138,6 +138,8 @@ }, "source_code_path": { "$ref": "#/$defs/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, "x-since-version": "v0.239.0" }, "space": { @@ -247,8 +249,7 @@ "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 + "x-since-version": "v0.298.0" }, "name": { "$ref": "#/$defs/string", @@ -1168,7 +1169,7 @@ "x-since-version": "v0.229.0" }, "channel": { - "description": "DLT Release Channel that specifies which version to use.", + "description": "SDP Release Channel that specifies which version to use.", "$ref": "#/$defs/string", "x-since-version": "v0.229.0" }, @@ -1371,6 +1372,10 @@ "$ref": "#/$defs/string", "x-since-version": "v0.287.0" }, + "replace_existing": { + "$ref": "#/$defs/bool", + "x-since-version": "v1.0.0" + }, "source_branch": { "$ref": "#/$defs/string", "x-since-version": "v0.287.0" @@ -1394,6 +1399,36 @@ "parent" ] }, + "resources.PostgresCatalog": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "catalog_id": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "create_database_if_missing": { + "$ref": "#/$defs/bool", + "x-since-version": "v1.0.0" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle", + "x-since-version": "v1.0.0" + }, + "postgres_database": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + } + }, + "additionalProperties": false, + "required": [ + "catalog_id", + "postgres_database" + ] + }, "resources.PostgresEndpoint": { "type": "object", "properties": { @@ -1433,6 +1468,10 @@ "$ref": "#/$defs/string", "x-since-version": "v0.287.0" }, + "replace_existing": { + "$ref": "#/$defs/bool", + "x-since-version": "v1.0.0" + }, "settings": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.EndpointSettings", "x-since-version": "v0.287.0" @@ -1461,7 +1500,8 @@ "x-since-version": "v0.290.0" }, "default_branch": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "default_endpoint_settings": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.ProjectDefaultEndpointSettings", @@ -1501,6 +1541,59 @@ "project_id" ] }, + "resources.PostgresSyncedTable": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "create_database_objects_if_missing": { + "$ref": "#/$defs/bool", + "x-since-version": "v1.0.0" + }, + "existing_pipeline_id": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "lifecycle": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle", + "x-since-version": "v1.0.0" + }, + "new_pipeline_spec": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.NewPipelineSpec", + "x-since-version": "v1.0.0" + }, + "postgres_database": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "primary_key_columns": { + "$ref": "#/$defs/slice/string", + "x-since-version": "v1.0.0" + }, + "scheduling_policy": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy", + "x-since-version": "v1.0.0" + }, + "source_table_full_name": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "synced_table_id": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "timeseries_key": { + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + } + }, + "additionalProperties": false, + "required": [ + "synced_table_id" + ] + }, "resources.QualityMonitor": { "type": "object", "properties": { @@ -1924,27 +2017,34 @@ "type": "object", "properties": { "budget_policy_id": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "endpoint_type": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/vectorsearch.EndpointType", + "x-since-version": "v0.298.0" }, "lifecycle": { - "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle" - }, - "min_qps": { - "$ref": "#/$defs/int64" + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.Lifecycle", + "x-since-version": "v0.298.0" }, "name": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "permissions": { - "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.Permission" + "$ref": "#/$defs/slice/github.com/databricks/cli/bundle/config/resources.Permission", + "x-since-version": "v0.298.0" + }, + "target_qps": { + "$ref": "#/$defs/int64", + "x-since-version": "v0.299.2" }, "usage_policy_id": { "$ref": "#/$defs/string", "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "doNotSuggest": true, + "x-since-version": "v0.298.0" } }, "additionalProperties": false, @@ -2482,6 +2582,11 @@ "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresBranch", "x-since-version": "v0.287.0" }, + "postgres_catalogs": { + "description": "The Postgres catalog definitions for the bundle, where each key is the name of the catalog. Each entry binds a Unity Catalog catalog to a Postgres database on a Lakebase Autoscaling branch.", + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresCatalog", + "x-since-version": "v1.0.0" + }, "postgres_endpoints": { "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresEndpoint", "x-since-version": "v0.287.0" @@ -2490,6 +2595,11 @@ "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresProject", "x-since-version": "v0.287.0" }, + "postgres_synced_tables": { + "description": "The Postgres synced table definitions for the bundle, where each key is the name of the synced table. Each entry continuously replicates a Unity Catalog Delta source table into a Postgres table on a Lakebase Autoscaling instance.", + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.PostgresSyncedTable", + "x-since-version": "v1.0.0" + }, "quality_monitors": { "description": "The quality monitor definitions for the bundle, where each key is the name of the quality monitor.", "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.QualityMonitor", @@ -2525,7 +2635,8 @@ "x-since-version": "v0.266.0" }, "vector_search_endpoints": { - "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint" + "$ref": "#/$defs/map/github.com/databricks/cli/bundle/config/resources.VectorSearchEndpoint", + "x-since-version": "v0.298.0" }, "volumes": { "description": "The volume definitions for the bundle, where each key is the name of the volume.", @@ -2704,7 +2815,7 @@ "x-since-version": "v0.229.0" }, "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", "x-since-version": "v0.285.0" }, @@ -2791,8 +2902,6 @@ "git_source": { "description": "Git repository to use as the source for the app deployment.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.GitSource", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.283.0" }, "mode": { @@ -2907,10 +3016,12 @@ "type": "object", "properties": { "name": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "permission": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourceAppAppPermission" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/apps.AppResourceAppAppPermission", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -3203,7 +3314,8 @@ "type": "string", "enum": [ "MEDIUM", - "LARGE" + "LARGE", + "LIQUID" ] }, "apps.ComputeState": { @@ -3344,13 +3456,16 @@ "type": "object", "properties": { "azure_cmk_access_connector_id": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "azure_cmk_managed_identity_id": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "azure_tenant_id": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false, @@ -3393,15 +3508,18 @@ "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" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/catalog.AzureEncryptionSettings", + "x-since-version": "v0.298.0" }, "azure_key_vault_key_id": { "description": "the AKV URL in Azure, null otherwise.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "customer_managed_key_id": { "description": "the CMK uuid in AWS and GCP, null otherwise.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -3702,7 +3820,21 @@ "CREATE_CLEAN_ROOM", "MODIFY_CLEAN_ROOM", "EXECUTE_CLEAN_ROOM_TASK", - "EXTERNAL_USE_SCHEMA" + "EXTERNAL_USE_SCHEMA", + "VIEW_OBJECT", + "MANAGE_GRANTS", + "INSERT", + "UPDATE", + "DELETE", + "VIEW_ADMIN_METADATA", + "VIEW_METADATA", + "USE_VOLUME", + "READ_METADATA", + "MANAGE_ACCESS", + "MANAGE_ACCESS_CONTROL", + "CREATE_SERVICE", + "CREATE_FEATURE", + "READ_FEATURE" ] }, "catalog.PrivilegeAssignment": { @@ -4134,9 +4266,17 @@ }, "additionalProperties": false }, + "compute.ConfidentialComputeType": { + "type": "string", + "description": "Confidential computing technology for GCP instances.\nAligns with gcloud's --confidential-compute-type flag and the REST API's\nconfidentialInstanceConfig.confidentialInstanceType field.\nSee: https://cloud.google.com/confidential-computing/confidential-vm/docs/create-a-confidential-vm-instance", + "enum": [ + "CONFIDENTIAL_COMPUTE_TYPE_NONE", + "SEV_SNP" + ] + }, "compute.DataSecurityMode": { "type": "string", - "description": "Data security mode decides what data governance model to use when accessing data\nfrom a cluster.\n\nThe following modes can only be used when `kind = CLASSIC_PREVIEW`.\n* `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration.\n* `DATA_SECURITY_MODE_STANDARD`: Alias for `USER_ISOLATION`.\n* `DATA_SECURITY_MODE_DEDICATED`: Alias for `SINGLE_USER`.\n\nThe following modes can be used regardless of `kind`.\n* `NONE`: No security isolation for multiple users sharing the cluster. Data governance features are not available in this mode.\n* `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.\n* `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.\n\nThe following modes are deprecated starting with Databricks Runtime 15.0 and\nwill be removed for future Databricks Runtime versions:\n\n* `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters.\n* `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters.\n* `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters.\n* `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled.", + "description": "Data security mode decides what data governance model to use when accessing data\nfrom a cluster.\n\n* `DATA_SECURITY_MODE_AUTO`: Databricks will choose the most appropriate access mode depending on your compute configuration.\n* `DATA_SECURITY_MODE_STANDARD`: 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.\n* `DATA_SECURITY_MODE_DEDICATED`: 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.\n\nThe following modes are legacy aliases for the above modes:\n\n* `USER_ISOLATION`: Legacy alias for `DATA_SECURITY_MODE_STANDARD`.\n* `SINGLE_USER`: Legacy alias for `DATA_SECURITY_MODE_DEDICATED`.\n\nThe following modes are deprecated starting with Databricks Runtime 15.0 and\nwill be removed for future Databricks Runtime versions:\n\n* `LEGACY_TABLE_ACL`: This mode is for users migrating from legacy Table ACL clusters.\n* `LEGACY_PASSTHROUGH`: This mode is for users migrating from legacy Passthrough on high concurrency clusters.\n* `LEGACY_SINGLE_USER`: This mode is for users migrating from legacy Passthrough on standard clusters.\n* `LEGACY_SINGLE_USER_STANDARD`: This mode provides a way that doesn’t have UC nor passthrough enabled.", "enum": [ "NONE", "SINGLE_USER", @@ -4206,10 +4346,10 @@ }, "compute.Environment": { "type": "object", - "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.", + "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP'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 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", + "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.\nFor more information about Databricks-provided base environments, see the\n[list workspace base environments](:method:Environments/ListWorkspaceBaseEnvironments) API.\nFor more information, see", "$ref": "#/$defs/string", "x-since-version": "v0.289.0" }, @@ -4250,6 +4390,13 @@ "$ref": "#/$defs/int", "x-since-version": "v0.229.0" }, + "confidential_compute_type": { + "description": "The confidential computing technology for this cluster's instances.\nCurrently only SEV_SNP is supported, and only on N2D instance types.\nWhen not set, no confidential computing is applied.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/compute.ConfidentialComputeType", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, + "x-since-version": "v0.299.2" + }, "first_on_demand": { "description": "The first `first_on_demand` nodes of the cluster will be placed on on-demand instances.\nThis value should be greater than 0, to make sure the cluster driver node is placed on an\non-demand instance. If this value is greater than or equal to the current cluster size, all\nnodes will be placed on on-demand instances. If this value is less than the current cluster\nsize, `first_on_demand` nodes will be placed on on-demand instances and the remainder will\nbe placed on `availability` instances. Note that this value does not affect\ncluster size and cannot currently be mutated over the lifetime of a cluster.", "$ref": "#/$defs/int", @@ -4309,7 +4456,8 @@ "description": "HardwareAcceleratorType: The type of hardware accelerator to use for compute workloads.\nNOTE: This enum is referenced and is intended to be used by other Databricks services\nthat need to specify hardware accelerator requirements for AI compute workloads.", "enum": [ "GPU_1xA10", - "GPU_8xH100" + "GPU_8xH100", + "GPU_1xH100" ] }, "compute.InitScriptInfo": { @@ -4660,7 +4808,8 @@ "DELETING", "STOPPED", "UPDATING", - "FAILING_OVER" + "FAILING_OVER", + "UPGRADING" ] }, "database.DeltaTableSyncInfo": { @@ -4854,7 +5003,8 @@ "CAN_MONITOR", "CAN_CREATE", "CAN_MONITOR_ONLY", - "CAN_CREATE_APP" + "CAN_CREATE_APP", + "UNSPECIFIED" ] }, "jobs.AlertTask": { @@ -5717,9 +5867,25 @@ "type": "object", "properties": { "full_refresh": { - "description": "If true, triggers a full refresh on the delta live table.", + "description": "If true, triggers a full refresh on the spark declarative pipeline.", "$ref": "#/$defs/bool", "x-since-version": "v0.229.0" + }, + "full_refresh_selection": { + "description": "A list of tables to update with fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_flow_selection": { + "description": "Flow names to selectively refresh. These are unioned with other selective refresh\noptions (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_selection": { + "description": "A list of tables to update without fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "reset_checkpoint_selection": { + "description": "A list of streaming flows to reset checkpoints without clearing data.", + "$ref": "#/$defs/slice/string" } }, "additionalProperties": false @@ -5728,14 +5894,30 @@ "type": "object", "properties": { "full_refresh": { - "description": "If true, triggers a full refresh on the delta live table.", + "description": "If true, triggers a full refresh on the spark declarative pipeline.", "$ref": "#/$defs/bool", "x-since-version": "v0.229.0" }, + "full_refresh_selection": { + "description": "A list of tables to update with fullRefresh.", + "$ref": "#/$defs/slice/string" + }, "pipeline_id": { "description": "The full name of the pipeline task to execute.", "$ref": "#/$defs/string", "x-since-version": "v0.229.0" + }, + "refresh_flow_selection": { + "description": "Flow names to selectively refresh. These are unioned with other selective refresh\noptions (refresh_selection, full_refresh_selection) to determine the final set of flows to refresh.", + "$ref": "#/$defs/slice/string" + }, + "refresh_selection": { + "description": "A list of tables to update without fullRefresh.", + "$ref": "#/$defs/slice/string" + }, + "reset_checkpoint_selection": { + "description": "A list of streaming flows to reset checkpoints without clearing data.", + "$ref": "#/$defs/slice/string" } }, "additionalProperties": false, @@ -5831,6 +6013,32 @@ }, "additionalProperties": false }, + "jobs.PythonOperatorTask": { + "type": "object", + "properties": { + "main": { + "description": "Fully qualified name of the main class or function.\nFor example, `my_project.my_function` or `my_project.MyOperator`.", + "$ref": "#/$defs/string" + }, + "parameters": { + "description": "An ordered list of task parameters.\nTODO(JOBS-30885): Add limits for parameters.", + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter" + } + }, + "additionalProperties": false + }, + "jobs.PythonOperatorTaskParameter": { + "type": "object", + "properties": { + "name": { + "$ref": "#/$defs/string" + }, + "value": { + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, "jobs.PythonWheelTask": { "type": "object", "properties": { @@ -6335,8 +6543,6 @@ "disabled": { "description": "An optional flag to disable the task. If set to true, the task will not run even if it is part of a job.", "$ref": "#/$defs/bool", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.271.0" }, "email_notifications": { @@ -6414,6 +6620,12 @@ "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PowerBiTask", "x-since-version": "v0.248.0" }, + "python_operator_task": { + "description": "The task runs a Python operator task.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTask", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, "python_wheel_task": { "description": "The task runs a Python wheel when the `python_wheel_task` field is present.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonWheelTask", @@ -6712,6 +6924,18 @@ "enabled" ] }, + "pipelines.ConfluenceConnectorOptions": { + "type": "object", + "description": "Confluence specific options for ingestion", + "properties": { + "include_confluence_spaces": { + "description": "(Optional) Spaces to filter Confluence data on", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + } + }, + "additionalProperties": false + }, "pipelines.ConnectionParameters": { "type": "object", "properties": { @@ -6729,27 +6953,72 @@ "type": "object", "description": "Wrapper message for source-specific options to support multiple connector types", "properties": { + "confluence_options": { + "description": "Confluence specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ConfluenceConnectorOptions", + "x-since-version": "v0.299.2" + }, "gdrive_options": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptions", "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "doNotSuggest": true, + "x-since-version": "v0.298.0" }, "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, + "x-since-version": "v0.298.0" + }, + "jira_options": { + "description": "Jira specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.JiraConnectorOptions", + "x-since-version": "v0.299.2" + }, + "kafka_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.KafkaOptions", + "x-databricks-preview": "PRIVATE", "doNotSuggest": true }, + "meta_ads_options": { + "description": "Meta Marketing (Meta Ads) specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.MetaMarketingOptions", + "x-since-version": "v0.299.2" + }, + "outlook_options": { + "description": "Outlook specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, + "x-since-version": "v0.299.2" + }, "sharepoint_options": { "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptions", "x-databricks-preview": "PRIVATE", - "doNotSuggest": true + "doNotSuggest": true, + "x-since-version": "v0.298.0" + }, + "smartsheet_options": { + "description": "Smartsheet specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SmartsheetOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, + "x-since-version": "v0.299.2" }, "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 + "doNotSuggest": true, + "x-since-version": "v0.298.0" + }, + "zendesk_support_options": { + "description": "Zendesk Support specific options for ingestion", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.ZendeskSupportOptions", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, + "x-since-version": "v0.299.2" } }, "additionalProperties": false @@ -6849,15 +7118,18 @@ "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -6866,43 +7138,54 @@ "type": "object", "properties": { "corrupt_record_column": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "file_filters": { "description": "Generic options", - "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter" + "$ref": "#/$defs/slice/github.com/databricks/databricks-sdk-go/service/pipelines.FileFilter", + "x-since-version": "v0.298.0" }, "format": { "description": "required for TableSpec", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFileFormat" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsFileFormat", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/map/string", + "x-since-version": "v0.298.0" }, "ignore_corrupt_files": { - "$ref": "#/$defs/bool" + "$ref": "#/$defs/bool", + "x-since-version": "v0.298.0" }, "infer_column_types": { - "$ref": "#/$defs/bool" + "$ref": "#/$defs/bool", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/bool", + "x-since-version": "v0.298.0" }, "rescued_data_column": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "single_variant_column": { - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -6917,7 +7200,8 @@ "EXCEL", "PARQUET", "AVRO", - "ORC" + "ORC", + "FILE" ] }, "pipelines.FileIngestionOptionsSchemaEvolutionMode": { @@ -6958,21 +7242,35 @@ }, "additionalProperties": false }, + "pipelines.GoogleAdsConfig": { + "type": "object", + "properties": { + "manager_account_id": { + "description": "(Required) Manager Account ID (also called MCC Account ID) used to list and access\ncustomer accounts under this manager account. This is required for fetching the list\nof customer accounts during source selection.\nIf the same field is also set in the object-level GoogleAdsOptions (connector_options),\nthe object-level value takes precedence over this top-level config.", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + } + }, + "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" + "$ref": "#/$defs/int", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false, @@ -6984,14 +7282,17 @@ "type": "object", "properties": { "entity_type": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoogleDriveEntityType" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleDriveOptionsGoogleDriveEntityType", + "x-since-version": "v0.298.0" }, "file_ingestion_options": { - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions", + "x-since-version": "v0.298.0" }, "url": { "description": "Google Drive URL.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -7001,7 +7302,9 @@ "enum": [ "FILE", "FILE_METADATA", - "PERMISSION" + "PERMISSION", + "FILE_PERMISSION", + "GROUP_MEMBERSHIP" ] }, "pipelines.IngestionConfig": { @@ -7100,8 +7403,6 @@ "ingest_from_uc_foreign_catalog": { "description": "Immutable. If set to true, the pipeline will ingest tables from the\nUC foreign catalogs directly without the need to specify a UC connection or ingestion gateway.\nThe `source_catalog` fields in objects of IngestionConfig are interpreted as\nthe UC foreign catalogs to ingest from.", "$ref": "#/$defs/bool", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.279.0" }, "ingestion_gateway_id": { @@ -7140,22 +7441,16 @@ "cursor_columns": { "description": "The names of the monotonically increasing columns in the source table that are used to enable\nthe table to be read and ingested incrementally through structured streaming.\nThe columns are allowed to have repeated values but have to be non-decreasing.\nIf the source data is merged into the destination (e.g., using SCD Type 1 or Type 2), these\ncolumns will implicitly define the `sequence_by` behavior. You can still explicitly set\n`sequence_by` to override this default.", "$ref": "#/$defs/slice/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.264.0" }, "deletion_condition": { "description": "Specifies a SQL WHERE condition that specifies that the source row has been deleted.\nThis is sometimes referred to as \"soft-deletes\".\nFor example: \"Operation = 'DELETE'\" or \"is_deleted = true\".\nThis field is orthogonal to `hard_deletion_sync_interval_in_seconds`,\none for soft-deletes and the other for hard-deletes.\nSee also the hard_deletion_sync_min_interval_in_seconds field for\nhandling of \"hard deletes\" where the source rows are physically removed from the table.", "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.264.0" }, "hard_deletion_sync_min_interval_in_seconds": { "description": "Specifies the minimum interval (in seconds) between snapshots on primary keys\nfor detecting and synchronizing hard deletions—i.e., rows that have been\nphysically removed from the source table.\nThis interval acts as a lower bound. If ingestion runs less frequently than\nthis value, hard deletion synchronization will align with the actual ingestion\nfrequency instead of happening more often.\nIf not set, hard deletion synchronization via snapshots is disabled.\nThis field is mutable and can be updated without triggering a full snapshot.", "$ref": "#/$defs/int64", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.264.0" } }, @@ -7207,6 +7502,8 @@ "enum": [ "MYSQL", "POSTGRESQL", + "REDSHIFT", + "SQLDW", "SQLSERVER", "SALESFORCE", "BIGQUERY", @@ -7220,13 +7517,219 @@ "SHAREPOINT", "DYNAMICS365", "GOOGLE_DRIVE", + "JIRA", + "CONFLUENCE", + "META_MARKETING", + "GOOGLE_ADS", + "TIKTOK_ADS", + "SALESFORCE_MARKETING_CLOUD", + "HUBSPOT", + "WORKDAY_HCM", + "GUIDEWIRE", + "ZENDESK", + "COMMUNITY", + "SLACK_AUDIT_LOGS", + "KAFKA", + "CROWDSTRIKE_EVENT_STREAM", + "WORKDAY_ACTIVITY_LOGGING", + "AKAMAI_WAF", + "VEEVA", + "VEEVA_VAULT", + "M365_AUDIT_LOGS", + "OKTA_SYSTEM_LOGS", + "ONE_PASSWORD_EVENT_LOGS", + "PROOFPOINT_SIEM", + "WIZ_AUDIT_LOGS", + "GITHUB", + "OUTLOOK", + "SMARTSHEET", + "MICROSOFT_TEAMS", + "ADOBE_CAMPAIGNS", + "LINKEDIN_ADS", + "X_ADS", + "BING_ADS", + "GOOGLE_SEARCH_CONSOLE", + "PINTEREST_ADS", + "REDDIT_ADS", + "PENDO", + "API_SOURCE", + "SLACK_ACCESS_AND_INTEGRATION_LOGS", + "ORACLE_FUSION_CLOUD", + "RABBITMQ", + "GOOGLE_ANALYTICS", + "AMPLITUDE", + "NOTION", + "ADP_WORKFORCE_NOW", + "SAS", + "GONG", + "SALESLOFT", + "GOOGLE_WORKSPACE", + "SHOPIFY", + "ORACLE_ELOQUA", + "EPIC_CLARITY", + "LINEAR", + "APPLE_APP_STORE", + "SPLUNK", + "GITLAB", + "ZOOM_LOGS", + "MONDAY_COM", + "AIRTABLE", + "MICROSOFT_ENTRA_ID", + "PAGERDUTY", + "APPFIGURES", + "ADOBE_COMMERCE", + "QUICKBOOKS", + "SQUARE", + "ZOHO_BOOKS", + "SNAPCHAT_ADS", + "GENESYS", + "SAP_SUCCESSFACTORS", + "YOUTUBE_ANALYTICS", + "CERIDIAN_DAYFORCE", + "FRESHSERVICE", + "SENDGRID", + "AZURE_MONITOR_LOGS", + "ATLASSIAN_ORGANIZATION", + "HIBOB", + "APPLE_SEARCH_ADS", + "AWIN", + "DELIGHTED", + "FRONT", + "GURU", + "PARTNERSTACK", + "MARKETO", + "AHA", + "NETSKOPE_LOGS", "FOREIGN_CATALOG" ] }, + "pipelines.JiraConnectorOptions": { + "type": "object", + "description": "Jira specific options for ingestion", + "properties": { + "include_jira_spaces": { + "description": "(Optional) Projects to filter Jira data on", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + } + }, + "additionalProperties": false + }, + "pipelines.JsonTransformerOptions": { + "type": "object", + "properties": { + "as_variant": { + "description": "Parse the entire value as a single Variant column.", + "$ref": "#/$defs/bool" + }, + "schema": { + "description": "Inline schema string for JSON parsing (Spark DDL format).", + "$ref": "#/$defs/string" + }, + "schema_evolution_mode": { + "description": "(Optional) Schema evolution mode for schema inference.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptionsSchemaEvolutionMode" + }, + "schema_file_path": { + "description": "Path to a schema file (.ddl).", + "$ref": "#/$defs/string" + }, + "schema_hints": { + "description": "(Optional) Schema hints as a comma-separated string of \"column_name type\" pairs.", + "$ref": "#/$defs/string" + } + }, + "additionalProperties": false + }, + "pipelines.KafkaOptions": { + "type": "object", + "properties": { + "client_config": { + "description": "Undocumented backdoor mechanism for overriding parameters\nto pass to the Kafka client.\nThis is not supported and may break at any time.", + "$ref": "#/$defs/map/string", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "key_transformer": { + "description": "(Optional) Transformer for the message key.\nIf not specified, the key is left as raw bytes.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.Transformer" + }, + "max_offsets_per_trigger": { + "description": "Internal option to control the maximum number of offsets to process per trigger.", + "$ref": "#/$defs/int64", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true + }, + "starting_offset": { + "description": "(Optional) Where to begin reading when no checkpoint exists.\nValid values: \"latest\" and \"earliest\". Defaults to \"latest\".", + "$ref": "#/$defs/string" + }, + "topic_pattern": { + "description": "Java regex pattern to subscribe to matching topics.\nOnly one of topics or topic_pattern must be specified.", + "$ref": "#/$defs/string" + }, + "topics": { + "description": "Topics to subscribe to.\nOnly one of topics or topic_pattern must be specified.", + "$ref": "#/$defs/slice/string" + }, + "value_transformer": { + "description": "(Optional) Transformer for the message value.\nIf not specified, the value is left as raw bytes.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.Transformer" + } + }, + "additionalProperties": false + }, "pipelines.ManualTrigger": { "type": "object", "additionalProperties": false }, + "pipelines.MetaMarketingOptions": { + "type": "object", + "description": "Meta Marketing (Meta Ads) specific options for ingestion", + "properties": { + "action_attribution_windows": { + "description": "(Optional) Action attribution windows for insights reporting (e.g. \"28d_click\", \"1d_view\")", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "action_breakdowns": { + "description": "(Optional) Action breakdowns to configure for data aggregation", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "action_report_time": { + "description": "(Optional) Timing used to report action statistics (impression, conversion, mixed, or lifetime)", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + }, + "breakdowns": { + "description": "(Optional) Breakdowns to configure for data aggregation", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "custom_insights_lookback_window": { + "description": "(Optional) Window in days to revisit data during sync to capture\nupdated conversion data from the API.", + "$ref": "#/$defs/int", + "x-since-version": "v0.299.2" + }, + "level": { + "description": "(Optional) Granularity of data to pull (account, ad, adset, campaign)", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + }, + "start_date": { + "description": "(Optional) Start date in yyyy-MM-dd format (e.g. 2025-01-15). Data added\nafter this date will be ingested", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + }, + "time_increment": { + "description": "(Optional) Value in string by which to aggregate statistics (can take all_days, monthly or number of days)", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + } + }, + "additionalProperties": false + }, "pipelines.NotebookLibrary": { "type": "object", "properties": { @@ -7279,6 +7782,87 @@ "start_hour" ] }, + "pipelines.OutlookAttachmentMode": { + "type": "string", + "description": "Attachment behavior mode for Outlook ingestion", + "enum": [ + "ALL", + "NON_INLINE_ONLY", + "INLINE_ONLY", + "NONE" + ] + }, + "pipelines.OutlookBodyFormat": { + "type": "string", + "description": "Body format for Outlook email content", + "enum": [ + "TEXT_HTML", + "TEXT_PLAIN" + ] + }, + "pipelines.OutlookOptions": { + "type": "object", + "description": "Outlook specific options for ingestion", + "properties": { + "attachment_mode": { + "description": "(Optional) Controls which attachments to ingest.\nIf not specified, defaults to ALL.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookAttachmentMode", + "x-since-version": "v0.299.2" + }, + "body_format": { + "description": "(Optional) Defines how the body_content column is populated.\nTEXT_HTML: Preserves full formatting, links, and styling.\nTEXT_PLAIN: Converts body to plain text. Recommended for AI/RAG pipelines to reduce token usage and noise.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.OutlookBodyFormat", + "x-since-version": "v0.299.2" + }, + "folder_filter": { + "description": "Deprecated. Use include_folders instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "x-since-version": "v0.299.2", + "deprecated": true + }, + "include_folders": { + "description": "(Optional) Filter mail folders to include in the sync.\nIf not specified, all folders will be synced.\nExamples: Inbox, Sent Items, Custom_Folder\nFilter semantics: OR between different folders.", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "include_mailboxes": { + "description": "(Optional) List of mailboxes to sync (e.g. mailbox email addresses or identifiers).\nIf not specified, all accessible mailboxes are ingested.\nFilter semantics: OR between different mailboxes.", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "include_senders": { + "description": "(Optional) Filter emails by sender address. Uses exact email match.\nExamples: user@vendor.com, alerts@system.io, noreply@company.com\nIf not specified, emails from all senders will be synced.\nFilter semantics: OR between different senders.", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "include_subjects": { + "description": "(Optional) Filter emails by subject line. Values ending with \"*\" use prefix match (subject starts with\nthe part before \"*\"); otherwise substring match (subject contains the value).\nExamples: \"Invoice\" (substring), \"Re:*\" (prefix), \"Support Ticket\", \"URGENT*\"\nIf not specified, emails with all subjects will be synced.\nFilter semantics: OR between different subjects.", + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.299.2" + }, + "sender_filter": { + "description": "Deprecated. Use include_senders instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "x-since-version": "v0.299.2", + "deprecated": true + }, + "start_date": { + "description": "(Optional) Start date for the initial sync in YYYY-MM-DD format.\nFormat: YYYY-MM-DD (e.g., 2024-01-01)\nThis determines the earliest date from which to sync historical data.\nIf not specified, complete history is ingested.", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + }, + "subject_filter": { + "description": "Deprecated. Use include_subjects instead.", + "$ref": "#/$defs/slice/string", + "deprecationMessage": "This field is deprecated", + "x-since-version": "v0.299.2", + "deprecated": true + } + }, + "additionalProperties": false + }, "pipelines.PathPattern": { "type": "object", "properties": { @@ -7511,7 +8095,7 @@ }, "pipelines.PipelinesEnvironment": { "type": "object", - "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 dependencies are supported.", + "description": "The environment entity used to preserve serverless environment side panel, jobs' environment for non-notebook task, and SDP's environment for classic and serverless pipelines.\nIn this minimal environment spec, only pip dependencies are supported.", "properties": { "dependencies": { "description": "List of pip dependencies, as supported by the version of pip in this environment.\nEach dependency is a pip requirement file line https://pip.pypa.io/en/stable/reference/requirements-file-format/\nAllowed dependency could be \u003crequirement specifier\u003e, \u003carchive url/path\u003e, \u003clocal project path\u003e(WSFS or Volumes in Databricks), \u003cvcs project url\u003e", @@ -7640,8 +8224,7 @@ "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 + "x-since-version": "v0.298.0" }, "destination_catalog": { "description": "Required. Destination catalog to store tables.", @@ -7681,15 +8264,18 @@ "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" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SharepointOptionsSharepointEntityType", + "x-since-version": "v0.298.0" }, "file_ingestion_options": { "description": "(Optional) File ingestion options for processing files.", - "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.FileIngestionOptions", + "x-since-version": "v0.298.0" }, "url": { "description": "Required. The SharePoint URL.", - "$ref": "#/$defs/string" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -7700,9 +8286,23 @@ "FILE", "FILE_METADATA", "PERMISSION", - "LIST" + "LIST", + "FILE_PERMISSION", + "GROUP_MEMBERSHIP" ] }, + "pipelines.SmartsheetOptions": { + "type": "object", + "description": "Smartsheet specific options for ingestion", + "properties": { + "enforce_schema": { + "description": "(Optional) When true, maps each column to its Smartsheet-declared type (Text/Number/Date/\nCheckbox/etc.). Cells that do not conform to the declared type are set to NULL.\nWhen false, all columns land as STRING. Use false for sheets with irregular data or columns\nthat frequently violate their own declared type.\nIf not specified, defaults to true.", + "$ref": "#/$defs/bool", + "x-since-version": "v0.299.2" + } + }, + "additionalProperties": false + }, "pipelines.SourceCatalogConfig": { "type": "object", "description": "SourceCatalogConfig contains catalog-level custom configuration parameters for each source", @@ -7727,6 +8327,12 @@ "description": "Catalog-level source configuration parameters", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.SourceCatalogConfig", "x-since-version": "v0.267.0" + }, + "google_ads_config": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.GoogleAdsConfig", + "x-databricks-preview": "PRIVATE", + "doNotSuggest": true, + "x-since-version": "v0.299.2" } }, "additionalProperties": false @@ -7737,8 +8343,7 @@ "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 + "x-since-version": "v0.298.0" }, "destination_catalog": { "description": "Required. Destination catalog to store table.", @@ -7809,15 +8414,11 @@ "query_based_connector_config": { "description": "Configurations that are only applicable for query-based ingestion connectors.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.IngestionPipelineDefinitionTableSpecificConfigQueryBasedConnectorConfig", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.264.0" }, "row_filter": { "description": "(Optional, Immutable) The row filter condition to be applied to the table.\nIt must not contain the WHERE keyword, only the actual filter condition.\nIt must be in DBSQL format.", "$ref": "#/$defs/string", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.283.0" }, "salesforce_include_formula_fields": { @@ -7830,8 +8431,6 @@ "scd_type": { "description": "The SCD type to use to ingest the table.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TableSpecificConfigScdType", - "x-databricks-preview": "PRIVATE", - "doNotSuggest": true, "x-since-version": "v0.229.0" }, "sequence_by": { @@ -7863,31 +8462,38 @@ "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" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokDataLevel", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/int", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/slice/string", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/bool", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TikTokAdsOptionsTikTokReportType", + "x-since-version": "v0.298.0" }, "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" + "$ref": "#/$defs/string", + "x-since-version": "v0.298.0" } }, "additionalProperties": false @@ -7914,6 +8520,41 @@ "GMV_MAX" ] }, + "pipelines.Transformer": { + "type": "object", + "description": "Specifies how to transform binary data into structured data.", + "properties": { + "format": { + "description": "Required: the wire format of the data.", + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.TransformerFormat" + }, + "json_options": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/pipelines.JsonTransformerOptions" + } + }, + "additionalProperties": false + }, + "pipelines.TransformerFormat": { + "type": "string", + "enum": [ + "STRING", + "JSON", + "AVRO", + "PROTOBUF" + ] + }, + "pipelines.ZendeskSupportOptions": { + "type": "object", + "description": "Zendesk Support specific options for ingestion", + "properties": { + "start_date": { + "description": "(Optional) Start date in YYYY-MM-DD format for the initial sync.\nThis determines the earliest date from which to sync historical data.", + "$ref": "#/$defs/string", + "x-since-version": "v0.299.2" + } + }, + "additionalProperties": false + }, "postgres.EndpointGroupSpec": { "type": "object", "properties": { @@ -7959,6 +8600,27 @@ "ENDPOINT_TYPE_READ_ONLY" ] }, + "postgres.NewPipelineSpec": { + "type": "object", + "properties": { + "budget_policy_id": { + "description": "Budget policy to set on the newly created pipeline.", + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "storage_catalog": { + "description": "UC catalog for the pipeline to store intermediate files (checkpoints, event logs etc).\nThis needs to be a standard catalog where the user has permissions to create Delta tables.", + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + }, + "storage_schema": { + "description": "UC schema for the pipeline to store intermediate files (checkpoints, event logs etc).\nThis needs to be in the standard catalog where the user has permissions to create Delta tables.", + "$ref": "#/$defs/string", + "x-since-version": "v1.0.0" + } + }, + "additionalProperties": false + }, "postgres.ProjectCustomTag": { "type": "object", "properties": { @@ -7990,7 +8652,7 @@ "x-since-version": "v0.287.0" }, "no_suspension": { - "description": "When set to true, explicitly disables automatic suspension (never suspend).\nShould be set to true when provided.", + "description": "When set to true, explicitly disables automatic suspension (never suspend).\nShould be set to true when provided.\nMutually exclusive with `suspend_timeout_duration`. When updating, use `spec.project_default_settings.suspension` in the update_mask.", "$ref": "#/$defs/bool", "x-since-version": "v0.287.0" }, @@ -8000,13 +8662,22 @@ "x-since-version": "v0.287.0" }, "suspend_timeout_duration": { - "description": "Duration of inactivity after which the compute endpoint is automatically suspended.\nIf specified should be between 60s and 604800s (1 minute to 1 week).", + "description": "Duration of inactivity after which the compute endpoint is automatically suspended.\nIf specified should be between 60s and 604800s (1 minute to 1 week).\nMutually exclusive with `no_suspension`. When updating, use `spec.project_default_settings.suspension` in the update_mask.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/common/types/duration.Duration", "x-since-version": "v0.287.0" } }, "additionalProperties": false }, + "postgres.SyncedTableSyncedTableSpecSyncedTableSchedulingPolicy": { + "type": "string", + "description": "Scheduling policy of the synced table's underlying pipeline.", + "enum": [ + "CONTINUOUS", + "TRIGGERED", + "SNAPSHOT" + ] + }, "serving.Ai21LabsConfig": { "type": "object", "properties": { @@ -8301,6 +8972,7 @@ }, "serving.AutoCaptureConfigInput": { "type": "object", + "description": "Deprecated: legacy inference table configuration. Please use AI Gateway inference tables instead.\nSee https://docs.databricks.com/aws/en/ai-gateway/inference-tables.", "properties": { "catalog_name": { "description": "The name of the catalog in Unity Catalog. NOTE: On update, you cannot change the catalog name if the inference table is already enabled.", @@ -8431,9 +9103,11 @@ "type": "object", "properties": { "auto_capture_config": { - "description": "Configuration for Inference Tables which automatically logs requests and responses to Unity Catalog.\nNote: this field is deprecated for creating new provisioned throughput endpoints,\nor updating existing provisioned throughput endpoints that never have inference table configured;\nin these cases please use AI Gateway to manage inference tables.", + "description": "Configuration for legacy Inference Tables which automatically log requests and responses to Unity\nCatalog.\nDeprecated: please use AI Gateway inference tables instead. See\nhttps://docs.databricks.com/aws/en/ai-gateway/inference-tables.", "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/serving.AutoCaptureConfigInput", - "x-since-version": "v0.229.0" + "deprecationMessage": "This field is deprecated", + "x-since-version": "v0.229.0", + "deprecated": true }, "served_entities": { "description": "The list of served entities under the serving endpoint config.", @@ -8907,7 +9581,8 @@ "GPU_MEDIUM", "GPU_SMALL", "GPU_LARGE", - "MULTIGPU_MEDIUM" + "MULTIGPU_MEDIUM", + "GPU_XLARGE" ] }, "serving.ServingEndpointPermissionLevel": { @@ -8927,7 +9602,8 @@ "GPU_MEDIUM", "GPU_SMALL", "GPU_LARGE", - "MULTIGPU_MEDIUM" + "MULTIGPU_MEDIUM", + "GPU_XLARGE" ] }, "serving.TrafficConfig": { @@ -9151,7 +9827,8 @@ "enum": [ "TYPE_UNSPECIFIED", "CLASSIC", - "PRO" + "PRO", + "REYDEN" ] }, "sql.CronSchedule": { @@ -9235,7 +9912,8 @@ "description": "Type of endpoint.", "enum": [ "STORAGE_OPTIMIZED", - "STANDARD" + "STANDARD", + "STANDARD_ON_ORION" ] }, "workspace.AzureKeyVaultSecretScopeMetadata": { @@ -9368,6 +10046,12 @@ "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresBranch" } }, + "resources.PostgresCatalog": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresCatalog" + } + }, "resources.PostgresEndpoint": { "type": "object", "additionalProperties": { @@ -9380,6 +10064,12 @@ "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresProject" } }, + "resources.PostgresSyncedTable": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config/resources.PostgresSyncedTable" + } + }, "resources.QualityMonitor": { "type": "object", "additionalProperties": { @@ -9661,6 +10351,12 @@ "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PowerBiTable" } }, + "jobs.PythonOperatorTaskParameter": { + "type": "array", + "items": { + "$ref": "#/$defs/github.com/databricks/databricks-sdk-go/service/jobs.PythonOperatorTaskParameter" + } + }, "jobs.SqlTaskSubscription": { "type": "array", "items": { diff --git a/bundle/statemgmt/state_load.go b/bundle/statemgmt/state_load.go index 3345792c295..573c69126c2 100644 --- a/bundle/statemgmt/state_load.go +++ b/bundle/statemgmt/state_load.go @@ -9,9 +9,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/bundle/config/engine" "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/bundle/deploy/terraform" "github.com/databricks/cli/bundle/statemgmt/resourcestate" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" @@ -26,8 +24,8 @@ type ( const ErrorOnEmptyState LoadMode = 0 type load struct { - modes []LoadMode - engine engine.EngineType + state ExportedResourcesMap + modes []LoadMode } func (l *load) Name() string { @@ -35,27 +33,16 @@ func (l *load) Name() string { } func (l *load) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - var err error - var state ExportedResourcesMap - - if l.engine.IsDirect() { - state = b.DeploymentBundle.ExportState(ctx) - } else { - var err error - state, err = terraform.ParseResourcesState(ctx, b) - if err != nil { - return diag.FromErr(err) - } - } + return applyState(ctx, b, l.state, l.modes) +} - err = l.validateState(state) - if err != nil { +// applyState merges the exported resource state into the bundle configuration. +func applyState(ctx context.Context, b *bundle.Bundle, state ExportedResourcesMap, modes []LoadMode) diag.Diagnostics { + if err := validateLoadedState(state, modes); err != nil { return diag.FromErr(err) } - // Merge state into configuration. - err = StateToBundle(ctx, state, &b.Config) - if err != nil { + if err := StateToBundle(ctx, state, &b.Config); err != nil { return diag.FromErr(err) } @@ -160,14 +147,14 @@ func StateToBundle(ctx context.Context, state ExportedResourcesMap, config *conf }) } -func (l *load) validateState(state ExportedResourcesMap) error { - if len(state) == 0 && slices.Contains(l.modes, ErrorOnEmptyState) { +func validateLoadedState(state ExportedResourcesMap, modes []LoadMode) error { + if len(state) == 0 && slices.Contains(modes, ErrorOnEmptyState) { return errors.New("resource not found or not yet deployed. Did you forget to run 'databricks bundle deploy'?") } - return nil } -func Load(engine engine.EngineType, modes ...LoadMode) bundle.Mutator { - return &load{modes: modes, engine: engine} +// Load returns a mutator that merges the provided resource state into the bundle configuration. +func Load(state ExportedResourcesMap, modes ...LoadMode) bundle.Mutator { + return &load{state: state, modes: modes} } diff --git a/bundle/statemgmt/state_load_test.go b/bundle/statemgmt/state_load_test.go index 34c4fa4f5aa..000a753fbc3 100644 --- a/bundle/statemgmt/state_load_test.go +++ b/bundle/statemgmt/state_load_test.go @@ -49,6 +49,8 @@ func TestStateToBundleEmptyLocalResources(t *testing.T) { "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.postgres_catalogs.test_postgres_catalog": {ID: "catalogs/test_catalog"}, + "resources.postgres_synced_tables.test_postgres_synced_table": {ID: "synced_tables/main.public.test_synced_table"}, "resources.vector_search_endpoints.test_vector_search_endpoint": {ID: "vs-endpoint-1"}, } err := StateToBundle(t.Context(), state, &config) @@ -118,6 +120,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, "catalogs/test_catalog", config.Resources.PostgresCatalogs["test_postgres_catalog"].ID) + assert.Equal(t, resources.ModifiedStatusDeleted, config.Resources.PostgresCatalogs["test_postgres_catalog"].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) @@ -292,6 +297,20 @@ func TestStateToBundleEmptyRemoteResources(t *testing.T) { }, }, }, + PostgresCatalogs: map[string]*resources.PostgresCatalog{ + "test_postgres_catalog": { + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "test_catalog", + }, + }, + }, + PostgresSyncedTables: map[string]*resources.PostgresSyncedTable{ + "test_postgres_synced_table": { + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "main.public.test_synced_table", + }, + }, + }, VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ "test_vector_search_endpoint": { CreateEndpoint: vectorsearch.CreateEndpoint{ @@ -374,6 +393,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.PostgresCatalogs["test_postgres_catalog"].ID) + assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.PostgresCatalogs["test_postgres_catalog"].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) @@ -661,6 +683,25 @@ func TestStateToBundleModifiedResources(t *testing.T) { }, }, }, + PostgresCatalogs: map[string]*resources.PostgresCatalog{ + "test_postgres_catalog": { + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "test_catalog", + }, + }, + "test_postgres_catalog_new": { + PostgresCatalogConfig: resources.PostgresCatalogConfig{ + CatalogId: "test_catalog_new", + }, + }, + }, + PostgresSyncedTables: map[string]*resources.PostgresSyncedTable{ + "test_postgres_synced_table": { + PostgresSyncedTableConfig: resources.PostgresSyncedTableConfig{ + SyncedTableId: "main.public.test_synced_table", + }, + }, + }, VectorSearchEndpoints: map[string]*resources.VectorSearchEndpoint{ "test_vector_search_endpoint": { CreateEndpoint: vectorsearch.CreateEndpoint{ @@ -716,6 +757,8 @@ func TestStateToBundleModifiedResources(t *testing.T) { "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.postgres_catalogs.test_postgres_catalog": {ID: "catalogs/test_catalog"}, + "resources.postgres_catalogs.test_postgres_catalog_old": {ID: "catalogs/test_catalog_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"}, } @@ -864,6 +907,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, "catalogs/test_catalog", config.Resources.PostgresCatalogs["test_postgres_catalog"].ID) + assert.Equal(t, "", config.Resources.PostgresCatalogs["test_postgres_catalog"].ModifiedStatus) + assert.Equal(t, "catalogs/test_catalog_old", config.Resources.PostgresCatalogs["test_postgres_catalog_old"].ID) + assert.Equal(t, resources.ModifiedStatusDeleted, config.Resources.PostgresCatalogs["test_postgres_catalog_old"].ModifiedStatus) + assert.Equal(t, "", config.Resources.PostgresCatalogs["test_postgres_catalog_new"].ID) + assert.Equal(t, resources.ModifiedStatusCreated, config.Resources.PostgresCatalogs["test_postgres_catalog_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) diff --git a/bundle/statemgmt/upload_state_for_yaml_sync.go b/bundle/statemgmt/upload_state_for_yaml_sync.go index 74def3174f8..0399c7b31ff 100644 --- a/bundle/statemgmt/upload_state_for_yaml_sync.go +++ b/bundle/statemgmt/upload_state_for_yaml_sync.go @@ -141,12 +141,8 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun migratedDB := dstate.NewDatabase(tfState.Lineage, tfState.Serial+1) migratedDB.State = state - deploymentBundle := &direct.DeploymentBundle{ - StateDB: dstate.DeploymentState{ - Path: snapshotPath, - Data: migratedDB, - }, - } + deploymentBundle := &direct.DeploymentBundle{} + deploymentBundle.StateDB.OpenWithData(snapshotPath, migratedDB) // Apply SecretScopeFixups so the config matches what the direct engine expects. // This adds MANAGE ACL for the current user to all secret scopes, ensuring @@ -197,8 +193,12 @@ func (m *uploadStateForYamlSync) convertState(ctx context.Context, b *bundle.Bun } } + if err := deploymentBundle.StateDB.UpgradeToWrite(); err != nil { + return false, fmt.Errorf("upgrading state for apply: %w", err) + } + deploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(true)) - if err := deploymentBundle.StateDB.Finalize(); err != nil { + if _, err := deploymentBundle.StateDB.Finalize(ctx); err != nil { return false, err } diff --git a/cmd/account/access-control/access-control.go b/cmd/account/access-control/access-control.go index 2dab1d83a99..3f601a89330 100755 --- a/cmd/account/access-control/access-control.go +++ b/cmd/account/access-control/access-control.go @@ -20,14 +20,20 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "access-control", - Short: `These APIs manage access rules on resources in an account.`, - Long: `These APIs manage access rules on resources in an account. Currently, only + Short: `*Public Preview* These APIs manage access rules on resources in an account.`, + Long: `This command is in Public Preview and may change without notice. + +These APIs manage access rules on resources in an account. Currently, only grant rules are supported. A grant rule specifies a role assigned to a set of principals. A list of rules attached to a resource is called a rule set.`, GroupID: "iam", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetAssignableRolesForResource()) cmd.AddCommand(newGetRuleSet()) @@ -56,8 +62,10 @@ func newGetAssignableRolesForResource() *cobra.Command { var getAssignableRolesForResourceReq iam.GetAssignableRolesForResourceRequest cmd.Use = "get-assignable-roles-for-resource RESOURCE" - cmd.Short = `Get assignable roles for a resource.` - cmd.Long = `Get assignable roles for a resource. + cmd.Short = `*Public Preview* Get assignable roles for a resource.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get assignable roles for a resource. Gets all the roles that can be granted on an account level resource. A role is grantable if the rule set on the resource can contain an access rule of the @@ -75,6 +83,8 @@ func newGetAssignableRolesForResource() *cobra.Command { name for the tag policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -123,8 +133,10 @@ func newGetRuleSet() *cobra.Command { var getRuleSetReq iam.GetRuleSetRequest cmd.Use = "get-rule-set NAME ETAG" - cmd.Short = `Get a rule set.` - cmd.Long = `Get a rule set. + cmd.Short = `*Public Preview* Get a rule set.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a rule set. Get a rule set by its name. A rule set is always attached to a resource and contains a list of access rules on the said resource. Currently only a default @@ -157,6 +169,8 @@ func newGetRuleSet() *cobra.Command { etag encoded a specific version of the rule set to get or to be updated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -209,14 +223,18 @@ func newUpdateRuleSet() *cobra.Command { cmd.Flags().Var(&updateRuleSetJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update-rule-set" - cmd.Short = `Update a rule set.` - cmd.Long = `Update a rule set. + cmd.Short = `*Public Preview* Update a rule set.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a rule set. Replace the rules of a rule set. First, use get to read the current version of the rule set before modifying it. This pattern helps prevent conflicts between concurrent updates.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/billable-usage/billable-usage.go b/cmd/account/billable-usage/billable-usage.go index d89b87e4440..6c0fa785fed 100755 --- a/cmd/account/billable-usage/billable-usage.go +++ b/cmd/account/billable-usage/billable-usage.go @@ -24,6 +24,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDownload()) @@ -77,6 +81,8 @@ func newDownload() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/budget-policy/budget-policy.go b/cmd/account/budget-policy/budget-policy.go index 419168fd9ee..917f23548dc 100755 --- a/cmd/account/budget-policy/budget-policy.go +++ b/cmd/account/budget-policy/budget-policy.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "budget-policy", - Short: `A service serves REST API about Budget policies.`, - Long: `A service serves REST API about Budget policies`, + Use: "budget-policy", + Short: `*Public Preview* A service serves REST API about Budget policies.`, + Long: `This command is in Public Preview and may change without notice. + +A service serves REST API about Budget policies`, GroupID: "billing", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -62,12 +68,16 @@ func newCreate() *cobra.Command { cmd.Flags().StringVar(&createReq.RequestId, "request-id", createReq.RequestId, `A unique identifier for this request.`) cmd.Use = "create" - cmd.Short = `Create a budget policy.` - cmd.Long = `Create a budget policy. + cmd.Short = `*Public Preview* Create a budget policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a budget policy. Creates a new policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -127,8 +137,10 @@ func newDelete() *cobra.Command { var deleteReq billing.DeleteBudgetPolicyRequest cmd.Use = "delete POLICY_ID" - cmd.Short = `Delete a budget policy.` - cmd.Long = `Delete a budget policy. + cmd.Short = `*Public Preview* Delete a budget policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a budget policy. Deletes a policy @@ -136,6 +148,8 @@ func newDelete() *cobra.Command { POLICY_ID: The Id of the policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -183,8 +197,10 @@ func newGet() *cobra.Command { var getReq billing.GetBudgetPolicyRequest cmd.Use = "get POLICY_ID" - cmd.Short = `Get a budget policy.` - cmd.Long = `Get a budget policy. + cmd.Short = `*Public Preview* Get a budget policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a budget policy. Retrieves a policy by it's ID. @@ -192,6 +208,8 @@ func newGet() *cobra.Command { POLICY_ID: The Id of the policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -255,13 +273,17 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List policies.` - cmd.Long = `List policies. + cmd.Short = `*Public Preview* List policies.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List policies. Lists all policies. Policies are returned in the alphabetically ascending order of their names.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -320,8 +342,10 @@ func newUpdate() *cobra.Command { cmd.Flags().StringVar(&updateReq.Policy.PolicyName, "policy-name", updateReq.Policy.PolicyName, `The name of the policy.`) cmd.Use = "update POLICY_ID" - cmd.Short = `Update a budget policy.` - cmd.Long = `Update a budget policy. + cmd.Short = `*Public Preview* Update a budget policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a budget policy. Updates a policy @@ -330,6 +354,8 @@ func newUpdate() *cobra.Command { unique.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/budgets/budgets.go b/cmd/account/budgets/budgets.go index b6dfed84376..94557e3f3b2 100755 --- a/cmd/account/budgets/budgets.go +++ b/cmd/account/budgets/budgets.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "budgets", - Short: `These APIs manage budget configurations for this account.`, - Long: `These APIs manage budget configurations for this account. Budgets enable you + Short: `*Public Preview* These APIs manage budget configurations for this account.`, + Long: `This command is in Public Preview and may change without notice. + +These APIs manage budget configurations for this account. Budgets enable you to monitor usage across your account. You can set up budgets to either track account-wide spending, or apply filters to track the spending of specific teams, projects, or workspaces.`, @@ -29,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -62,13 +68,17 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create" - cmd.Short = `Create new budget.` - cmd.Long = `Create new budget. + cmd.Short = `*Public Preview* Create new budget.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create new budget. Create a new budget configuration for an account. For full details, see https://docs.databricks.com/en/admin/account-settings/budgets.html.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -125,8 +135,10 @@ func newDelete() *cobra.Command { var deleteReq billing.DeleteBudgetConfigurationRequest cmd.Use = "delete BUDGET_ID" - cmd.Short = `Delete budget.` - cmd.Long = `Delete budget. + cmd.Short = `*Public Preview* Delete budget.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete budget. Deletes a budget configuration for an account. Both account and budget configuration are specified by ID. This cannot be undone. @@ -135,6 +147,8 @@ func newDelete() *cobra.Command { BUDGET_ID: The Databricks budget configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -182,8 +196,10 @@ func newGet() *cobra.Command { var getReq billing.GetBudgetConfigurationRequest cmd.Use = "get BUDGET_ID" - cmd.Short = `Get budget.` - cmd.Long = `Get budget. + cmd.Short = `*Public Preview* Get budget.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get budget. Gets a budget configuration for an account. Both account and budget configuration are specified by ID. @@ -192,6 +208,8 @@ func newGet() *cobra.Command { BUDGET_ID: The budget configuration ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -251,12 +269,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `Get all budgets.` - cmd.Long = `Get all budgets. + cmd.Short = `*Public Preview* Get all budgets.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get all budgets. Gets all budgets associated with this account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -309,8 +331,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update BUDGET_ID" - cmd.Short = `Modify budget.` - cmd.Long = `Modify budget. + cmd.Short = `*Public Preview* Modify budget.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Modify budget. Updates a budget configuration for an account. Both account and budget configuration are specified by ID. @@ -319,6 +343,8 @@ func newUpdate() *cobra.Command { BUDGET_ID: The Databricks budget configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/cmd.go b/cmd/account/cmd.go index 22f8ba8c407..41213f5cc8b 100644 --- a/cmd/account/cmd.go +++ b/cmd/account/cmd.go @@ -11,6 +11,7 @@ import ( budgets "github.com/databricks/cli/cmd/account/budgets" credentials "github.com/databricks/cli/cmd/account/credentials" custom_app_integration "github.com/databricks/cli/cmd/account/custom-app-integration" + disaster_recovery "github.com/databricks/cli/cmd/account/disaster-recovery" encryption_keys "github.com/databricks/cli/cmd/account/encryption-keys" endpoints "github.com/databricks/cli/cmd/account/endpoints" account_federation_policy "github.com/databricks/cli/cmd/account/federation-policy" @@ -57,6 +58,7 @@ func New() *cobra.Command { cmd.AddCommand(budgets.New()) cmd.AddCommand(credentials.New()) cmd.AddCommand(custom_app_integration.New()) + cmd.AddCommand(disaster_recovery.New()) cmd.AddCommand(encryption_keys.New()) cmd.AddCommand(endpoints.New()) cmd.AddCommand(account_federation_policy.New()) diff --git a/cmd/account/credentials/credentials.go b/cmd/account/credentials/credentials.go index a940bf547d7..e4461fac52e 100755 --- a/cmd/account/credentials/credentials.go +++ b/cmd/account/credentials/credentials.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -81,6 +85,8 @@ func newCreate() *cobra.Command { [Create a new workspace using the Account API]: http://docs.databricks.com/administration-guide/account-api/new-workspace.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -148,6 +154,8 @@ func newDelete() *cobra.Command { CREDENTIALS_ID: Databricks Account API credential configuration ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -206,6 +214,8 @@ func newGet() *cobra.Command { CREDENTIALS_ID: Credential configuration ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -258,6 +268,8 @@ func newList() *cobra.Command { ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/csp-enablement-account/csp-enablement-account.go b/cmd/account/csp-enablement-account/csp-enablement-account.go index f4d5388981f..07d9981a23d 100755 --- a/cmd/account/csp-enablement-account/csp-enablement-account.go +++ b/cmd/account/csp-enablement-account/csp-enablement-account.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "csp-enablement-account", - Short: `The compliance security profile settings at the account level control whether to enable it for new workspaces.`, - Long: `The compliance security profile settings at the account level control whether + Short: `*Public Preview* The compliance security profile settings at the account level control whether to enable it for new workspaces.`, + Long: `This command is in Public Preview and may change without notice. + +The compliance security profile settings at the account level control whether to enable it for new workspaces. By default, this account-level setting is disabled for new workspaces. After workspace creation, account admins can enable the compliance security profile individually for each workspace. @@ -31,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -60,12 +66,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the compliance security profile setting for new workspaces.` - cmd.Long = `Get the compliance security profile setting for new workspaces. + cmd.Short = `*Public Preview* Get the compliance security profile setting for new workspaces.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the compliance security profile setting for new workspaces. Gets the compliance security profile setting for new workspaces.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -115,13 +125,17 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the compliance security profile setting for new workspaces.` - cmd.Long = `Update the compliance security profile setting for new workspaces. + cmd.Short = `*Public Preview* Update the compliance security profile setting for new workspaces.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the compliance security profile setting for new workspaces. Updates the value of the compliance security profile setting for new workspaces.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/custom-app-integration/custom-app-integration.go b/cmd/account/custom-app-integration/custom-app-integration.go index 7e1f79bcef8..f0c2ffd9539 100755 --- a/cmd/account/custom-app-integration/custom-app-integration.go +++ b/cmd/account/custom-app-integration/custom-app-integration.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -77,6 +81,8 @@ func newCreate() *cobra.Command { :method:CustomAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -143,6 +149,8 @@ func newDelete() *cobra.Command { OAuth app integration via :method:CustomAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -199,6 +207,8 @@ func newGet() *cobra.Command { INTEGRATION_ID: The OAuth app integration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -268,6 +278,8 @@ func newList() *cobra.Command { account` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -332,6 +344,8 @@ func newUpdate() *cobra.Command { OAuth app integration via :method:CustomAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/disable-legacy-features/disable-legacy-features.go b/cmd/account/disable-legacy-features/disable-legacy-features.go index 32d554869d2..8289e28f92e 100755 --- a/cmd/account/disable-legacy-features/disable-legacy-features.go +++ b/cmd/account/disable-legacy-features/disable-legacy-features.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -65,6 +69,8 @@ func newDelete() *cobra.Command { Deletes the disable legacy features setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -119,6 +125,8 @@ func newGet() *cobra.Command { Gets the value of the disable legacy features setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -174,6 +182,8 @@ func newUpdate() *cobra.Command { Updates the value of the disable legacy features setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/disaster-recovery/disaster-recovery.go b/cmd/account/disaster-recovery/disaster-recovery.go new file mode 100755 index 00000000000..c002596e732 --- /dev/null +++ b/cmd/account/disaster-recovery/disaster-recovery.go @@ -0,0 +1,865 @@ +// Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +package disaster_recovery + +import ( + "fmt" + "strings" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + "github.com/databricks/databricks-sdk-go/service/disasterrecovery" + "github.com/spf13/cobra" +) + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var cmdOverrides []func(*cobra.Command) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "disaster-recovery", + Short: `Manage disaster recovery configurations and execute failover operations.`, + Long: `Manage disaster recovery configurations and execute failover operations.`, + GroupID: "disasterrecovery", + + // This service is being previewed; hide from help output. + Hidden: true, + RunE: root.ReportUnknownSubcommand, + } + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + // Add methods + cmd.AddCommand(newCreateFailoverGroup()) + cmd.AddCommand(newCreateStableUrl()) + cmd.AddCommand(newDeleteFailoverGroup()) + cmd.AddCommand(newDeleteStableUrl()) + cmd.AddCommand(newFailoverFailoverGroup()) + cmd.AddCommand(newGetFailoverGroup()) + cmd.AddCommand(newGetStableUrl()) + cmd.AddCommand(newListFailoverGroups()) + cmd.AddCommand(newListStableUrls()) + cmd.AddCommand(newUpdateFailoverGroup()) + + // Apply optional overrides to this command. + for _, fn := range cmdOverrides { + fn(cmd) + } + + return cmd +} + +// start create-failover-group command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createFailoverGroupOverrides []func( + *cobra.Command, + *disasterrecovery.CreateFailoverGroupRequest, +) + +func newCreateFailoverGroup() *cobra.Command { + cmd := &cobra.Command{} + + var createFailoverGroupReq disasterrecovery.CreateFailoverGroupRequest + createFailoverGroupReq.FailoverGroup = disasterrecovery.FailoverGroup{} + var createFailoverGroupJson flags.JsonFlag + + cmd.Flags().Var(&createFailoverGroupJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().BoolVar(&createFailoverGroupReq.ValidateOnly, "validate-only", createFailoverGroupReq.ValidateOnly, `When true, validates the request without creating the failover group.`) + cmd.Flags().StringVar(&createFailoverGroupReq.FailoverGroup.Etag, "etag", createFailoverGroupReq.FailoverGroup.Etag, `Opaque version string for optimistic locking.`) + cmd.Flags().StringVar(&createFailoverGroupReq.FailoverGroup.Name, "name", createFailoverGroupReq.FailoverGroup.Name, `Fully qualified resource name in the format accounts/{account_id}/failover-groups/{failover_group_id}.`) + // TODO: complex arg: unity_catalog_assets + + cmd.Use = "create-failover-group PARENT FAILOVER_GROUP_ID REGIONS WORKSPACE_SETS INITIAL_PRIMARY_REGION" + cmd.Short = `Create a Failover Group.` + cmd.Long = `Create a Failover Group. + + Create a new failover group. + + Arguments: + PARENT: The parent resource. Format: accounts/{account_id}. + FAILOVER_GROUP_ID: Client-provided identifier for the failover group. Used to construct the + resource name as {parent}/failover-groups/{failover_group_id}. + REGIONS: List of all regions participating in this failover group. + WORKSPACE_SETS: Workspace sets, each containing workspaces that replicate to each other. + INITIAL_PRIMARY_REGION: Initial primary region. Used only in Create requests to set the starting + primary region. Not returned in responses.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT, FAILOVER_GROUP_ID as positional arguments. Provide 'regions', 'workspace_sets', 'initial_primary_region' in your JSON input") + } + return nil + } + check := root.ExactArgs(5) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createFailoverGroupJson.Unmarshal(&createFailoverGroupReq.FailoverGroup) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createFailoverGroupReq.Parent = args[0] + createFailoverGroupReq.FailoverGroupId = args[1] + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[2], &createFailoverGroupReq.FailoverGroup.Regions) + if err != nil { + return fmt.Errorf("invalid REGIONS: %s", args[2]) + } + + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[3], &createFailoverGroupReq.FailoverGroup.WorkspaceSets) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_SETS: %s", args[3]) + } + + } + if !cmd.Flags().Changed("json") { + createFailoverGroupReq.FailoverGroup.InitialPrimaryRegion = args[4] + } + + response, err := a.DisasterRecovery.CreateFailoverGroup(ctx, createFailoverGroupReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createFailoverGroupOverrides { + fn(cmd, &createFailoverGroupReq) + } + + return cmd +} + +// start create-stable-url command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createStableUrlOverrides []func( + *cobra.Command, + *disasterrecovery.CreateStableUrlRequest, +) + +func newCreateStableUrl() *cobra.Command { + cmd := &cobra.Command{} + + var createStableUrlReq disasterrecovery.CreateStableUrlRequest + createStableUrlReq.StableUrl = disasterrecovery.StableUrl{} + var createStableUrlJson flags.JsonFlag + + cmd.Flags().Var(&createStableUrlJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().BoolVar(&createStableUrlReq.ValidateOnly, "validate-only", createStableUrlReq.ValidateOnly, `When true, validates the request without creating the stable URL.`) + cmd.Flags().StringVar(&createStableUrlReq.StableUrl.Name, "name", createStableUrlReq.StableUrl.Name, `Fully qualified resource name.`) + + cmd.Use = "create-stable-url PARENT STABLE_URL_ID INITIAL_WORKSPACE_ID" + cmd.Short = `Create a Stable URL.` + cmd.Long = `Create a Stable URL. + + Create a new stable URL. + + Arguments: + PARENT: The parent resource. Format: accounts/{account_id}. + STABLE_URL_ID: Client-provided identifier for the stable URL. Used to construct the + resource name as {parent}/stable-urls/{stable_url_id}. + INITIAL_WORKSPACE_ID: The workspace this stable URL is initially bound to. Used only in Create + requests to associate the stable URL with a workspace. Not returned in + responses. Mirrors FailoverGroup.initial_primary_region semantics.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT, STABLE_URL_ID as positional arguments. Provide 'initial_workspace_id' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createStableUrlJson.Unmarshal(&createStableUrlReq.StableUrl) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createStableUrlReq.Parent = args[0] + createStableUrlReq.StableUrlId = args[1] + if !cmd.Flags().Changed("json") { + createStableUrlReq.StableUrl.InitialWorkspaceId = args[2] + } + + response, err := a.DisasterRecovery.CreateStableUrl(ctx, createStableUrlReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createStableUrlOverrides { + fn(cmd, &createStableUrlReq) + } + + return cmd +} + +// start delete-failover-group command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteFailoverGroupOverrides []func( + *cobra.Command, + *disasterrecovery.DeleteFailoverGroupRequest, +) + +func newDeleteFailoverGroup() *cobra.Command { + cmd := &cobra.Command{} + + var deleteFailoverGroupReq disasterrecovery.DeleteFailoverGroupRequest + + cmd.Flags().StringVar(&deleteFailoverGroupReq.Etag, "etag", deleteFailoverGroupReq.Etag, `Opaque version string for optimistic locking.`) + + cmd.Use = "delete-failover-group NAME" + cmd.Short = `Delete a Failover Group.` + cmd.Long = `Delete a Failover Group. + + Delete a failover group. + + Arguments: + NAME: The fully qualified resource name of the failover group to delete. Format: + accounts/{account_id}/failover-groups/{failover_group_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + deleteFailoverGroupReq.Name = args[0] + + err = a.DisasterRecovery.DeleteFailoverGroup(ctx, deleteFailoverGroupReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteFailoverGroupOverrides { + fn(cmd, &deleteFailoverGroupReq) + } + + return cmd +} + +// start delete-stable-url command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteStableUrlOverrides []func( + *cobra.Command, + *disasterrecovery.DeleteStableUrlRequest, +) + +func newDeleteStableUrl() *cobra.Command { + cmd := &cobra.Command{} + + var deleteStableUrlReq disasterrecovery.DeleteStableUrlRequest + + cmd.Use = "delete-stable-url NAME" + cmd.Short = `Delete a Stable URL.` + cmd.Long = `Delete a Stable URL. + + Delete a stable URL. + + Arguments: + NAME: The fully qualified resource name. Format: + accounts/{account_id}/stable-urls/{stable_url_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + deleteStableUrlReq.Name = args[0] + + err = a.DisasterRecovery.DeleteStableUrl(ctx, deleteStableUrlReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteStableUrlOverrides { + fn(cmd, &deleteStableUrlReq) + } + + return cmd +} + +// start failover-failover-group command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var failoverFailoverGroupOverrides []func( + *cobra.Command, + *disasterrecovery.FailoverFailoverGroupRequest, +) + +func newFailoverFailoverGroup() *cobra.Command { + cmd := &cobra.Command{} + + var failoverFailoverGroupReq disasterrecovery.FailoverFailoverGroupRequest + var failoverFailoverGroupJson flags.JsonFlag + + cmd.Flags().Var(&failoverFailoverGroupJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&failoverFailoverGroupReq.Etag, "etag", failoverFailoverGroupReq.Etag, `Opaque version string for optimistic locking.`) + + cmd.Use = "failover-failover-group NAME TARGET_PRIMARY_REGION FAILOVER_TYPE" + cmd.Short = `Failover a Failover Group to a new primary region.` + cmd.Long = `Failover a Failover Group to a new primary region. + + Initiate a failover to a new primary region. + + Arguments: + NAME: The fully qualified resource name of the failover group to failover. + Format: accounts/{account_id}/failover-groups/{failover_group_id}. + TARGET_PRIMARY_REGION: The target primary region. Must be one of the derived regions and + different from the current effective_primary_region. Serves as an + idempotency check. + FAILOVER_TYPE: The type of failover to perform. + Supported values: [FORCED]` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(1)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME as positional arguments. Provide 'target_primary_region', 'failover_type' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := failoverFailoverGroupJson.Unmarshal(&failoverFailoverGroupReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + failoverFailoverGroupReq.Name = args[0] + if !cmd.Flags().Changed("json") { + failoverFailoverGroupReq.TargetPrimaryRegion = args[1] + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[2], &failoverFailoverGroupReq.FailoverType) + if err != nil { + return fmt.Errorf("invalid FAILOVER_TYPE: %s", args[2]) + } + + } + + response, err := a.DisasterRecovery.FailoverFailoverGroup(ctx, failoverFailoverGroupReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range failoverFailoverGroupOverrides { + fn(cmd, &failoverFailoverGroupReq) + } + + return cmd +} + +// start get-failover-group command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getFailoverGroupOverrides []func( + *cobra.Command, + *disasterrecovery.GetFailoverGroupRequest, +) + +func newGetFailoverGroup() *cobra.Command { + cmd := &cobra.Command{} + + var getFailoverGroupReq disasterrecovery.GetFailoverGroupRequest + + cmd.Use = "get-failover-group NAME" + cmd.Short = `Get a Failover Group.` + cmd.Long = `Get a Failover Group. + + Get a failover group. + + Arguments: + NAME: The fully qualified resource name of the failover group. Format: + accounts/{account_id}/failover-groups/{failover_group_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + getFailoverGroupReq.Name = args[0] + + response, err := a.DisasterRecovery.GetFailoverGroup(ctx, getFailoverGroupReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getFailoverGroupOverrides { + fn(cmd, &getFailoverGroupReq) + } + + return cmd +} + +// start get-stable-url command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getStableUrlOverrides []func( + *cobra.Command, + *disasterrecovery.GetStableUrlRequest, +) + +func newGetStableUrl() *cobra.Command { + cmd := &cobra.Command{} + + var getStableUrlReq disasterrecovery.GetStableUrlRequest + + cmd.Use = "get-stable-url NAME" + cmd.Short = `Get a Stable URL.` + cmd.Long = `Get a Stable URL. + + Get a stable URL. + + Arguments: + NAME: The fully qualified resource name. Format: + accounts/{account_id}/stable-urls/{stable_url_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + getStableUrlReq.Name = args[0] + + response, err := a.DisasterRecovery.GetStableUrl(ctx, getStableUrlReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getStableUrlOverrides { + fn(cmd, &getStableUrlReq) + } + + return cmd +} + +// start list-failover-groups command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listFailoverGroupsOverrides []func( + *cobra.Command, + *disasterrecovery.ListFailoverGroupsRequest, +) + +func newListFailoverGroups() *cobra.Command { + cmd := &cobra.Command{} + + var listFailoverGroupsReq disasterrecovery.ListFailoverGroupsRequest + // 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 listFailoverGroupsLimit int + + cmd.Flags().IntVar(&listFailoverGroupsReq.PageSize, "page-size", listFailoverGroupsReq.PageSize, `Maximum number of failover groups to return per page.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listFailoverGroupsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listFailoverGroupsReq.PageToken, "page-token", listFailoverGroupsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-failover-groups PARENT" + cmd.Short = `List Failover Groups.` + cmd.Long = `List Failover Groups. + + List failover groups. + + Arguments: + PARENT: The parent resource. Format: accounts/{account_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + listFailoverGroupsReq.Parent = args[0] + + response := a.DisasterRecovery.ListFailoverGroups(ctx, listFailoverGroupsReq) + if listFailoverGroupsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listFailoverGroupsLimit) + } + if listFailoverGroupsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listFailoverGroupsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listFailoverGroupsOverrides { + fn(cmd, &listFailoverGroupsReq) + } + + return cmd +} + +// start list-stable-urls command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listStableUrlsOverrides []func( + *cobra.Command, + *disasterrecovery.ListStableUrlsRequest, +) + +func newListStableUrls() *cobra.Command { + cmd := &cobra.Command{} + + var listStableUrlsReq disasterrecovery.ListStableUrlsRequest + // 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 listStableUrlsLimit int + + cmd.Flags().IntVar(&listStableUrlsReq.PageSize, "page-size", listStableUrlsReq.PageSize, `Maximum number of stable URLs to return per page.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listStableUrlsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listStableUrlsReq.PageToken, "page-token", listStableUrlsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-stable-urls PARENT" + cmd.Short = `List Stable URLs.` + cmd.Long = `List Stable URLs. + + List stable URLs for an account. + + Arguments: + PARENT: The parent resource. Format: accounts/{account_id}.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + listStableUrlsReq.Parent = args[0] + + response := a.DisasterRecovery.ListStableUrls(ctx, listStableUrlsReq) + if listStableUrlsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listStableUrlsLimit) + } + if listStableUrlsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listStableUrlsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listStableUrlsOverrides { + fn(cmd, &listStableUrlsReq) + } + + return cmd +} + +// start update-failover-group command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateFailoverGroupOverrides []func( + *cobra.Command, + *disasterrecovery.UpdateFailoverGroupRequest, +) + +func newUpdateFailoverGroup() *cobra.Command { + cmd := &cobra.Command{} + + var updateFailoverGroupReq disasterrecovery.UpdateFailoverGroupRequest + updateFailoverGroupReq.FailoverGroup = disasterrecovery.FailoverGroup{} + var updateFailoverGroupJson flags.JsonFlag + + cmd.Flags().Var(&updateFailoverGroupJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&updateFailoverGroupReq.FailoverGroup.Etag, "etag", updateFailoverGroupReq.FailoverGroup.Etag, `Opaque version string for optimistic locking.`) + cmd.Flags().StringVar(&updateFailoverGroupReq.FailoverGroup.Name, "name", updateFailoverGroupReq.FailoverGroup.Name, `Fully qualified resource name in the format accounts/{account_id}/failover-groups/{failover_group_id}.`) + // TODO: complex arg: unity_catalog_assets + + cmd.Use = "update-failover-group NAME UPDATE_MASK REGIONS WORKSPACE_SETS INITIAL_PRIMARY_REGION" + cmd.Short = `Update a Failover Group.` + cmd.Long = `Update a Failover Group. + + Update a failover group. + + Arguments: + NAME: Fully qualified resource name in the format + accounts/{account_id}/failover-groups/{failover_group_id}. + UPDATE_MASK: Comma-separated list of fields to update. + REGIONS: List of all regions participating in this failover group. + WORKSPACE_SETS: Workspace sets, each containing workspaces that replicate to each other. + INITIAL_PRIMARY_REGION: Initial primary region. Used only in Create requests to set the starting + primary region. Not returned in responses.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME, UPDATE_MASK as positional arguments. Provide 'regions', 'workspace_sets', 'initial_primary_region' in your JSON input") + } + return nil + } + check := root.ExactArgs(5) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateFailoverGroupJson.Unmarshal(&updateFailoverGroupReq.FailoverGroup) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateFailoverGroupReq.Name = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateFailoverGroupReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[2], &updateFailoverGroupReq.FailoverGroup.Regions) + if err != nil { + return fmt.Errorf("invalid REGIONS: %s", args[2]) + } + + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[3], &updateFailoverGroupReq.FailoverGroup.WorkspaceSets) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_SETS: %s", args[3]) + } + + } + if !cmd.Flags().Changed("json") { + updateFailoverGroupReq.FailoverGroup.InitialPrimaryRegion = args[4] + } + + response, err := a.DisasterRecovery.UpdateFailoverGroup(ctx, updateFailoverGroupReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateFailoverGroupOverrides { + fn(cmd, &updateFailoverGroupReq) + } + + return cmd +} + +// end service DisasterRecovery 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 f1329c9732c..b89397ce1c2 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 @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -65,6 +69,8 @@ func newDelete() *cobra.Command { Reverts the value of the account IP access toggle setting to default (ON)` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -119,6 +125,8 @@ func newGet() *cobra.Command { Gets the value of the account IP access toggle setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -174,6 +182,8 @@ func newUpdate() *cobra.Command { Updates the value of the account IP access toggle setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/encryption-keys/encryption-keys.go b/cmd/account/encryption-keys/encryption-keys.go index dd0ee03b2c8..1ae7e68ea37 100755 --- a/cmd/account/encryption-keys/encryption-keys.go +++ b/cmd/account/encryption-keys/encryption-keys.go @@ -41,6 +41,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -73,6 +77,7 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) // TODO: complex arg: aws_key_info + // TODO: complex arg: azure_key_info // TODO: complex arg: gcp_key_info cmd.Use = "create" @@ -95,9 +100,21 @@ func newCreate() *cobra.Command { This operation is available only if your account is on the E2 version of the platform or on a select custom plan that allows multiple workspaces per - account.` + account. + + **GCP only**: To create a customer-managed key on GCP, you must include the + X-Databricks-GCP-SA-Access-Token HTTP header in your request. This header + must contain a Google Cloud OAuth access token with the cloud-platform + scope. The Google identity associated with the token must also have the + setIamPermissions and getIamPermissions IAM permissions on the key + resource. For details on obtaining this token, see [Authenticate with Google + ID tokens]. + + [Authenticate with Google ID tokens]: https://docs.databricks.com/gcp/en/dev-tools/auth/authentication-google-id.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -164,6 +181,8 @@ func newDelete() *cobra.Command { CUSTOMER_MANAGED_KEY_ID: Databricks encryption key configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -235,6 +254,8 @@ func newGet() *cobra.Command { CUSTOMER_MANAGED_KEY_ID: Databricks encryption key configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -286,6 +307,8 @@ func newList() *cobra.Command { Lists Databricks customer-managed key configurations for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/endpoints/endpoints.go b/cmd/account/endpoints/endpoints.go index 3c8a78bbd1d..eb3056de47f 100755 --- a/cmd/account/endpoints/endpoints.go +++ b/cmd/account/endpoints/endpoints.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "endpoints", - Short: `These APIs manage endpoint configurations for this account.`, - Long: `These APIs manage endpoint configurations for this account.`, + Use: "endpoints", + Short: `*Public Preview* These APIs manage endpoint configurations for this account.`, + Long: `This command is in Public Preview and may change without notice. + +These APIs manage endpoint configurations for this account.`, GroupID: "provisioning", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreateEndpoint()) cmd.AddCommand(newDeleteEndpoint()) @@ -61,8 +67,10 @@ func newCreateEndpoint() *cobra.Command { // TODO: complex arg: azure_private_endpoint_info cmd.Use = "create-endpoint PARENT DISPLAY_NAME REGION" - cmd.Short = `Create a network endpoint.` - cmd.Long = `Create a network endpoint. + cmd.Short = `*Public Preview* Create a network endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a network endpoint. Creates a new network connectivity endpoint that enables private connectivity between your network resources and Databricks services. @@ -83,6 +91,8 @@ func newCreateEndpoint() *cobra.Command { REGION: The cloud provider region where this endpoint is located.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -156,14 +166,18 @@ func newDeleteEndpoint() *cobra.Command { var deleteEndpointReq networking.DeleteEndpointRequest cmd.Use = "delete-endpoint NAME" - cmd.Short = `Delete a network endpoint.` - cmd.Long = `Delete a network endpoint. + cmd.Short = `*Public Preview* Delete a network endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a network endpoint. Deletes a network endpoint. This will remove the endpoint configuration from Databricks. Depending on the endpoint type and use case, you may also need to delete corresponding network resources in your cloud provider account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -211,12 +225,16 @@ func newGetEndpoint() *cobra.Command { var getEndpointReq networking.GetEndpointRequest cmd.Use = "get-endpoint NAME" - cmd.Short = `Get a network endpoint.` - cmd.Long = `Get a network endpoint. + cmd.Short = `*Public Preview* Get a network endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a network endpoint. Gets details of a specific network endpoint.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -278,8 +296,10 @@ func newListEndpoints() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-endpoints PARENT" - cmd.Short = `List network endpoints.` - cmd.Long = `List network endpoints. + cmd.Short = `*Public Preview* List network endpoints.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List network endpoints. Lists all network connectivity endpoints for the account. @@ -288,6 +308,8 @@ func newListEndpoints() *cobra.Command { accounts/{account_id}.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/esm-enablement-account/esm-enablement-account.go b/cmd/account/esm-enablement-account/esm-enablement-account.go index d7468cf8f68..7f0286dd2b6 100755 --- a/cmd/account/esm-enablement-account/esm-enablement-account.go +++ b/cmd/account/esm-enablement-account/esm-enablement-account.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "esm-enablement-account", - Short: `The enhanced security monitoring setting at the account level controls whether to enable the feature on new workspaces.`, - Long: `The enhanced security monitoring setting at the account level controls whether + Short: `*Public Preview* The enhanced security monitoring setting at the account level controls whether to enable the feature on new workspaces.`, + Long: `This command is in Public Preview and may change without notice. + +The enhanced security monitoring setting at the account level controls whether to enable the feature on new workspaces. By default, this account-level setting is disabled for new workspaces. After workspace creation, account admins can enable enhanced security monitoring individually for each @@ -29,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -58,12 +64,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the enhanced security monitoring setting for new workspaces.` - cmd.Long = `Get the enhanced security monitoring setting for new workspaces. + cmd.Short = `*Public Preview* Get the enhanced security monitoring setting for new workspaces.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the enhanced security monitoring setting for new workspaces. Gets the enhanced security monitoring setting for new workspaces.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -113,13 +123,17 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the enhanced security monitoring setting for new workspaces.` - cmd.Long = `Update the enhanced security monitoring setting for new workspaces. + cmd.Short = `*Public Preview* Update the enhanced security monitoring setting for new workspaces.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the enhanced security monitoring setting for new workspaces. Updates the value of the enhanced security monitoring setting for new workspaces.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/federation-policy/federation-policy.go b/cmd/account/federation-policy/federation-policy.go index 64610ef39df..f9ccb47c442 100755 --- a/cmd/account/federation-policy/federation-policy.go +++ b/cmd/account/federation-policy/federation-policy.go @@ -74,6 +74,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -116,6 +120,8 @@ func newCreate() *cobra.Command { cmd.Long = `Create account federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -182,6 +188,8 @@ func newDelete() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -236,6 +244,8 @@ func newGet() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -301,6 +311,8 @@ func newList() *cobra.Command { cmd.Long = `List account federation policies.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -365,6 +377,8 @@ func newUpdate() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/groups-v2/groups-v2.go b/cmd/account/groups-v2/groups-v2.go index 678a9f5bb84..63c47c53ee8 100755 --- a/cmd/account/groups-v2/groups-v2.go +++ b/cmd/account/groups-v2/groups-v2.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -80,6 +84,8 @@ func newCreate() *cobra.Command { supplied group details.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -148,6 +154,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a group in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -204,6 +212,8 @@ func newGet() *cobra.Command { ID: Unique ID for a group in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -281,6 +291,8 @@ func newList() *cobra.Command { receiving member data as before.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -345,6 +357,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -422,6 +436,8 @@ func newUpdate() *cobra.Command { ID: Databricks group ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/groups.go b/cmd/account/groups.go index 53dd7b8f02f..267aaa3b184 100644 --- a/cmd/account/groups.go +++ b/cmd/account/groups.go @@ -32,5 +32,9 @@ func Groups() []cobra.Group { ID: "oauth2", Title: "OAuth", }, + { + ID: "disasterrecovery", + Title: "Disaster Recovery", + }, } } diff --git a/cmd/account/iam-v2/iam-v2.go b/cmd/account/iam-v2/iam-v2.go index 028c48b47b9..965966c124c 100755 --- a/cmd/account/iam-v2/iam-v2.go +++ b/cmd/account/iam-v2/iam-v2.go @@ -4,11 +4,13 @@ package iam_v2 import ( "fmt" + "strings" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/common/types/fieldmask" "github.com/databricks/databricks-sdk-go/service/iamv2" "github.com/spf13/cobra" ) @@ -20,18 +22,29 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "iam-v2", - Short: `These APIs are used to manage identities and the workspace access of these identities in .`, - Long: `These APIs are used to manage identities and the workspace access of these + Short: `*Beta* These APIs are used to manage identities and the workspace access of these identities in .`, + Long: `This command is in Beta and may change without notice. + +These APIs are used to manage identities and the workspace access of these identities in .`, GroupID: "iam", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods + cmd.AddCommand(newCreateWorkspaceAssignmentDetail()) + cmd.AddCommand(newDeleteWorkspaceAssignmentDetail()) cmd.AddCommand(newGetWorkspaceAccessDetail()) + cmd.AddCommand(newGetWorkspaceAssignmentDetail()) + cmd.AddCommand(newListWorkspaceAssignmentDetails()) cmd.AddCommand(newResolveGroup()) cmd.AddCommand(newResolveServicePrincipal()) cmd.AddCommand(newResolveUser()) + cmd.AddCommand(newUpdateWorkspaceAssignmentDetail()) // Apply optional overrides to this command. for _, fn := range cmdOverrides { @@ -41,6 +54,185 @@ func New() *cobra.Command { return cmd } +// start create-workspace-assignment-detail command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createWorkspaceAssignmentDetailOverrides []func( + *cobra.Command, + *iamv2.CreateWorkspaceAssignmentDetailRequest, +) + +func newCreateWorkspaceAssignmentDetail() *cobra.Command { + cmd := &cobra.Command{} + + var createWorkspaceAssignmentDetailReq iamv2.CreateWorkspaceAssignmentDetailRequest + createWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail = iamv2.WorkspaceAssignmentDetail{} + var createWorkspaceAssignmentDetailJson flags.JsonFlag + + cmd.Flags().Var(&createWorkspaceAssignmentDetailJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: entitlements + + cmd.Use = "create-workspace-assignment-detail WORKSPACE_ID PRINCIPAL_ID" + cmd.Short = `Create a workspace assignment detail.` + cmd.Long = `Create a workspace assignment detail. + + Creates a workspace assignment detail for a principal. Entitlement grants are + applied individually and non-atomically — if a failure occurs partway + through, the principal will be assigned to the workspace but with only a + subset of the requested entitlements. Use GetWorkspaceAssignmentDetail to + confirm which entitlements were successfully granted. + + Arguments: + WORKSPACE_ID: Required. The workspace ID for which the workspace assignment detail is + being created. + PRINCIPAL_ID: The internal ID of the principal (user/sp/group) in Databricks.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(1)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only WORKSPACE_ID as positional arguments. Provide 'principal_id' in your JSON input") + } + return nil + } + check := root.ExactArgs(2) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createWorkspaceAssignmentDetailJson.Unmarshal(&createWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + _, err = fmt.Sscan(args[0], &createWorkspaceAssignmentDetailReq.WorkspaceId) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_ID: %s", args[0]) + } + + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[1], &createWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[1]) + } + + } + + response, err := a.IamV2.CreateWorkspaceAssignmentDetail(ctx, createWorkspaceAssignmentDetailReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createWorkspaceAssignmentDetailOverrides { + fn(cmd, &createWorkspaceAssignmentDetailReq) + } + + return cmd +} + +// start delete-workspace-assignment-detail command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteWorkspaceAssignmentDetailOverrides []func( + *cobra.Command, + *iamv2.DeleteWorkspaceAssignmentDetailRequest, +) + +func newDeleteWorkspaceAssignmentDetail() *cobra.Command { + cmd := &cobra.Command{} + + var deleteWorkspaceAssignmentDetailReq iamv2.DeleteWorkspaceAssignmentDetailRequest + + cmd.Use = "delete-workspace-assignment-detail WORKSPACE_ID PRINCIPAL_ID" + cmd.Short = `Delete a workspace assignment detail.` + cmd.Long = `Delete a workspace assignment detail. + + Deletes a workspace assignment detail for a principal, revoking all associated + entitlements. Entitlement revocations are applied individually and + non-atomically — if a failure occurs partway through, the principal remains + assigned with a subset of its original entitlements, and the operation is safe + to retry. + + Arguments: + WORKSPACE_ID: The workspace ID where the principal has access. + PRINCIPAL_ID: Required. ID of the principal in Databricks to delete workspace assignment + for.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(2) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + _, err = fmt.Sscan(args[0], &deleteWorkspaceAssignmentDetailReq.WorkspaceId) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_ID: %s", args[0]) + } + + _, err = fmt.Sscan(args[1], &deleteWorkspaceAssignmentDetailReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[1]) + } + + err = a.IamV2.DeleteWorkspaceAssignmentDetail(ctx, deleteWorkspaceAssignmentDetailReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteWorkspaceAssignmentDetailOverrides { + fn(cmd, &deleteWorkspaceAssignmentDetailReq) + } + + return cmd +} + // start get-workspace-access-detail command // Slice with functions to override default command behavior. @@ -58,8 +250,10 @@ func newGetWorkspaceAccessDetail() *cobra.Command { cmd.Flags().Var(&getWorkspaceAccessDetailReq.View, "view", `Controls what fields are returned. Supported values: [BASIC, FULL]`) cmd.Use = "get-workspace-access-detail WORKSPACE_ID PRINCIPAL_ID" - cmd.Short = `Get workspace access details for a principal.` - cmd.Long = `Get workspace access details for a principal. + cmd.Short = `*Beta* Get workspace access details for a principal.` + cmd.Long = `This command is in Beta and may change without notice. + +Get workspace access details for a principal. Returns the access details for a principal in a workspace. Allows for checking access details for any provisioned principal (user, service principal, or @@ -75,6 +269,8 @@ func newGetWorkspaceAccessDetail() *cobra.Command { access details are being requested.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -116,6 +312,148 @@ func newGetWorkspaceAccessDetail() *cobra.Command { return cmd } +// start get-workspace-assignment-detail command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getWorkspaceAssignmentDetailOverrides []func( + *cobra.Command, + *iamv2.GetWorkspaceAssignmentDetailRequest, +) + +func newGetWorkspaceAssignmentDetail() *cobra.Command { + cmd := &cobra.Command{} + + var getWorkspaceAssignmentDetailReq iamv2.GetWorkspaceAssignmentDetailRequest + + cmd.Use = "get-workspace-assignment-detail WORKSPACE_ID PRINCIPAL_ID" + cmd.Short = `Get workspace assignment details for a principal.` + cmd.Long = `Get workspace assignment details for a principal. + + Returns the assignment details for a principal in a workspace. + + Arguments: + WORKSPACE_ID: Required. The workspace ID for which the assignment details are being + requested. + PRINCIPAL_ID: Required. The internal ID of the principal (user/sp/group) for which the + assignment details are being requested.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(2) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + _, err = fmt.Sscan(args[0], &getWorkspaceAssignmentDetailReq.WorkspaceId) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_ID: %s", args[0]) + } + + _, err = fmt.Sscan(args[1], &getWorkspaceAssignmentDetailReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[1]) + } + + response, err := a.IamV2.GetWorkspaceAssignmentDetail(ctx, getWorkspaceAssignmentDetailReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getWorkspaceAssignmentDetailOverrides { + fn(cmd, &getWorkspaceAssignmentDetailReq) + } + + return cmd +} + +// start list-workspace-assignment-details command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listWorkspaceAssignmentDetailsOverrides []func( + *cobra.Command, + *iamv2.ListWorkspaceAssignmentDetailsRequest, +) + +func newListWorkspaceAssignmentDetails() *cobra.Command { + cmd := &cobra.Command{} + + var listWorkspaceAssignmentDetailsReq iamv2.ListWorkspaceAssignmentDetailsRequest + + cmd.Flags().IntVar(&listWorkspaceAssignmentDetailsReq.PageSize, "page-size", listWorkspaceAssignmentDetailsReq.PageSize, `The maximum number of workspace assignment details to return.`) + cmd.Flags().StringVar(&listWorkspaceAssignmentDetailsReq.PageToken, "page-token", listWorkspaceAssignmentDetailsReq.PageToken, `A page token, received from a previous ListWorkspaceAssignmentDetails call.`) + + cmd.Use = "list-workspace-assignment-details WORKSPACE_ID" + cmd.Short = `List workspace assignment details for a workspace.` + cmd.Long = `List workspace assignment details for a workspace. + + Lists workspace assignment details for a workspace. + + Arguments: + WORKSPACE_ID: Required. The workspace ID for which the workspace assignment details are + being fetched.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + _, err = fmt.Sscan(args[0], &listWorkspaceAssignmentDetailsReq.WorkspaceId) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_ID: %s", args[0]) + } + + response, err := a.IamV2.ListWorkspaceAssignmentDetails(ctx, listWorkspaceAssignmentDetailsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listWorkspaceAssignmentDetailsOverrides { + fn(cmd, &listWorkspaceAssignmentDetailsReq) + } + + return cmd +} + // start resolve-group command // Slice with functions to override default command behavior. @@ -134,8 +472,10 @@ func newResolveGroup() *cobra.Command { cmd.Flags().Var(&resolveGroupJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-group EXTERNAL_ID" - cmd.Short = `Resolve an external group in the Databricks account.` - cmd.Long = `Resolve an external group in the Databricks account. + cmd.Short = `*Beta* Resolve an external group in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external group in the Databricks account. Resolves a group with the given external ID from the customer's IdP. If the group does not exist, it will be created in the account. If the customer is @@ -146,6 +486,8 @@ func newResolveGroup() *cobra.Command { EXTERNAL_ID: Required. The external ID of the group in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -218,8 +560,10 @@ func newResolveServicePrincipal() *cobra.Command { cmd.Flags().Var(&resolveServicePrincipalJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-service-principal EXTERNAL_ID" - cmd.Short = `Resolve an external service principal in the Databricks account.` - cmd.Long = `Resolve an external service principal in the Databricks account. + cmd.Short = `*Beta* Resolve an external service principal in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external service principal in the Databricks account. Resolves an SP with the given external ID from the customer's IdP. If the SP does not exist, it will be created. If the customer is not onboarded onto @@ -229,6 +573,8 @@ func newResolveServicePrincipal() *cobra.Command { EXTERNAL_ID: Required. The external ID of the service principal in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -301,8 +647,10 @@ func newResolveUser() *cobra.Command { cmd.Flags().Var(&resolveUserJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-user EXTERNAL_ID" - cmd.Short = `Resolve an external user in the Databricks account.` - cmd.Long = `Resolve an external user in the Databricks account. + cmd.Short = `*Beta* Resolve an external user in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external user in the Databricks account. Resolves a user with the given external ID from the customer's IdP. If the user does not exist, it will be created. If the customer is not onboarded onto @@ -312,6 +660,8 @@ func newResolveUser() *cobra.Command { EXTERNAL_ID: Required. The external ID of the user in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -366,4 +716,119 @@ func newResolveUser() *cobra.Command { return cmd } +// start update-workspace-assignment-detail command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateWorkspaceAssignmentDetailOverrides []func( + *cobra.Command, + *iamv2.UpdateWorkspaceAssignmentDetailRequest, +) + +func newUpdateWorkspaceAssignmentDetail() *cobra.Command { + cmd := &cobra.Command{} + + var updateWorkspaceAssignmentDetailReq iamv2.UpdateWorkspaceAssignmentDetailRequest + updateWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail = iamv2.WorkspaceAssignmentDetail{} + var updateWorkspaceAssignmentDetailJson flags.JsonFlag + + cmd.Flags().Var(&updateWorkspaceAssignmentDetailJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: entitlements + + cmd.Use = "update-workspace-assignment-detail WORKSPACE_ID PRINCIPAL_ID UPDATE_MASK PRINCIPAL_ID" + cmd.Short = `Update a workspace assignment detail.` + cmd.Long = `Update a workspace assignment detail. + + Updates the entitlements of a directly assigned principal in a workspace. + Entitlement changes are applied individually and non-atomically — if a + failure occurs partway through, only a subset of the requested changes may + have been applied. Use GetWorkspaceAssignmentDetail to confirm the final + state. + + Arguments: + WORKSPACE_ID: Required. The workspace ID for which the workspace assignment detail is + being updated. + PRINCIPAL_ID: Required. ID of the principal in Databricks. + UPDATE_MASK: Required. The list of fields to update. + PRINCIPAL_ID: The internal ID of the principal (user/sp/group) in Databricks.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(3)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only WORKSPACE_ID, PRINCIPAL_ID, UPDATE_MASK as positional arguments. Provide 'principal_id' in your JSON input") + } + return nil + } + check := root.ExactArgs(4) + return check(cmd, args) + } + + cmd.PreRunE = root.MustAccountClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + a := cmdctx.AccountClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateWorkspaceAssignmentDetailJson.Unmarshal(&updateWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + _, err = fmt.Sscan(args[0], &updateWorkspaceAssignmentDetailReq.WorkspaceId) + if err != nil { + return fmt.Errorf("invalid WORKSPACE_ID: %s", args[0]) + } + + _, err = fmt.Sscan(args[1], &updateWorkspaceAssignmentDetailReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[1]) + } + + if args[2] != "" { + updateMaskArray := strings.Split(args[2], ",") + updateWorkspaceAssignmentDetailReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[3], &updateWorkspaceAssignmentDetailReq.WorkspaceAssignmentDetail.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[3]) + } + + } + + response, err := a.IamV2.UpdateWorkspaceAssignmentDetail(ctx, updateWorkspaceAssignmentDetailReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateWorkspaceAssignmentDetailOverrides { + fn(cmd, &updateWorkspaceAssignmentDetailReq) + } + + return cmd +} + // end service AccountIamV2 diff --git a/cmd/account/ip-access-lists/ip-access-lists.go b/cmd/account/ip-access-lists/ip-access-lists.go index 187cce71305..f7bc59185a3 100755 --- a/cmd/account/ip-access-lists/ip-access-lists.go +++ b/cmd/account/ip-access-lists/ip-access-lists.go @@ -47,6 +47,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -107,6 +111,8 @@ func newCreate() *cobra.Command { Supported values: [ALLOW, BLOCK]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -192,6 +198,8 @@ func newDelete() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -260,6 +268,8 @@ func newGet() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -332,6 +342,8 @@ func newList() *cobra.Command { Gets all IP access lists for the specified account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -402,6 +414,8 @@ func newReplace() *cobra.Command { ENABLED: Specifies whether this IP access list is enabled.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -515,6 +529,8 @@ func newUpdate() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { 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 fa094ed844b..28143768d41 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 @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -63,6 +67,8 @@ func newGet() *cobra.Command { Gets the enable partner powered AI features account setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -118,6 +124,8 @@ func newUpdate() *cobra.Command { Updates the enable partner powered AI features account setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { 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 94428f62769..be082440cb8 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 @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -64,6 +68,8 @@ func newGet() *cobra.Command { Gets the enforcement status of partner powered AI features account setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -120,6 +126,8 @@ func newUpdate() *cobra.Command { setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/log-delivery/log-delivery.go b/cmd/account/log-delivery/log-delivery.go index 348df018025..ec1f2b0e191 100755 --- a/cmd/account/log-delivery/log-delivery.go +++ b/cmd/account/log-delivery/log-delivery.go @@ -91,6 +91,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newGet()) @@ -153,6 +157,8 @@ func newCreate() *cobra.Command { [Deliver and access billable usage logs]: https://docs.databricks.com/administration-guide/account-settings/billable-usage-delivery.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -219,6 +225,8 @@ func newGet() *cobra.Command { LOG_DELIVERY_CONFIGURATION_ID: The log delivery configuration id of customer` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -301,6 +309,8 @@ func newList() *cobra.Command { specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -372,6 +382,8 @@ func newPatchStatus() *cobra.Command { Supported values: [DISABLED, ENABLED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/account/metastore-assignments/metastore-assignments.go b/cmd/account/metastore-assignments/metastore-assignments.go index 124d059cea1..622b56f75dd 100755 --- a/cmd/account/metastore-assignments/metastore-assignments.go +++ b/cmd/account/metastore-assignments/metastore-assignments.go @@ -26,6 +26,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -71,6 +75,8 @@ func newCreate() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -147,6 +153,8 @@ func newDelete() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -212,6 +220,8 @@ func newGet() *cobra.Command { WORKSPACE_ID: Workspace ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -282,6 +292,8 @@ func newList() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -349,6 +361,8 @@ func newUpdate() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/metastores/metastores.go b/cmd/account/metastores/metastores.go index 8d58352bc3b..7967101987f 100755 --- a/cmd/account/metastores/metastores.go +++ b/cmd/account/metastores/metastores.go @@ -27,6 +27,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -68,6 +72,8 @@ func newCreate() *cobra.Command { Creates a Unity Catalog metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -138,6 +144,8 @@ func newDelete() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -195,6 +203,8 @@ func newGet() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -255,6 +265,8 @@ func newList() *cobra.Command { Gets all Unity Catalog metastores associated with an account specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -312,6 +324,8 @@ func newUpdate() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/network-connectivity/network-connectivity.go b/cmd/account/network-connectivity/network-connectivity.go index 3f8c32ed001..9f0807d611b 100755 --- a/cmd/account/network-connectivity/network-connectivity.go +++ b/cmd/account/network-connectivity/network-connectivity.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateNetworkConnectivityConfiguration()) cmd.AddCommand(newCreatePrivateEndpointRule()) @@ -98,6 +102,8 @@ func newCreateNetworkConnectivityConfiguration() *cobra.Command { the same region can be attached to the network connectivity configuration.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -200,6 +206,8 @@ func newCreatePrivateEndpointRule() *cobra.Command { NETWORK_CONNECTIVITY_CONFIG_ID: Your Network Connectivity Configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -269,6 +277,8 @@ func newDeleteNetworkConnectivityConfiguration() *cobra.Command { NETWORK_CONNECTIVITY_CONFIG_ID: Your Network Connectivity Configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -321,7 +331,7 @@ func newDeletePrivateEndpointRule() *cobra.Command { Initiates deleting a private endpoint rule. If the connection state is PENDING or EXPIRED, the private endpoint is immediately deleted. Otherwise, the - private endpoint is deactivated and will be deleted after seven days of + private endpoint is deactivated and will be deleted after one day of deactivation. When a private endpoint is deactivated, the deactivated field is set to true and the private endpoint is not available to your serverless compute resources. @@ -331,6 +341,8 @@ func newDeletePrivateEndpointRule() *cobra.Command { PRIVATE_ENDPOINT_RULE_ID: Your private endpoint rule ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -389,6 +401,8 @@ func newGetNetworkConnectivityConfiguration() *cobra.Command { NETWORK_CONNECTIVITY_CONFIG_ID: Your Network Connectivity Configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -447,6 +461,8 @@ func newGetPrivateEndpointRule() *cobra.Command { PRIVATE_ENDPOINT_RULE_ID: Your private endpoint rule ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -513,6 +529,8 @@ func newListNetworkConnectivityConfigurations() *cobra.Command { Gets an array of network connectivity configurations.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -582,6 +600,8 @@ func newListPrivateEndpointRules() *cobra.Command { NETWORK_CONNECTIVITY_CONFIG_ID: Your Network Connectvity Configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -637,7 +657,7 @@ func newUpdatePrivateEndpointRule() *cobra.Command { cmd.Flags().Var(&updatePrivateEndpointRuleJson, "json", `either inline JSON string or @path/to/file.json with request body`) // 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().BoolVar(&updatePrivateEndpointRuleReq.PrivateEndpointRule.Enabled, "enabled", updatePrivateEndpointRuleReq.PrivateEndpointRule.Enabled, `Update this field to activate/deactivate this private endpoint to allow egress access from serverless compute resources.`) cmd.Flags().StringVar(&updatePrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, "error-message", updatePrivateEndpointRuleReq.PrivateEndpointRule.ErrorMessage, ``) // TODO: complex arg: gcp_endpoint // TODO: array: resource_names @@ -661,6 +681,8 @@ func newUpdatePrivateEndpointRule() *cobra.Command { exactly match the resource field names.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) diff --git a/cmd/account/network-policies/network-policies.go b/cmd/account/network-policies/network-policies.go index 229012a888a..15cc31d5fa0 100755 --- a/cmd/account/network-policies/network-policies.go +++ b/cmd/account/network-policies/network-policies.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateNetworkPolicyRpc()) cmd.AddCommand(newDeleteNetworkPolicyRpc()) @@ -78,6 +82,8 @@ func newCreateNetworkPolicyRpc() *cobra.Command { accessed from the Databricks environment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -146,6 +152,8 @@ func newDeleteNetworkPolicyRpc() *cobra.Command { NETWORK_POLICY_ID: The unique identifier of the network policy to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -202,6 +210,8 @@ func newGetNetworkPolicyRpc() *cobra.Command { NETWORK_POLICY_ID: The unique identifier of the network policy to retrieve.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -267,6 +277,8 @@ func newListNetworkPoliciesRpc() *cobra.Command { Gets an array of network policies.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -335,6 +347,8 @@ func newUpdateNetworkPolicyRpc() *cobra.Command { NETWORK_POLICY_ID: The unique identifier for the network policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/networks/networks.go b/cmd/account/networks/networks.go index 4845a1aa3b2..ae7318e76b9 100755 --- a/cmd/account/networks/networks.go +++ b/cmd/account/networks/networks.go @@ -25,6 +25,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,6 +76,8 @@ func newCreate() *cobra.Command { pre-existing VPC and subnets.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -145,6 +151,8 @@ func newDelete() *cobra.Command { NETWORK_ID: Databricks Account API network configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -203,6 +211,8 @@ func newGet() *cobra.Command { NETWORK_ID: Databricks Account API network configuration ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -254,6 +264,8 @@ func newList() *cobra.Command { Lists Databricks network configurations for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { 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 2bade901da7..89b96f0fadb 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 @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newList()) @@ -73,6 +77,8 @@ func newList() *cobra.Command { Get all the available published OAuth apps in Databricks.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/account/personal-compute/personal-compute.go b/cmd/account/personal-compute/personal-compute.go index 1888fc237fe..69e944cef0c 100755 --- a/cmd/account/personal-compute/personal-compute.go +++ b/cmd/account/personal-compute/personal-compute.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -72,6 +76,8 @@ func newDelete() *cobra.Command { Reverts back the Personal Compute setting value to default (ON)` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -126,6 +132,8 @@ func newGet() *cobra.Command { Gets the value of the Personal Compute setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -181,6 +189,8 @@ func newUpdate() *cobra.Command { Updates the value of the Personal Compute setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/private-access/private-access.go b/cmd/account/private-access/private-access.go index 2933414a449..779cad0cff7 100755 --- a/cmd/account/private-access/private-access.go +++ b/cmd/account/private-access/private-access.go @@ -24,6 +24,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,6 +76,8 @@ func newCreate() *cobra.Command { private endpoints.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -138,6 +144,8 @@ func newDelete() *cobra.Command { ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -192,6 +200,8 @@ func newGet() *cobra.Command { Gets a Databricks private access settings configuration, both specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -243,6 +253,8 @@ func newList() *cobra.Command { Lists Databricks private access settings for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -314,6 +326,8 @@ func newReplace() *cobra.Command { PRIVATE_ACCESS_SETTINGS_ID: Databricks private access settings ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/published-app-integration/published-app-integration.go b/cmd/account/published-app-integration/published-app-integration.go index 693767985a2..e51a46fee89 100755 --- a/cmd/account/published-app-integration/published-app-integration.go +++ b/cmd/account/published-app-integration/published-app-integration.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -73,6 +77,8 @@ func newCreate() *cobra.Command { :method:PublishedAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -139,6 +145,8 @@ func newDelete() *cobra.Command { published OAuth app integration via :method:PublishedAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -192,6 +200,8 @@ func newGet() *cobra.Command { Gets the Published OAuth App Integration for the given integration id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -260,6 +270,8 @@ func newList() *cobra.Command { account` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -321,6 +333,8 @@ func newUpdate() *cobra.Command { published OAuth app integration via :method:PublishedAppIntegration/get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) 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 233c5e2e730..7a148865891 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 @@ -79,6 +79,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -126,6 +130,8 @@ func newCreate() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal id for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -199,6 +205,8 @@ func newDelete() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -261,6 +269,8 @@ func newGet() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -336,6 +346,8 @@ func newList() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal id for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -408,6 +420,8 @@ func newUpdate() *cobra.Command { POLICY_ID: The identifier for the federation policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/service-principal-secrets/service-principal-secrets.go b/cmd/account/service-principal-secrets/service-principal-secrets.go index cf17cb035f2..0ed1a3995f2 100755 --- a/cmd/account/service-principal-secrets/service-principal-secrets.go +++ b/cmd/account/service-principal-secrets/service-principal-secrets.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -80,6 +84,8 @@ func newCreate() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -150,6 +156,8 @@ func newDelete() *cobra.Command { SECRET_ID: The secret ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -222,6 +230,8 @@ func newList() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/service-principals-v2/service-principals-v2.go b/cmd/account/service-principals-v2/service-principals-v2.go index 4604dfa3256..ba6aadb6cb6 100755 --- a/cmd/account/service-principals-v2/service-principals-v2.go +++ b/cmd/account/service-principals-v2/service-principals-v2.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -78,6 +82,8 @@ func newCreate() *cobra.Command { Creates a new service principal in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -146,6 +152,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a service principal in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -203,6 +211,8 @@ func newGet() *cobra.Command { ID: Unique ID for a service principal in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -276,6 +286,8 @@ func newList() *cobra.Command { Gets the set of service principals associated with a Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -341,6 +353,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -420,6 +434,8 @@ func newUpdate() *cobra.Command { ID: Databricks service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/settings-v2/settings-v2.go b/cmd/account/settings-v2/settings-v2.go index f2d464d1804..c42665912dd 100755 --- a/cmd/account/settings-v2/settings-v2.go +++ b/cmd/account/settings-v2/settings-v2.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "settings-v2", - Short: `APIs to manage account level settings.`, - Long: `APIs to manage account level settings`, + Use: "settings-v2", + Short: `*Public Preview* APIs to manage account level settings.`, + Long: `This command is in Public Preview and may change without notice. + +APIs to manage account level settings`, GroupID: "settings", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetPublicAccountSetting()) cmd.AddCommand(newGetPublicAccountUserPreference()) @@ -57,14 +63,18 @@ func newGetPublicAccountSetting() *cobra.Command { var getPublicAccountSettingReq settingsv2.GetPublicAccountSettingRequest cmd.Use = "get-public-account-setting NAME" - cmd.Short = `Get an account setting.` - cmd.Long = `Get an account setting. + cmd.Short = `*Public Preview* Get an account setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an account setting. Get a setting value at account level. See :method:settingsv2/listaccountsettingsmetadata for list of setting available via public APIs at account level.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -113,8 +123,10 @@ func newGetPublicAccountUserPreference() *cobra.Command { var getPublicAccountUserPreferenceReq settingsv2.GetPublicAccountUserPreferenceRequest cmd.Use = "get-public-account-user-preference USER_ID NAME" - cmd.Short = `Get a user preference.` - cmd.Long = `Get a user preference. + cmd.Short = `*Beta* Get a user preference.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a user preference. Get a user preference for a specific user. User preferences are personal settings that allow individual customization without affecting other users. @@ -126,6 +138,8 @@ func newGetPublicAccountUserPreference() *cobra.Command { NAME: User Setting name.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -188,14 +202,18 @@ func newListAccountSettingsMetadata() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-account-settings-metadata" - cmd.Short = `List valid setting keys and their metadata.` - cmd.Long = `List valid setting keys and their metadata. + cmd.Short = `*Public Preview* List valid setting keys and their metadata.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List valid setting keys and their metadata. List valid setting keys and metadata. These settings are available to be referenced via GET :method:settingsv2/getpublicaccountsetting and PATCH :method:settingsv2/patchpublicaccountsetting APIs` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -258,8 +276,10 @@ func newListAccountUserPreferencesMetadata() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-account-user-preferences-metadata USER_ID" - cmd.Short = `List user preferences and their metadata.` - cmd.Long = `List user preferences and their metadata. + cmd.Short = `*Beta* List user preferences and their metadata.` + cmd.Long = `This command is in Beta and may change without notice. + +List user preferences and their metadata. List valid user preferences and their metadata for a specific user. User preferences are personal settings that allow individual customization without @@ -271,6 +291,8 @@ func newListAccountUserPreferencesMetadata() *cobra.Command { USER_ID: User ID of the user whose settings metadata is being retrieved.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -327,25 +349,31 @@ func newPatchPublicAccountSetting() *cobra.Command { // TODO: complex arg: aibi_dashboard_embedding_access_policy // TODO: complex arg: aibi_dashboard_embedding_approved_domains + // TODO: complex arg: allowed_apps_user_api_scopes // TODO: complex arg: automatic_cluster_update_workspace // TODO: complex arg: boolean_val // TODO: complex arg: effective_aibi_dashboard_embedding_access_policy // TODO: complex arg: effective_aibi_dashboard_embedding_approved_domains + // TODO: complex arg: effective_allowed_apps_user_api_scopes // TODO: complex arg: effective_automatic_cluster_update_workspace // TODO: complex arg: effective_boolean_val // TODO: complex arg: effective_integer_val + // TODO: complex arg: effective_operational_email_custom_recipient // TODO: complex arg: effective_personal_compute // TODO: complex arg: effective_restrict_workspace_admins // TODO: complex arg: effective_string_val // TODO: complex arg: integer_val cmd.Flags().StringVar(&patchPublicAccountSettingReq.Setting.Name, "name", patchPublicAccountSettingReq.Setting.Name, `Name of the setting.`) + // TODO: complex arg: operational_email_custom_recipient // TODO: complex arg: personal_compute // TODO: complex arg: restrict_workspace_admins // TODO: complex arg: string_val cmd.Use = "patch-public-account-setting NAME" - cmd.Short = `Update an account setting.` - cmd.Long = `Update an account setting. + cmd.Short = `*Public Preview* Update an account setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an account setting. Patch a setting value at account level. See :method:settingsv2/listaccountsettingsmetadata for list of setting available @@ -356,6 +384,8 @@ func newPatchPublicAccountSetting() *cobra.Command { Note: Page refresh is required for changes to take effect in UI.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -427,8 +457,10 @@ func newPatchPublicAccountUserPreference() *cobra.Command { cmd.Flags().StringVar(&patchPublicAccountUserPreferenceReq.Setting.UserId, "user-id", patchPublicAccountUserPreferenceReq.Setting.UserId, `User ID of the user.`) cmd.Use = "patch-public-account-user-preference USER_ID NAME" - cmd.Short = `Update a user preference.` - cmd.Long = `Update a user preference. + cmd.Short = `*Beta* Update a user preference.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a user preference. Update a user preference for a specific user. User preferences are personal settings that allow individual customization without affecting other users. @@ -442,6 +474,8 @@ func newPatchPublicAccountUserPreference() *cobra.Command { NAME: ` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/settings/settings.go b/cmd/account/settings/settings.go index 297e489de84..752e80d0660 100755 --- a/cmd/account/settings/settings.go +++ b/cmd/account/settings/settings.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add subservices cmd.AddCommand(csp_enablement_account.New()) cmd.AddCommand(disable_legacy_features.New()) diff --git a/cmd/account/storage-credentials/storage-credentials.go b/cmd/account/storage-credentials/storage-credentials.go index 2eaee847d60..ba3f3ded1b6 100755 --- a/cmd/account/storage-credentials/storage-credentials.go +++ b/cmd/account/storage-credentials/storage-credentials.go @@ -26,6 +26,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -76,6 +80,8 @@ func newCreate() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -149,6 +155,8 @@ func newDelete() *cobra.Command { STORAGE_CREDENTIAL_NAME: Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -210,6 +218,8 @@ func newGet() *cobra.Command { STORAGE_CREDENTIAL_NAME: Required. Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -278,6 +288,8 @@ func newList() *cobra.Command { METASTORE_ID: Unity Catalog metastore ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -347,6 +359,8 @@ func newUpdate() *cobra.Command { STORAGE_CREDENTIAL_NAME: Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/storage/storage.go b/cmd/account/storage/storage.go index c852a05084f..94753f450f5 100755 --- a/cmd/account/storage/storage.go +++ b/cmd/account/storage/storage.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -71,6 +75,8 @@ func newCreate() *cobra.Command { Creates a Databricks storage configuration for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -134,6 +140,8 @@ func newDelete() *cobra.Command { configuration that is associated with any workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -188,6 +196,8 @@ func newGet() *cobra.Command { Gets a Databricks storage configuration for an account, both specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -239,6 +249,8 @@ func newList() *cobra.Command { Lists Databricks storage configurations for an account, specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/usage-dashboards/usage-dashboards.go b/cmd/account/usage-dashboards/usage-dashboards.go index 7e08d033845..cb19cbce56a 100755 --- a/cmd/account/usage-dashboards/usage-dashboards.go +++ b/cmd/account/usage-dashboards/usage-dashboards.go @@ -18,14 +18,20 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "usage-dashboards", - Short: `These APIs manage usage dashboards for this account.`, - Long: `These APIs manage usage dashboards for this account. Usage dashboards enable + Short: `*Public Preview* These APIs manage usage dashboards for this account.`, + Long: `This command is in Public Preview and may change without notice. + +These APIs manage usage dashboards for this account. Usage dashboards enable you to gain insights into your usage with pre-built dashboards: visualize breakdowns, analyze tag attributions, and identify cost drivers.`, GroupID: "billing", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newGet()) @@ -60,13 +66,17 @@ func newCreate() *cobra.Command { cmd.Flags().Int64Var(&createReq.WorkspaceId, "workspace-id", createReq.WorkspaceId, `The workspace ID of the workspace in which the usage dashboard is created.`) cmd.Use = "create" - cmd.Short = `Create new usage dashboard.` - cmd.Long = `Create new usage dashboard. + cmd.Short = `*Public Preview* Create new usage dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create new usage dashboard. Create a usage dashboard specified by workspaceId, accountId, and dashboard type.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -129,12 +139,16 @@ func newGet() *cobra.Command { cmd.Flags().Int64Var(&getReq.WorkspaceId, "workspace-id", getReq.WorkspaceId, `The workspace ID of the workspace in which the usage dashboard is created.`) cmd.Use = "get" - cmd.Short = `Get usage dashboard.` - cmd.Long = `Get usage dashboard. + cmd.Short = `*Public Preview* Get usage dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get usage dashboard. Get a usage dashboard specified by workspaceId, accountId, and dashboard type.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/account/users-v2/users-v2.go b/cmd/account/users-v2/users-v2.go index 3a401ce16cd..baaec49dfc6 100755 --- a/cmd/account/users-v2/users-v2.go +++ b/cmd/account/users-v2/users-v2.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -86,6 +90,8 @@ func newCreate() *cobra.Command { to the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -155,6 +161,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a user in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -219,6 +227,8 @@ func newGet() *cobra.Command { ID: Unique ID for a user in the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -292,6 +302,8 @@ func newList() *cobra.Command { Gets details for all the users associated with a Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -357,6 +369,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -436,6 +450,8 @@ func newUpdate() *cobra.Command { ID: Databricks user ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/vpc-endpoints/vpc-endpoints.go b/cmd/account/vpc-endpoints/vpc-endpoints.go index ee2bf1aa6a6..d05ae2fc063 100755 --- a/cmd/account/vpc-endpoints/vpc-endpoints.go +++ b/cmd/account/vpc-endpoints/vpc-endpoints.go @@ -24,6 +24,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -79,6 +83,8 @@ func newCreate() *cobra.Command { [endpoint service]: https://docs.aws.amazon.com/vpc/latest/privatelink/privatelink-share-your-services.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -145,6 +151,8 @@ func newDelete() *cobra.Command { endpoint configuration that is associated with any workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -206,6 +214,8 @@ func newGet() *cobra.Command { VPC_ENDPOINT_ID: Databricks VPC endpoint ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -257,6 +267,8 @@ func newList() *cobra.Command { Lists Databricks VPC endpoint configurations for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/account/workspace-assignment/workspace-assignment.go b/cmd/account/workspace-assignment/workspace-assignment.go index 43146462329..08895b11b96 100755 --- a/cmd/account/workspace-assignment/workspace-assignment.go +++ b/cmd/account/workspace-assignment/workspace-assignment.go @@ -27,6 +27,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -67,6 +71,8 @@ func newDelete() *cobra.Command { PRINCIPAL_ID: The ID of the user, service principal, or group.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -131,6 +137,8 @@ func newGet() *cobra.Command { WORKSPACE_ID: The workspace ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -201,6 +209,8 @@ func newList() *cobra.Command { WORKSPACE_ID: The workspace ID for the account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -271,6 +281,8 @@ func newUpdate() *cobra.Command { PRINCIPAL_ID: The ID of the user, service principal, or group.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/account/workspace-network-configuration/workspace-network-configuration.go b/cmd/account/workspace-network-configuration/workspace-network-configuration.go index 4dd194c9462..58ef41e0119 100755 --- a/cmd/account/workspace-network-configuration/workspace-network-configuration.go +++ b/cmd/account/workspace-network-configuration/workspace-network-configuration.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGetWorkspaceNetworkOptionRpc()) cmd.AddCommand(newUpdateWorkspaceNetworkOptionRpc()) @@ -70,6 +74,8 @@ func newGetWorkspaceNetworkOptionRpc() *cobra.Command { WORKSPACE_ID: The workspace ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -139,6 +145,8 @@ func newUpdateWorkspaceNetworkOptionRpc() *cobra.Command { WORKSPACE_ID: The workspace ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/account/workspaces/workspaces.go b/cmd/account/workspaces/workspaces.go index 4ee0ab08f52..1025f94e68b 100755 --- a/cmd/account/workspaces/workspaces.go +++ b/cmd/account/workspaces/workspaces.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -140,6 +144,8 @@ func newCreate() *cobra.Command { [Create a new workspace using the Account API]: http://docs.databricks.com/administration-guide/account-api/new-workspace.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -216,6 +222,8 @@ func newDelete() *cobra.Command { Deletes a Databricks workspace, both specified by ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -281,6 +289,8 @@ func newGet() *cobra.Command { [Create a new workspace using the Account API]: http://docs.databricks.com/administration-guide/account-api/new-workspace.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -335,6 +345,8 @@ func newList() *cobra.Command { Lists Databricks workspaces for an account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustAccountClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -422,6 +434,8 @@ func newUpdate() *cobra.Command { WORKSPACE_ID: A unique integer ID for the workspace` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/aitools/aitools.go b/cmd/aitools/aitools.go new file mode 100644 index 00000000000..99ffbb8bc82 --- /dev/null +++ b/cmd/aitools/aitools.go @@ -0,0 +1,25 @@ +package aitools + +import ( + "github.com/spf13/cobra" +) + +func NewAitoolsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "aitools", + Short: "Databricks AI Tools for coding agents", + Long: `Install Databricks skills into your coding agent so it can work +effectively with Databricks resources (bundles, jobs, SQL, and more). + +Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub +Copilot, Antigravity.`, + } + + cmd.AddCommand(NewInstallCmd()) + cmd.AddCommand(NewUpdateCmd()) + cmd.AddCommand(NewUninstallCmd()) + cmd.AddCommand(NewListCmd()) + cmd.AddCommand(NewVersionCmd()) + + return cmd +} diff --git a/experimental/aitools/cmd/flags.go b/cmd/aitools/flags.go similarity index 100% rename from experimental/aitools/cmd/flags.go rename to cmd/aitools/flags.go diff --git a/experimental/aitools/cmd/flags_test.go b/cmd/aitools/flags_test.go similarity index 100% rename from experimental/aitools/cmd/flags_test.go rename to cmd/aitools/flags_test.go diff --git a/experimental/aitools/cmd/install.go b/cmd/aitools/install.go similarity index 66% rename from experimental/aitools/cmd/install.go rename to cmd/aitools/install.go index b6e87d68b1e..ab51ad8f9c7 100644 --- a/experimental/aitools/cmd/install.go +++ b/cmd/aitools/install.go @@ -6,15 +6,52 @@ import ( "fmt" "strings" - "github.com/databricks/cli/experimental/aitools/lib/agents" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/charmbracelet/huh" + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" - "github.com/fatih/color" "github.com/spf13/cobra" ) -func newInstallCmd() *cobra.Command { - var skillsFlag, agentsFlag string +// Package-level for testability. Tests in this package override them via +// helpers in install_test.go. +var ( + promptAgentSelection = defaultPromptAgentSelection + installSkillsForAgentsFn = installer.InstallSkillsForAgents +) + +func defaultPromptAgentSelection(ctx context.Context, detected []*agents.Agent) ([]*agents.Agent, error) { + options := make([]huh.Option[string], 0, len(detected)) + agentsByName := make(map[string]*agents.Agent, len(detected)) + for _, a := range detected { + options = append(options, huh.NewOption(a.DisplayName, a.Name).Selected(true)) + agentsByName[a.Name] = a + } + + var selected []string + err := huh.NewMultiSelect[string](). + Title("Select coding agents to install skills for"). + Description("space to toggle, enter to confirm"). + Options(options...). + Value(&selected). + Run() + if err != nil { + return nil, err + } + + if len(selected) == 0 { + return nil, errors.New("at least one agent must be selected") + } + + result := make([]*agents.Agent, 0, len(selected)) + for _, name := range selected { + result = append(result, agentsByName[name]) + } + return result, nil +} + +func NewInstallCmd() *cobra.Command { + var skillsFlag, agentsFlag, scopeFlag string var includeExperimental bool var projectFlag, globalFlag bool @@ -24,15 +61,30 @@ func newInstallCmd() *cobra.Command { Long: `Install Databricks AI skills for detected coding agents. By default, skills are installed globally to each agent's skills directory. -Use --project to install to the current project directory instead. +Use --scope=project to install to the current project directory instead. When multiple agents are detected, skills are stored in a canonical location and symlinked to each agent to avoid duplication. +Use --skills name1,name2 to install specific skills. + +Agent selection: + --agents [,...] Install only for the named agents. + (unset, interactive) Multi-select prompt over detected agents. + (unset, non-interactive) Install for every detected agent. + +The list of agents the command will act on is always logged to stderr before +the install runs, so callers can verify what was picked. + Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity`, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() + projectFlag, globalFlag, err := parseScopeFlag(scopeFlag, projectFlag, globalFlag, false) + if err != nil { + return err + } + // Resolve scope. scope, err := resolveScopeWithPrompt(ctx, projectFlag, globalFlag) if err != nil { @@ -91,8 +143,10 @@ Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Anti cmd.Flags().StringVar(&skillsFlag, "skills", "", "Specific skills to install (comma-separated)") cmd.Flags().StringVar(&agentsFlag, "agents", "", "Agents to install for (comma-separated, e.g. claude-code,cursor)") cmd.Flags().BoolVar(&includeExperimental, "experimental", false, "Include experimental skills") + cmd.Flags().StringVar(&scopeFlag, "scope", "", "Install scope: project or global (default: global, or prompt when interactive)") cmd.Flags().BoolVar(&projectFlag, "project", false, "Install to project directory (cwd)") cmd.Flags().BoolVar(&globalFlag, "global", false, "Install globally (default)") + markScopeBoolsDeprecated(cmd) return cmd } @@ -141,7 +195,7 @@ func filterProjectScopeAgents(detected []*agents.Agent) []*agents.Agent { // printNoAgentsMessage prints the "no agents detected" message. func printNoAgentsMessage(ctx context.Context) { - cmdio.LogString(ctx, color.YellowString("No supported coding agents detected.")) + cmdio.LogString(ctx, cmdio.Yellow(ctx, "No supported coding agents detected.")) cmdio.LogString(ctx, "") cmdio.LogString(ctx, "Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity") cmdio.LogString(ctx, "Please install at least one coding agent first.") diff --git a/experimental/aitools/cmd/install_test.go b/cmd/aitools/install_test.go similarity index 80% rename from experimental/aitools/cmd/install_test.go rename to cmd/aitools/install_test.go index 38639705ea4..22e57ee0aa9 100644 --- a/experimental/aitools/cmd/install_test.go +++ b/cmd/aitools/install_test.go @@ -7,8 +7,8 @@ import ( "path/filepath" "testing" - "github.com/databricks/cli/experimental/aitools/lib/agents" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -53,6 +53,7 @@ func setupTestAgents(t *testing.T) string { t.Helper() tmp := t.TempDir() t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) // Create config dirs for two agents. require.NoError(t, os.MkdirAll(filepath.Join(tmp, ".claude"), 0o755)) require.NoError(t, os.MkdirAll(filepath.Join(tmp, ".cursor"), 0o755)) @@ -64,7 +65,7 @@ func TestInstallAllSkillsForAllAgents(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -80,7 +81,7 @@ func TestInstallSpecificSkills(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--skills", "databricks,databricks-apps"}) @@ -96,7 +97,7 @@ func TestInstallSingleSkill(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--skills", "databricks"}) @@ -112,7 +113,7 @@ func TestInstallSpecificAgents(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--agents", "claude-code"}) @@ -128,7 +129,7 @@ func TestInstallUnknownAgentErrors(t *testing.T) { setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--agents", "invalid-agent"}) cmd.SilenceErrors = true @@ -145,7 +146,7 @@ func TestInstallIncludeExperimental(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--experimental"}) @@ -185,7 +186,7 @@ func TestInstallInteractivePrompt(t *testing.T) { go drain(test.Stdout) go drain(test.Stderr) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -210,7 +211,7 @@ func TestInstallNonInteractiveUsesAllAgents(t *testing.T) { } ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -224,11 +225,12 @@ func TestInstallNonInteractiveUsesAllAgents(t *testing.T) { func TestInstallNoAgentsDetected(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -250,7 +252,7 @@ func TestInstallAgentsFlagSkipsPrompt(t *testing.T) { } ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--agents", "claude-code,cursor"}) @@ -262,103 +264,11 @@ func TestInstallAgentsFlagSkipsPrompt(t *testing.T) { assert.Equal(t, []string{"claude-code", "cursor"}, (*calls)[0].agents) } -func TestSkillsInstallDelegatesToInstall(t *testing.T) { - setupTestAgents(t) - calls := setupInstallMock(t) - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - - err := cmd.RunE(cmd, nil) - require.NoError(t, err) - - require.Len(t, *calls, 1) - assert.Len(t, (*calls)[0].agents, 2) -} - -func TestSkillsInstallForwardsSkillName(t *testing.T) { - setupTestAgents(t) - calls := setupInstallMock(t) - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - - err := cmd.RunE(cmd, []string{"databricks"}) - require.NoError(t, err) - - require.Len(t, *calls, 1) - assert.Equal(t, []string{"databricks"}, (*calls)[0].opts.SpecificSkills) -} - -func TestSkillsInstallExecuteNoArgs(t *testing.T) { - setupTestAgents(t) - calls := setupInstallMock(t) - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - cmd.SetArgs([]string{}) - - err := cmd.Execute() - require.NoError(t, err) - - require.Len(t, *calls, 1) - assert.Len(t, (*calls)[0].agents, 2) - assert.Nil(t, (*calls)[0].opts.SpecificSkills) -} - -func TestSkillsInstallExecuteWithSkillName(t *testing.T) { - setupTestAgents(t) - calls := setupInstallMock(t) - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - cmd.SetArgs([]string{"databricks"}) - - err := cmd.Execute() - require.NoError(t, err) - - require.Len(t, *calls, 1) - assert.Equal(t, []string{"databricks"}, (*calls)[0].opts.SpecificSkills) -} - -func TestSkillsInstallForwardsExperimental(t *testing.T) { - setupTestAgents(t) - calls := setupInstallMock(t) - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - cmd.SetArgs([]string{"--experimental"}) - - err := cmd.Execute() - require.NoError(t, err) - - require.Len(t, *calls, 1) - assert.True(t, (*calls)[0].opts.IncludeExperimental, "--experimental should be forwarded") -} - -func TestSkillsInstallExecuteRejectsTwoArgs(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsInstallCmd() - cmd.SetContext(ctx) - cmd.SetArgs([]string{"a", "b"}) - cmd.SilenceErrors = true - cmd.SilenceUsage = true - - err := cmd.Execute() - require.Error(t, err) - assert.Contains(t, err.Error(), "accepts at most 1 arg") -} - func TestInstallRejectsPositionalArgs(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) - cmd.SetArgs([]string{"databricks"}) + cmd.SetArgs([]string{"databricks-jobs"}) cmd.SilenceErrors = true cmd.SilenceUsage = true @@ -369,7 +279,7 @@ func TestInstallRejectsPositionalArgs(t *testing.T) { func TestUpdateRejectsPositionalArgs(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - cmd := newUpdateCmd() + cmd := NewUpdateCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"databricks"}) cmd.SilenceErrors = true @@ -382,7 +292,7 @@ func TestUpdateRejectsPositionalArgs(t *testing.T) { func TestUninstallRejectsPositionalArgs(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - cmd := newUninstallCmd() + cmd := NewUninstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"databricks"}) cmd.SilenceErrors = true @@ -395,7 +305,7 @@ func TestUninstallRejectsPositionalArgs(t *testing.T) { func TestListRejectsPositionalArgs(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - cmd := newListCmd() + cmd := NewListCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"databricks"}) cmd.SilenceErrors = true @@ -408,7 +318,7 @@ func TestListRejectsPositionalArgs(t *testing.T) { func TestVersionRejectsPositionalArgs(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - cmd := newVersionCmd() + cmd := NewVersionCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"databricks"}) cmd.SilenceErrors = true @@ -458,7 +368,7 @@ func TestInstallProjectFlag(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--project"}) @@ -474,7 +384,7 @@ func TestInstallGlobalFlag(t *testing.T) { calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--global"}) @@ -490,7 +400,7 @@ func TestInstallGlobalAndProjectErrors(t *testing.T) { setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) cmd.SetArgs([]string{"--global", "--project"}) cmd.SilenceErrors = true @@ -501,12 +411,51 @@ func TestInstallGlobalAndProjectErrors(t *testing.T) { assert.Contains(t, err.Error(), "cannot use --global and --project together") } +func TestInstallScopeFlag(t *testing.T) { + tests := []struct { + name string + args []string + wantScope string + wantErr string + }{ + {name: "scope project", args: []string{"--scope", "project"}, wantScope: installer.ScopeProject}, + {name: "scope global", args: []string{"--scope", "global"}, wantScope: installer.ScopeGlobal}, + {name: "scope both rejected", args: []string{"--scope", "both"}, wantErr: "--scope=both is not supported"}, + {name: "scope invalid value", args: []string{"--scope", "all"}, wantErr: `invalid --scope "all"`}, + {name: "scope conflicts with legacy", args: []string{"--scope", "global", "--project"}, wantErr: "cannot use --scope with --project or --global"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := NewInstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs(tt.args) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + err := cmd.Execute() + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + require.Len(t, *calls, 1) + assert.Equal(t, tt.wantScope, (*calls)[0].opts.Scope) + }) + } +} + func TestInstallNoFlagNonInteractiveUsesGlobal(t *testing.T) { setupTestAgents(t) calls := setupInstallMock(t) ctx := cmdio.MockDiscard(t.Context()) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -543,7 +492,7 @@ func TestInstallNoFlagInteractiveShowsScopePrompt(t *testing.T) { go drain(test.Stdout) go drain(test.Stderr) - cmd := newInstallCmd() + cmd := NewInstallCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) diff --git a/cmd/aitools/legacy_skills.go b/cmd/aitools/legacy_skills.go new file mode 100644 index 00000000000..d258039f105 --- /dev/null +++ b/cmd/aitools/legacy_skills.go @@ -0,0 +1,62 @@ +package aitools + +import ( + "github.com/spf13/cobra" +) + +// NewLegacySkillsCmd returns the deprecated `skills` subgroup used under +// `databricks experimental aitools`. It is only mounted there for backward +// compatibility; new code should call the top-level install/list commands. +func NewLegacySkillsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "skills", + Hidden: true, + Short: "Manage Databricks skills for coding agents", + Long: `Manage Databricks skills that extend coding agents with Databricks-specific capabilities.`, + Deprecated: `use "databricks aitools" instead.`, + } + + cmd.AddCommand(newLegacySkillsListCmd()) + cmd.AddCommand(newLegacySkillsInstallCmd()) + + return cmd +} + +func newLegacySkillsListCmd() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List available skills", + Deprecated: `use "databricks aitools list" instead.`, + RunE: func(cmd *cobra.Command, args []string) error { + return listSkillsFn(cmd, "") + }, + } +} + +func newLegacySkillsInstallCmd() *cobra.Command { + var includeExperimental bool + + cmd := &cobra.Command{ + Use: "install [skill-name]", + Short: "Install Databricks skills for detected coding agents", + Deprecated: `use "databricks aitools install --skills " instead.`, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + installCmd := NewInstallCmd() + installCmd.SetContext(cmd.Context()) + + var delegateArgs []string + if len(args) > 0 { + delegateArgs = append(delegateArgs, "--skills", args[0]) + } + if includeExperimental { + delegateArgs = append(delegateArgs, "--experimental") + } + installCmd.SetArgs(delegateArgs) + return installCmd.Execute() + }, + } + + cmd.Flags().BoolVar(&includeExperimental, "experimental", false, "Include experimental skills") + return cmd +} diff --git a/cmd/aitools/legacy_skills_test.go b/cmd/aitools/legacy_skills_test.go new file mode 100644 index 00000000000..250d910960b --- /dev/null +++ b/cmd/aitools/legacy_skills_test.go @@ -0,0 +1,121 @@ +package aitools + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLegacySkillsInstallDelegatesToInstall(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + + err := cmd.RunE(cmd, nil) + require.NoError(t, err) + + require.Len(t, *calls, 1) + assert.Len(t, (*calls)[0].agents, 2) +} + +func TestLegacySkillsInstallForwardsSkillName(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + + err := cmd.RunE(cmd, []string{"databricks"}) + require.NoError(t, err) + + require.Len(t, *calls, 1) + assert.Equal(t, []string{"databricks"}, (*calls)[0].opts.SpecificSkills) +} + +func TestLegacySkillsInstallExecuteNoArgs(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs([]string{}) + + err := cmd.Execute() + require.NoError(t, err) + + require.Len(t, *calls, 1) + assert.Len(t, (*calls)[0].agents, 2) + assert.Nil(t, (*calls)[0].opts.SpecificSkills) +} + +func TestLegacySkillsInstallExecuteWithSkillName(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs([]string{"databricks"}) + + err := cmd.Execute() + require.NoError(t, err) + + require.Len(t, *calls, 1) + assert.Equal(t, []string{"databricks"}, (*calls)[0].opts.SpecificSkills) +} + +func TestLegacySkillsInstallForwardsExperimental(t *testing.T) { + setupTestAgents(t) + calls := setupInstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs([]string{"--experimental"}) + + err := cmd.Execute() + require.NoError(t, err) + + require.Len(t, *calls, 1) + assert.True(t, (*calls)[0].opts.IncludeExperimental, "--experimental should be forwarded") +} + +func TestLegacySkillsInstallExecuteRejectsTwoArgs(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsInstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs([]string{"a", "b"}) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + err := cmd.Execute() + require.Error(t, err) + assert.Contains(t, err.Error(), "accepts at most 1 arg") +} + +func TestLegacySkillsListDelegatesToListFn(t *testing.T) { + orig := listSkillsFn + t.Cleanup(func() { listSkillsFn = orig }) + + called := false + listSkillsFn = func(cmd *cobra.Command, scope string) error { + called = true + return nil + } + + ctx := cmdio.MockDiscard(t.Context()) + cmd := newLegacySkillsListCmd() + cmd.SetContext(ctx) + + err := cmd.RunE(cmd, nil) + require.NoError(t, err) + assert.True(t, called) +} diff --git a/cmd/aitools/list.go b/cmd/aitools/list.go new file mode 100644 index 00000000000..7e82ff348e6 --- /dev/null +++ b/cmd/aitools/list.go @@ -0,0 +1,275 @@ +package aitools + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "maps" + "slices" + "strings" + "text/tabwriter" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/aitools/installer" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/cli/libs/log" + "github.com/spf13/cobra" +) + +// listSkillsFn is the function used to render the skills list. +// It is a package-level var so tests can replace the data-fetching layer. +var listSkillsFn = defaultListSkills + +func NewListCmd() *cobra.Command { + var scopeFlag string + var projectFlag, globalFlag bool + + cmd := &cobra.Command{ + Use: "list", + Short: "List installed AI tools components", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + // Reject the legacy --project --global combination here so it + // doesn't silently degrade to --scope=both. Users who want both + // scopes should use --scope=both (the new explicit spelling). + if projectFlag && globalFlag && scopeFlag == "" { + return errors.New("cannot use --global and --project together") + } + + projectFlag, globalFlag, err := parseScopeFlag(scopeFlag, projectFlag, globalFlag, true) + if err != nil { + return err + } + + // list: empty scope = show both. --scope=both also lands here. + var scope string + switch { + case projectFlag && !globalFlag: + scope = installer.ScopeProject + case globalFlag && !projectFlag: + scope = installer.ScopeGlobal + } + return listSkillsFn(cmd, scope) + }, + } + + cmd.Flags().StringVar(&scopeFlag, "scope", "", "Scope to show: project, global, or both (default: both)") + cmd.Flags().BoolVar(&projectFlag, "project", false, "Show only project-scoped skills") + cmd.Flags().BoolVar(&globalFlag, "global", false, "Show only globally-scoped skills") + markScopeBoolsDeprecated(cmd) + return cmd +} + +// listOutput is the structured representation of `aitools list` used by both +// text rendering and `--output json` consumers. The JSON shape is part of +// the public CLI contract; do not break field names or types. +type listOutput struct { + Release string `json:"release"` + Skills []skillEntry `json:"skills"` + Summary map[string]scopeSummary `json:"summary"` +} + +type skillEntry struct { + Name string `json:"name"` + LatestVersion string `json:"latest_version"` + Experimental bool `json:"experimental"` + Installed map[string]string `json:"installed"` +} + +type scopeSummary struct { + Installed int `json:"installed"` + Total int `json:"total"` + + // loaded preserves text rendering semantics without changing the JSON contract. + loaded bool +} + +func defaultListSkills(cmd *cobra.Command, scope string) error { + ctx := cmd.Context() + + out, err := buildListOutput(ctx, scope) + if err != nil { + return err + } + + switch root.OutputType(cmd) { + case flags.OutputJSON: + return renderListJSON(cmd.OutOrStdout(), out) + default: + renderListText(ctx, out, scope) + return nil + } +} + +// buildListOutput fetches the manifest and per-scope install state and +// returns the structured listOutput. scope=="" loads both scopes; "global" +// or "project" loads only that scope. +func buildListOutput(ctx context.Context, scope string) (listOutput, error) { + ref, explicit, err := installer.GetSkillsRef(ctx) + if err != nil { + return listOutput{}, err + } + + src := &installer.GitHubManifestSource{} + manifest, ref, err := installer.FetchSkillsManifestWithFallback(ctx, src, ref, !explicit) + if err != nil { + return listOutput{}, fmt.Errorf("failed to fetch manifest: %w", err) + } + + globalState := loadStateForScope(ctx, scope, installer.ScopeProject, installer.GlobalSkillsDir, "global") + projectState := loadStateForScope(ctx, scope, installer.ScopeGlobal, installer.ProjectSkillsDir, "project") + + names := slices.Sorted(maps.Keys(manifest.Skills)) + + out := listOutput{ + Release: strings.TrimPrefix(ref, "v"), + Skills: make([]skillEntry, 0, len(names)), + Summary: map[string]scopeSummary{}, + } + + globalCount, projectCount := 0, 0 + for _, name := range names { + meta := manifest.Skills[name] + entry := skillEntry{ + Name: name, + LatestVersion: meta.Version, + Experimental: meta.IsExperimental(), + Installed: map[string]string{}, + } + if globalState != nil { + if v, ok := globalState.Skills[name]; ok { + entry.Installed[installer.ScopeGlobal] = v + globalCount++ + } + } + if projectState != nil { + if v, ok := projectState.Skills[name]; ok { + entry.Installed[installer.ScopeProject] = v + projectCount++ + } + } + out.Skills = append(out.Skills, entry) + } + + // Include a summary entry for every scope that was queried, even when the + // install state is missing — agents should see "0/N" rather than guess + // from the absence of a key. + if scope != installer.ScopeProject { + out.Summary[installer.ScopeGlobal] = scopeSummary{Installed: globalCount, Total: len(names), loaded: globalState != nil} + } + if scope != installer.ScopeGlobal { + out.Summary[installer.ScopeProject] = scopeSummary{Installed: projectCount, Total: len(names), loaded: projectState != nil} + } + + return out, nil +} + +// loadStateForScope returns the install state for the named scope when the +// scope filter allows it. excludeScope is the scope value that means "skip +// loading this one" (so passing ScopeProject to the global loader skips +// global when --scope=project). +func loadStateForScope(ctx context.Context, scopeFilter, excludeScope string, dirFn func(context.Context) (string, error), label string) *installer.InstallState { + if scopeFilter == excludeScope { + return nil + } + dir, err := dirFn(ctx) + if err != nil { + return nil + } + state, err := installer.LoadState(dir) + if err != nil { + log.Debugf(ctx, "Could not load %s install state: %v", label, err) + return nil + } + return state +} + +func renderListJSON(w io.Writer, out listOutput) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(out) +} + +func renderListText(ctx context.Context, out listOutput, scope string) { + cmdio.LogString(ctx, "Available skills (v"+out.Release+"):") + cmdio.LogString(ctx, "") + + bothScopes := scope == "" && + out.Summary[installer.ScopeGlobal].loaded && + out.Summary[installer.ScopeProject].loaded + + var buf strings.Builder + tw := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0) + fmt.Fprintln(tw, " NAME\tVERSION\tINSTALLED") + for _, s := range out.Skills { + tag := "" + if s.Experimental { + tag = " [experimental]" + } + fmt.Fprintf(tw, " %s%s\tv%s\t%s\n", s.Name, tag, s.LatestVersion, installedStatusFromEntry(s, bothScopes)) + } + tw.Flush() + cmdio.LogString(ctx, buf.String()) + + cmdio.LogString(ctx, summaryLine(out, scope)) +} + +func installedStatusFromEntry(s skillEntry, bothScopes bool) string { + globalVer := s.Installed[installer.ScopeGlobal] + projectVer := s.Installed[installer.ScopeProject] + + if globalVer == "" && projectVer == "" { + return "not installed" + } + + if bothScopes && globalVer != "" && projectVer != "" { + return versionLabel(projectVer, s.LatestVersion) + " (project, global)" + } + + if projectVer != "" { + label := versionLabel(projectVer, s.LatestVersion) + if bothScopes { + return label + " (project)" + } + return label + } + + label := versionLabel(globalVer, s.LatestVersion) + if bothScopes { + return label + " (global)" + } + return label +} + +// versionLabel formats version with update status. +func versionLabel(installed, latest string) string { + if installed == latest { + return "v" + installed + " (up to date)" + } + return "v" + installed + " (update available)" +} + +func summaryLine(out listOutput, scope string) string { + g, gOK := out.Summary[installer.ScopeGlobal] + p, pOK := out.Summary[installer.ScopeProject] + + switch { + case gOK && pOK: + // Mirror prior behavior: only print the dual-scope line when both + // scopes have a state file; otherwise only mention the one that does. + if g.loaded && p.loaded { + return fmt.Sprintf("%d/%d skills installed (global), %d/%d (project)", g.Installed, g.Total, p.Installed, p.Total) + } + if p.loaded { + return fmt.Sprintf("%d/%d skills installed (project)", p.Installed, p.Total) + } + return fmt.Sprintf("%d/%d skills installed (global)", g.Installed, g.Total) + case pOK: + return fmt.Sprintf("%d/%d skills installed (project)", p.Installed, p.Total) + default: + return fmt.Sprintf("%d/%d skills installed (global)", g.Installed, g.Total) + } +} diff --git a/cmd/aitools/list_test.go b/cmd/aitools/list_test.go new file mode 100644 index 00000000000..e8ed690ca57 --- /dev/null +++ b/cmd/aitools/list_test.go @@ -0,0 +1,298 @@ +package aitools + +import ( + "bytes" + "encoding/json" + "testing" + + "github.com/databricks/cli/libs/aitools/installer" + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListCommandExists(t *testing.T) { + cmd := NewListCmd() + assert.Equal(t, "list", cmd.Use) +} + +func TestListCommandCallsListFn(t *testing.T) { + orig := listSkillsFn + t.Cleanup(func() { listSkillsFn = orig }) + + called := false + listSkillsFn = func(cmd *cobra.Command, scope string) error { + called = true + return nil + } + + ctx := cmdio.MockDiscard(t.Context()) + cmd := NewListCmd() + cmd.SetContext(ctx) + + err := cmd.RunE(cmd, nil) + require.NoError(t, err) + assert.True(t, called) +} + +func TestListCommandHasScopeFlags(t *testing.T) { + cmd := NewListCmd() + f := cmd.Flags().Lookup("project") + require.NotNil(t, f, "--project flag should exist (deprecated alias)") + assert.NotEmpty(t, f.Deprecated, "--project should be marked deprecated") + f = cmd.Flags().Lookup("global") + require.NotNil(t, f, "--global flag should exist (deprecated alias)") + assert.NotEmpty(t, f.Deprecated, "--global should be marked deprecated") + f = cmd.Flags().Lookup("scope") + require.NotNil(t, f, "--scope flag should exist") +} + +func TestRenderListJSON(t *testing.T) { + out := listOutput{ + Release: "0.1.0", + Skills: []skillEntry{ + { + Name: "databricks-jobs", + LatestVersion: "1.0.0", + Experimental: false, + Installed: map[string]string{ + installer.ScopeGlobal: "1.0.0", + installer.ScopeProject: "0.9.0", + }, + }, + { + Name: "experimental-thing", + LatestVersion: "0.1.0", + Experimental: true, + Installed: map[string]string{}, + }, + }, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 1, Total: 2}, + installer.ScopeProject: {Installed: 1, Total: 2}, + }, + } + + var buf bytes.Buffer + require.NoError(t, renderListJSON(&buf, out)) + + var got listOutput + require.NoError(t, json.Unmarshal(buf.Bytes(), &got)) + assert.Equal(t, out, got) + + var raw map[string]any + require.NoError(t, json.Unmarshal(buf.Bytes(), &raw)) + assert.Contains(t, raw, "release") + assert.Contains(t, raw, "skills") + assert.Contains(t, raw, "summary") + + skills := raw["skills"].([]any) + first := skills[0].(map[string]any) + assert.Equal(t, "databricks-jobs", first["name"]) + assert.Equal(t, "1.0.0", first["latest_version"]) + assert.Equal(t, false, first["experimental"]) + + installed := first["installed"].(map[string]any) + assert.Equal(t, "1.0.0", installed["global"]) + assert.Equal(t, "0.9.0", installed["project"]) + + second := skills[1].(map[string]any) + assert.Equal(t, true, second["experimental"]) + assert.Empty(t, second["installed"]) +} + +func TestRenderListJSONScopeFiltersSummary(t *testing.T) { + out := listOutput{ + Release: "0.1.0", + Skills: []skillEntry{}, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 0, Total: 5}, + }, + } + + var buf bytes.Buffer + require.NoError(t, renderListJSON(&buf, out)) + + var raw map[string]any + require.NoError(t, json.Unmarshal(buf.Bytes(), &raw)) + summary := raw["summary"].(map[string]any) + assert.Contains(t, summary, "global") + assert.NotContains(t, summary, "project") +} + +func TestInstalledStatusFromEntry(t *testing.T) { + tests := []struct { + name string + entry skillEntry + bothScopes bool + want string + }{ + { + name: "not installed", + entry: skillEntry{LatestVersion: "1.0.0", Installed: map[string]string{}}, + want: "not installed", + }, + { + name: "global up to date", + entry: skillEntry{ + LatestVersion: "1.0.0", + Installed: map[string]string{installer.ScopeGlobal: "1.0.0"}, + }, + want: "v1.0.0 (up to date)", + }, + { + name: "project update available", + entry: skillEntry{ + LatestVersion: "1.0.0", + Installed: map[string]string{installer.ScopeProject: "0.9.0"}, + }, + want: "v0.9.0 (update available)", + }, + { + name: "both scopes installed", + entry: skillEntry{ + LatestVersion: "1.0.0", + Installed: map[string]string{ + installer.ScopeGlobal: "1.0.0", + installer.ScopeProject: "0.9.0", + }, + }, + bothScopes: true, + want: "v0.9.0 (update available) (project, global)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, installedStatusFromEntry(tt.entry, tt.bothScopes)) + }) + } +} + +func TestSummaryLinePreservesStatePresence(t *testing.T) { + tests := []struct { + name string + out listOutput + want string + }{ + { + name: "both state files loaded even with no installs", + out: listOutput{ + Skills: []skillEntry{ + {Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}}, + }, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 0, Total: 1, loaded: true}, + installer.ScopeProject: {Installed: 0, Total: 1, loaded: true}, + }, + }, + want: "0/1 skills installed (global), 0/1 (project)", + }, + { + name: "only project state loaded", + out: listOutput{ + Skills: []skillEntry{ + {Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}}, + }, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 0, Total: 1}, + installer.ScopeProject: {Installed: 0, Total: 1, loaded: true}, + }, + }, + want: "0/1 skills installed (project)", + }, + { + name: "only global state loaded", + out: listOutput{ + Skills: []skillEntry{ + {Name: "databricks-jobs", LatestVersion: "1.0.0", Installed: map[string]string{}}, + }, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 0, Total: 1, loaded: true}, + installer.ScopeProject: {Installed: 0, Total: 1}, + }, + }, + want: "0/1 skills installed (global)", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, summaryLine(tt.out, "")) + }) + } +} + +func TestRenderListTextUsesLoadedStateForScopeLabels(t *testing.T) { + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + out := listOutput{ + Release: "0.1.0", + Skills: []skillEntry{ + { + Name: "databricks-jobs", + LatestVersion: "1.0.0", + Installed: map[string]string{ + installer.ScopeGlobal: "1.0.0", + }, + }, + }, + Summary: map[string]scopeSummary{ + installer.ScopeGlobal: {Installed: 1, Total: 1, loaded: true}, + installer.ScopeProject: {Installed: 0, Total: 1, loaded: true}, + }, + } + + renderListText(ctx, out, "") + + got := stderr.String() + assert.Contains(t, got, "v1.0.0 (up to date) (global)") + assert.Contains(t, got, "1/1 skills installed (global), 0/1 (project)") +} + +func TestListScopeFlag(t *testing.T) { + tests := []struct { + name string + args []string + wantScope string + wantErr string + }{ + {name: "scope project", args: []string{"--scope", "project"}, wantScope: installer.ScopeProject}, + {name: "scope global", args: []string{"--scope", "global"}, wantScope: installer.ScopeGlobal}, + {name: "scope both shows both", args: []string{"--scope", "both"}, wantScope: ""}, + {name: "scope invalid", args: []string{"--scope", "all"}, wantErr: `invalid --scope "all"`}, + {name: "legacy both flags together rejected", args: []string{"--project", "--global"}, wantErr: "cannot use --global and --project together"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + orig := listSkillsFn + t.Cleanup(func() { listSkillsFn = orig }) + + var gotScope string + called := false + listSkillsFn = func(_ *cobra.Command, scope string) error { + called = true + gotScope = scope + return nil + } + + ctx := cmdio.MockDiscard(t.Context()) + cmd := NewListCmd() + cmd.SetContext(ctx) + cmd.SetArgs(tt.args) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + err := cmd.Execute() + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.True(t, called) + assert.Equal(t, tt.wantScope, gotScope) + }) + } +} diff --git a/experimental/aitools/cmd/scope.go b/cmd/aitools/scope.go similarity index 77% rename from experimental/aitools/cmd/scope.go rename to cmd/aitools/scope.go index 8c6ce0f0130..15f00825399 100644 --- a/experimental/aitools/cmd/scope.go +++ b/cmd/aitools/scope.go @@ -8,9 +8,10 @@ import ( "path/filepath" "github.com/charmbracelet/huh" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" + "github.com/spf13/cobra" ) // promptScopeSelection is a package-level var so tests can replace it with a mock. @@ -82,6 +83,53 @@ func defaultPromptScopeSelection(ctx context.Context) (string, error) { const scopeBoth = "both" +// markScopeBoolsDeprecated hides --project and --global from help and emits a +// stderr warning pointing at --scope when they're used. The booleans are kept +// so existing scripts and the experimental backward-compat aliases keep +// working through the next release. +func markScopeBoolsDeprecated(cmd *cobra.Command) { + cmd.Flags().Lookup("project").Deprecated = "use --scope=project" + cmd.Flags().Lookup("project").Hidden = true + cmd.Flags().Lookup("global").Deprecated = "use --scope=global" + cmd.Flags().Lookup("global").Hidden = true +} + +// parseScopeFlag translates --scope into the equivalent --project/--global bool pair. +// Returns (projectFlag, globalFlag, nil) unchanged when --scope is empty so the +// deprecated booleans can keep flowing through the existing resolveScope* helpers +// (including update's supported `--project --global` "both scopes" path). Errors +// if --scope is combined with --project or --global. When allowBoth is false, +// --scope=both is rejected up front so install and uninstall don't have to +// special-case it. +// +// Note: install/list/uninstall reject the legacy `--project --global` combination +// at their own RunE / resolveScope layer; update intentionally accepts it as the +// "both scopes" path until those flags are removed. +func parseScopeFlag(scopeFlag string, projectFlag, globalFlag, allowBoth bool) (proj, glob bool, err error) { + if scopeFlag == "" { + return projectFlag, globalFlag, nil + } + if projectFlag || globalFlag { + return false, false, errors.New("cannot use --scope with --project or --global; --project and --global are deprecated aliases for --scope") + } + switch scopeFlag { + case installer.ScopeProject: + return true, false, nil + case installer.ScopeGlobal: + return false, true, nil + case scopeBoth: + if !allowBoth { + return false, false, errors.New("--scope=both is not supported for this command; use 'project' or 'global'") + } + return true, true, nil + default: + if allowBoth { + return false, false, fmt.Errorf("invalid --scope %q: must be one of project, global, both", scopeFlag) + } + return false, false, fmt.Errorf("invalid --scope %q: must be one of project, global", scopeFlag) + } +} + // detectInstalledScopes checks which scopes have a .state.json file present. func detectInstalledScopes(globalDir, projectDir string) (global, project bool, err error) { globalState, err := installer.LoadState(globalDir) @@ -132,7 +180,7 @@ func resolveScopeForUpdate(ctx context.Context, projectFlag, globalFlag bool, gl switch { case hasGlobal && hasProject: if !cmdio.IsPromptSupported(ctx) { - return nil, errors.New("skills are installed in both global and project scopes; use --global, --project, or both flags to specify which to update") + return nil, errors.New("skills are installed in both global and project scopes; use --scope=global, --scope=project, or --scope=both to specify which to update") } scopes, err := promptUpdateScopeSelection(ctx) if err != nil { @@ -158,7 +206,7 @@ func resolveScopeForUpdate(ctx context.Context, projectFlag, globalFlag bool, gl // Unlike update, uninstall never allows "both" scopes at once. func resolveScopeForUninstall(ctx context.Context, projectFlag, globalFlag bool, globalDir, projectDir string) (string, error) { if projectFlag && globalFlag { - return "", errors.New("cannot uninstall both scopes at once; run uninstall separately for --global and --project") + return "", errors.New("cannot uninstall both scopes at once; run uninstall separately with --scope=global and --scope=project") } hasGlobal, hasProject, err := detectInstalledScopes(globalDir, projectDir) @@ -182,7 +230,7 @@ func resolveScopeForUninstall(ctx context.Context, projectFlag, globalFlag bool, switch { case hasGlobal && hasProject: if !cmdio.IsPromptSupported(ctx) { - return "", errors.New("skills are installed in both global and project scopes; use --global or --project to specify which to uninstall") + return "", errors.New("skills are installed in both global and project scopes; use --scope=global or --scope=project to specify which to uninstall") } scope, err := promptUninstallScopeSelection(ctx) if err != nil { @@ -230,10 +278,10 @@ func scopeNotInstalledError(scope, verb, projectDir string, hasGlobal, hasProjec "no project-scoped skills found in the current directory.\n\n"+ "Project-scoped skills are detected based on your working directory.\n"+ "Make sure you are in the project root where you originally ran\n"+ - "'databricks experimental aitools install --project'.\n\n"+ + "'databricks aitools install --scope=project'.\n\n"+ "Expected location: %s/", expectedPath) } else { - msg = "no globally-scoped skills installed. Run 'databricks experimental aitools install --global' to install" + msg = "no globally-scoped skills installed. Run 'databricks aitools install --scope=global' to install" } hint := crossScopeHint(scope, verb, hasGlobal, hasProject) @@ -248,10 +296,10 @@ func scopeNotInstalledError(scope, verb, projectDir string, hasGlobal, hasProjec // The verb parameter (e.g. "update", "uninstall") controls the action in the hint message. func crossScopeHint(requestedScope, verb string, hasGlobal, hasProject bool) string { if requestedScope == installer.ScopeProject && hasGlobal { - return fmt.Sprintf("Global skills are installed. Run without --project to %s those.", verb) + return fmt.Sprintf("Global skills are installed. Run with --scope=global to %s those.", verb) } if requestedScope == installer.ScopeGlobal && hasProject { - return fmt.Sprintf("Project-scoped skills are installed. Run without --global to %s those.", verb) + return fmt.Sprintf("Project-scoped skills are installed. Run with --scope=project to %s those.", verb) } return "" } diff --git a/experimental/aitools/cmd/scope_test.go b/cmd/aitools/scope_test.go similarity index 85% rename from experimental/aitools/cmd/scope_test.go rename to cmd/aitools/scope_test.go index ecda25faade..da941dbb316 100644 --- a/experimental/aitools/cmd/scope_test.go +++ b/cmd/aitools/scope_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "testing" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -67,6 +67,57 @@ func interactiveCtx(t *testing.T) (context.Context, func()) { return ctx, test.Done } +// --- parseScopeFlag tests --- + +func TestParseScopeFlag(t *testing.T) { + tests := []struct { + name string + scope string + project bool + global bool + allowBoth bool + wantProj bool + wantGlob bool + wantErr string + }{ + {name: "unset", scope: ""}, + {name: "legacy project only", project: true, wantProj: true}, + {name: "legacy global only", global: true, wantGlob: true}, + {name: "legacy both passthrough", project: true, global: true, wantProj: true, wantGlob: true}, + {name: "scope project", scope: "project", wantProj: true}, + {name: "scope global", scope: "global", wantGlob: true}, + {name: "scope both allowed", scope: "both", allowBoth: true, wantProj: true, wantGlob: true}, + {name: "scope both disallowed", scope: "both", wantErr: "--scope=both is not supported"}, + {name: "scope invalid value with allowBoth", scope: "all", allowBoth: true, wantErr: `invalid --scope "all": must be one of project, global, both`}, + {name: "scope invalid value without allowBoth omits both from error", scope: "all", wantErr: `invalid --scope "all": must be one of project, global`}, + {name: "scope conflicts with project", scope: "project", project: true, wantErr: "cannot use --scope with --project or --global"}, + {name: "scope conflicts with global", scope: "global", global: true, wantErr: "cannot use --scope with --project or --global"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + proj, glob, err := parseScopeFlag(tt.scope, tt.project, tt.global, tt.allowBoth) + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantProj, proj) + assert.Equal(t, tt.wantGlob, glob) + }) + } + + // Stronger check that the without-allowBoth invalid-value branch omits + // "both" from the error message (the table assertion uses Contains which + // can't distinguish a substring shared with the allowBoth variant). + t.Run("invalid scope error message without allowBoth does not mention both", func(t *testing.T) { + _, _, err := parseScopeFlag("all", false, false, false) + require.Error(t, err) + assert.NotContains(t, err.Error(), "both") + }) +} + // --- detectInstalledScopes tests (table-driven) --- func TestDetectInstalledScopes(t *testing.T) { @@ -184,21 +235,21 @@ func TestCrossScopeHint(t *testing.T) { scope: installer.ScopeProject, verb: "update", hasGlobal: true, - wantHint: "without --project to update those", + wantHint: "with --scope=global to update those", }, { name: "ProjectMissingHintsGlobalUninstall", scope: installer.ScopeProject, verb: "uninstall", hasGlobal: true, - wantHint: "without --project to uninstall those", + wantHint: "with --scope=global to uninstall those", }, { name: "GlobalMissingHintsProject", scope: installer.ScopeGlobal, verb: "update", hasProj: true, - wantHint: "without --global to update those", + wantHint: "with --scope=project to update those", }, { name: "NeitherInstalledNoHint", @@ -225,7 +276,7 @@ func TestScopeNotInstalledErrorProjectIncludesPath(t *testing.T) { projectDir := "/some/project/.databricks/aitools/skills" err := scopeNotInstalledError(installer.ScopeProject, "update", projectDir, false, false) assert.Contains(t, err.Error(), "no project-scoped skills found") - assert.Contains(t, err.Error(), "install --project") + assert.Contains(t, err.Error(), "install --scope=project") assert.Contains(t, err.Error(), "Expected location:") assert.Contains(t, err.Error(), "/some/project/.databricks/aitools/skills/") } @@ -233,7 +284,7 @@ func TestScopeNotInstalledErrorProjectIncludesPath(t *testing.T) { func TestScopeNotInstalledErrorGlobal(t *testing.T) { err := scopeNotInstalledError(installer.ScopeGlobal, "update", "/irrelevant", false, false) assert.Contains(t, err.Error(), "no globally-scoped skills installed") - assert.Contains(t, err.Error(), "install --global") + assert.Contains(t, err.Error(), "install --scope=global") } // --- resolveScopeForUpdate tests --- @@ -307,7 +358,7 @@ func TestResolveScopeForUpdateProjectFlagNoInstall(t *testing.T) { _, err := resolveScopeForUpdate(ctx, true, false, globalDir, projectDir) require.Error(t, err) assert.Contains(t, err.Error(), "no project-scoped skills found") - assert.Contains(t, err.Error(), "install --project") + assert.Contains(t, err.Error(), "install --scope=project") assert.Contains(t, err.Error(), "Expected location:") assert.Contains(t, err.Error(), ".databricks/aitools/skills/") } @@ -351,8 +402,8 @@ func TestResolveScopeForUpdateNoFlagsBothNonInteractive(t *testing.T) { _, err := resolveScopeForUpdate(ctx, false, false, globalDir, projectDir) require.Error(t, err) assert.Contains(t, err.Error(), "skills are installed in both global and project scopes") - assert.Contains(t, err.Error(), "--global") - assert.Contains(t, err.Error(), "--project") + assert.Contains(t, err.Error(), "--scope=global") + assert.Contains(t, err.Error(), "--scope=project") } func TestResolveScopeForUpdateNoFlagsBothInteractive(t *testing.T) { @@ -425,7 +476,7 @@ func TestResolveScopeForUninstallProjectFlagNoInstall(t *testing.T) { _, err := resolveScopeForUninstall(ctx, true, false, globalDir, projectDir) require.Error(t, err) assert.Contains(t, err.Error(), "no project-scoped skills found") - assert.Contains(t, err.Error(), "install --project") + assert.Contains(t, err.Error(), "install --scope=project") assert.Contains(t, err.Error(), "Expected location:") } @@ -468,8 +519,8 @@ func TestResolveScopeForUninstallNoFlagsBothNonInteractive(t *testing.T) { _, err := resolveScopeForUninstall(ctx, false, false, globalDir, projectDir) require.Error(t, err) assert.Contains(t, err.Error(), "skills are installed in both global and project scopes") - assert.Contains(t, err.Error(), "--global") - assert.Contains(t, err.Error(), "--project") + assert.Contains(t, err.Error(), "--scope=global") + assert.Contains(t, err.Error(), "--scope=project") } func TestResolveScopeForUninstallNoFlagsBothInteractive(t *testing.T) { @@ -556,7 +607,7 @@ func TestResolveScopeForUninstallProjectFlagHintsUninstall(t *testing.T) { // Project flag with no project state should hint about global using "uninstall" verb. _, err := resolveScopeForUninstall(ctx, true, false, globalDir, projectDir) require.Error(t, err) - assert.Contains(t, err.Error(), "without --project to uninstall those") + assert.Contains(t, err.Error(), "with --scope=global to uninstall those") } func TestResolveScopeForUpdateProjectFlagHintsUpdate(t *testing.T) { @@ -567,5 +618,5 @@ func TestResolveScopeForUpdateProjectFlagHintsUpdate(t *testing.T) { // Project flag with no project state should hint about global using "update" verb. _, err := resolveScopeForUpdate(ctx, true, false, globalDir, projectDir) require.Error(t, err) - assert.Contains(t, err.Error(), "without --project to update those") + assert.Contains(t, err.Error(), "with --scope=global to update those") } diff --git a/experimental/aitools/cmd/uninstall.go b/cmd/aitools/uninstall.go similarity index 64% rename from experimental/aitools/cmd/uninstall.go rename to cmd/aitools/uninstall.go index 3eda84cfbc9..7f4b32ebf6f 100644 --- a/experimental/aitools/cmd/uninstall.go +++ b/cmd/aitools/uninstall.go @@ -1,12 +1,19 @@ package aitools import ( - "github.com/databricks/cli/experimental/aitools/lib/installer" + "context" + + "github.com/databricks/cli/libs/aitools/installer" "github.com/spf13/cobra" ) -func newUninstallCmd() *cobra.Command { - var skillsFlag string +// Package-level for testability. Tests override via uninstall_test.go. +var uninstallSkillsFn = func(ctx context.Context, opts installer.UninstallOptions) error { + return installer.UninstallSkillsOpts(ctx, opts) +} + +func NewUninstallCmd() *cobra.Command { + var skillsFlag, scopeFlag string var projectFlag, globalFlag bool cmd := &cobra.Command{ @@ -19,6 +26,11 @@ By default, removes all skills. Use --skills to remove specific skills only.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() + projectFlag, globalFlag, err := parseScopeFlag(scopeFlag, projectFlag, globalFlag, false) + if err != nil { + return err + } + globalDir, err := installer.GlobalSkillsDir(ctx) if err != nil { return err @@ -37,12 +49,14 @@ By default, removes all skills. Use --skills to remove specific skills only.`, Scope: scope, } opts.Skills = splitAndTrim(skillsFlag) - return installer.UninstallSkillsOpts(ctx, opts) + return uninstallSkillsFn(ctx, opts) }, } cmd.Flags().StringVar(&skillsFlag, "skills", "", "Specific skills to uninstall (comma-separated)") + cmd.Flags().StringVar(&scopeFlag, "scope", "", "Uninstall scope: project or global") cmd.Flags().BoolVar(&projectFlag, "project", false, "Uninstall project-scoped skills") cmd.Flags().BoolVar(&globalFlag, "global", false, "Uninstall globally-scoped skills") + markScopeBoolsDeprecated(cmd) return cmd } diff --git a/cmd/aitools/uninstall_test.go b/cmd/aitools/uninstall_test.go new file mode 100644 index 00000000000..ec0dff79890 --- /dev/null +++ b/cmd/aitools/uninstall_test.go @@ -0,0 +1,66 @@ +package aitools + +import ( + "context" + "testing" + + "github.com/databricks/cli/libs/aitools/installer" + "github.com/databricks/cli/libs/cmdio" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupUninstallMock(t *testing.T) *[]installer.UninstallOptions { + t.Helper() + orig := uninstallSkillsFn + t.Cleanup(func() { uninstallSkillsFn = orig }) + + var calls []installer.UninstallOptions + uninstallSkillsFn = func(_ context.Context, opts installer.UninstallOptions) error { + calls = append(calls, opts) + return nil + } + return &calls +} + +func TestUninstallScopeFlag(t *testing.T) { + tests := []struct { + name string + args []string + wantScope string + wantErr string + }{ + // scope=project requires installed project state and is covered via TestParseScopeFlag + // and TestResolveScopeForUninstallProjectFlagWithState. Here we cover the no-state paths + // and the failure modes specific to the Cobra wiring. + {name: "scope global", args: []string{"--scope", "global"}, wantScope: installer.ScopeGlobal}, + {name: "scope both rejected", args: []string{"--scope", "both"}, wantErr: "--scope=both is not supported"}, + {name: "scope invalid value", args: []string{"--scope", "all"}, wantErr: `invalid --scope "all"`}, + {name: "scope conflicts with legacy project", args: []string{"--scope", "global", "--project"}, wantErr: "cannot use --scope with --project or --global"}, + {name: "legacy both flags together rejected", args: []string{"--project", "--global"}, wantErr: "cannot uninstall both scopes at once"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setupTestAgents(t) + calls := setupUninstallMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := NewUninstallCmd() + cmd.SetContext(ctx) + cmd.SetArgs(tt.args) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + err := cmd.Execute() + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + require.Len(t, *calls, 1) + assert.Equal(t, tt.wantScope, (*calls)[0].Scope) + }) + } +} diff --git a/experimental/aitools/cmd/update.go b/cmd/aitools/update.go similarity index 72% rename from experimental/aitools/cmd/update.go rename to cmd/aitools/update.go index c5072d1fb19..4452440c2eb 100644 --- a/experimental/aitools/cmd/update.go +++ b/cmd/aitools/update.go @@ -1,17 +1,23 @@ package aitools import ( + "context" "fmt" - "github.com/databricks/cli/experimental/aitools/lib/agents" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" "github.com/spf13/cobra" ) -func newUpdateCmd() *cobra.Command { +// Package-level for testability. Tests override via update_test.go. +var updateSkillsFn = func(ctx context.Context, src installer.ManifestSource, installed []*agents.Agent, opts installer.UpdateOptions) (*installer.UpdateResult, error) { + return installer.UpdateSkills(ctx, src, installed, opts) +} + +func NewUpdateCmd() *cobra.Command { var check, force, noNew bool - var skillsFlag string + var skillsFlag, scopeFlag string var projectFlag, globalFlag bool cmd := &cobra.Command{ @@ -35,6 +41,11 @@ preview what would change without downloading.`, return err } + projectFlag, globalFlag, err := parseScopeFlag(scopeFlag, projectFlag, globalFlag, true) + if err != nil { + return err + } + scopes, err := resolveScopeForUpdate(ctx, projectFlag, globalFlag, globalDir, projectDir) if err != nil { return err @@ -57,7 +68,7 @@ preview what would change without downloading.`, } opts.Skills = skills - result, err := installer.UpdateSkills(ctx, src, installed, opts) + result, err := updateSkillsFn(ctx, src, installed, opts) if err != nil { return err } @@ -73,7 +84,9 @@ preview what would change without downloading.`, cmd.Flags().BoolVar(&force, "force", false, "Re-download even if versions match") cmd.Flags().BoolVar(&noNew, "no-new", false, "Don't auto-install new skills from manifest") cmd.Flags().StringVar(&skillsFlag, "skills", "", "Specific skills to update (comma-separated)") + cmd.Flags().StringVar(&scopeFlag, "scope", "", "Update scope: project, global, or both") cmd.Flags().BoolVar(&projectFlag, "project", false, "Update project-scoped skills") cmd.Flags().BoolVar(&globalFlag, "global", false, "Update globally-scoped skills") + markScopeBoolsDeprecated(cmd) return cmd } diff --git a/cmd/aitools/update_test.go b/cmd/aitools/update_test.go new file mode 100644 index 00000000000..f93094a81b3 --- /dev/null +++ b/cmd/aitools/update_test.go @@ -0,0 +1,72 @@ +package aitools + +import ( + "context" + "testing" + + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/aitools/installer" + "github.com/databricks/cli/libs/cmdio" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupUpdateMock(t *testing.T) *[]installer.UpdateOptions { + t.Helper() + orig := updateSkillsFn + t.Cleanup(func() { updateSkillsFn = orig }) + + var calls []installer.UpdateOptions + updateSkillsFn = func(_ context.Context, _ installer.ManifestSource, _ []*agents.Agent, opts installer.UpdateOptions) (*installer.UpdateResult, error) { + calls = append(calls, opts) + return &installer.UpdateResult{}, nil + } + return &calls +} + +func TestUpdateScopeFlag(t *testing.T) { + tests := []struct { + name string + args []string + wantScopes []string + wantErr string + }{ + // scope=project requires installed project state and is covered via TestParseScopeFlag + // (cmd/aitools/scope_test.go) and TestResolveScopeForUpdateProjectFlagWithState. Here + // we cover the no-state paths and the failure modes specific to the Cobra wiring. + {name: "scope global", args: []string{"--scope", "global"}, wantScopes: []string{installer.ScopeGlobal}}, + {name: "scope both with no installs falls through to global", args: []string{"--scope", "both"}, wantScopes: []string{installer.ScopeGlobal}}, + {name: "scope invalid value", args: []string{"--scope", "all"}, wantErr: `invalid --scope "all"`}, + {name: "scope conflicts with legacy project", args: []string{"--scope", "global", "--project"}, wantErr: "cannot use --scope with --project or --global"}, + // Legacy `--project --global` is the supported "update both scopes" path + // (preserved until the deprecated flags are removed). Without state, it + // falls through to global per TestResolveScopeForUpdateBothFlagsNeitherInstalled. + {name: "legacy both flags fall through to global without state", args: []string{"--project", "--global"}, wantScopes: []string{installer.ScopeGlobal}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + setupTestAgents(t) + calls := setupUpdateMock(t) + + ctx := cmdio.MockDiscard(t.Context()) + cmd := NewUpdateCmd() + cmd.SetContext(ctx) + cmd.SetArgs(tt.args) + cmd.SilenceErrors = true + cmd.SilenceUsage = true + + err := cmd.Execute() + if tt.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.wantErr) + return + } + require.NoError(t, err) + require.Len(t, *calls, len(tt.wantScopes)) + for i, scope := range tt.wantScopes { + assert.Equal(t, scope, (*calls)[i].Scope) + } + }) + } +} diff --git a/experimental/aitools/cmd/version.go b/cmd/aitools/version.go similarity index 79% rename from experimental/aitools/cmd/version.go rename to cmd/aitools/version.go index 67c38fec42a..11ef2f03bcf 100644 --- a/experimental/aitools/cmd/version.go +++ b/cmd/aitools/version.go @@ -5,12 +5,13 @@ import ( "fmt" "strings" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/log" "github.com/spf13/cobra" ) -func newVersionCmd() *cobra.Command { +func NewVersionCmd() *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Show installed AI skills version", @@ -40,11 +41,14 @@ func newVersionCmd() *cobra.Command { if globalState == nil && projectState == nil { cmdio.LogString(ctx, "No Databricks AI Tools components installed.") cmdio.LogString(ctx, "") - cmdio.LogString(ctx, "Run 'databricks experimental aitools install' to get started.") + cmdio.LogString(ctx, "Run 'databricks aitools install' to get started.") return nil } - latestRef := installer.GetSkillsRef(ctx) + latestRef, _, err := installer.GetSkillsRef(ctx) + if err != nil { + log.Debugf(ctx, "could not resolve skills version: %v", err) + } bothScopes := globalState != nil && projectState != nil cmdio.LogString(ctx, "Databricks AI Tools:") @@ -80,6 +84,12 @@ func printVersionLine(ctx context.Context, label string, state *installer.Instal skillNoun = "skill" } + if latestRef == "" { + cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s)", label, version, len(state.Skills), skillNoun)) + cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02")) + return + } + if latestRef == state.Release { cmdio.LogString(ctx, fmt.Sprintf(" %s: v%s (%d %s, up to date)", label, version, len(state.Skills), skillNoun)) cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02")) @@ -89,6 +99,6 @@ func printVersionLine(ctx context.Context, label string, state *installer.Instal cmdio.LogString(ctx, " Update available: v"+latestVersion) cmdio.LogString(ctx, " Last updated: "+state.LastUpdated.Format("2006-01-02")) cmdio.LogString(ctx, "") - cmdio.LogString(ctx, "Run 'databricks experimental aitools update' to update.") + cmdio.LogString(ctx, "Run 'databricks aitools update' to update.") } } diff --git a/experimental/aitools/cmd/version_test.go b/cmd/aitools/version_test.go similarity index 94% rename from experimental/aitools/cmd/version_test.go rename to cmd/aitools/version_test.go index d24f7e99f81..1192e8e3c86 100644 --- a/experimental/aitools/cmd/version_test.go +++ b/cmd/aitools/version_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/cmdio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,6 +15,7 @@ import ( func TestVersionShowsBothScopes(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) t.Setenv("DATABRICKS_SKILLS_REF", "v0.1.0") // Create global state. @@ -51,7 +52,7 @@ func TestVersionShowsBothScopes(t *testing.T) { require.NoError(t, installer.SaveState(projectSkillsDir, projectState)) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) - cmd := newVersionCmd() + cmd := NewVersionCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) @@ -70,6 +71,7 @@ func TestVersionShowsBothScopes(t *testing.T) { func TestVersionShowsSingleScopeWithoutQualifier(t *testing.T) { tmp := t.TempDir() t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) t.Setenv("DATABRICKS_SKILLS_REF", "v0.1.0") // Create only global state. @@ -90,7 +92,7 @@ func TestVersionShowsSingleScopeWithoutQualifier(t *testing.T) { t.Chdir(projectDir) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) - cmd := newVersionCmd() + cmd := NewVersionCmd() cmd.SetContext(ctx) err := cmd.RunE(cmd, nil) diff --git a/cmd/api/api.go b/cmd/api/api.go index 057c8f22468..ab70ca8b753 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -1,18 +1,44 @@ 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/databrickscfg" + "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/flags" "github.com/databricks/databricks-sdk-go/client" "github.com/databricks/databricks-sdk-go/config" "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 +58,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", @@ -49,19 +79,43 @@ func makeCommand(method string) *cobra.Command { cfg := &config.Config{} - // command-line flag can specify the profile in use - profileFlag := cmd.Flag("profile") - if profileFlag != nil { + // Resolve the profile mirroring MustWorkspaceClient precedence: + // 1. --profile flag, 2. DATABRICKS_CONFIG_PROFILE env var (the SDK + // also reads it, but setting cfg.Profile here keeps any error + // messages we render referring to the same name), 3. + // [__settings__].default_profile in the config file. + if profileFlag := cmd.Flag("profile"); profileFlag != nil { cfg.Profile = profileFlag.Value.String() } + if cfg.Profile == "" { + cfg.Profile = env.Get(cmd.Context(), "DATABRICKS_CONFIG_PROFILE") + } + if cfg.Profile == "" { + cfg.Profile = databrickscfg.ResolveDefaultProfile(cmd.Context()) + } api, err := client.New(cfg) if err != nil { 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 +125,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/bundle_helpers.go b/cmd/apps/bundle_helpers.go index 6b1712de563..d1e587bebca 100644 --- a/cmd/apps/bundle_helpers.go +++ b/cmd/apps/bundle_helpers.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "os" + "path/filepath" "strings" "time" @@ -27,12 +29,71 @@ func makeArgsOptionalWithBundle(cmd *cobra.Command, usage string) { return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) } if !hasBundleConfig() && len(args) != 1 { - return fmt.Errorf("accepts 1 arg(s), received %d", len(args)) + return missingAppNameError(cmd) } return nil } } +// missingAppNameError returns an error message that explains what the positional +// argument should be, and attempts to infer a suggestion from the local environment. +// The full subcommand path (e.g. "databricks apps start") is rendered from cmd so +// the usage line and "Did you mean?" hint match the verb the user actually ran. +func missingAppNameError(cmd *cobra.Command) error { + hint := inferAppNameHint() + commandPath := "databricks apps " + argName := "APP_NAME" + if cmd != nil { + if p := cmd.CommandPath(); p != "" { + commandPath = p + } + if name := positionalArgName(cmd.Use); name != "" { + argName = name + } + } + msg := fmt.Sprintf(`missing required argument: %s + +Usage: %s %s + +%s is the name of the Databricks app to operate on. +Alternatively, run this command from a project directory containing +databricks.yml to auto-detect the app name.`, argName, commandPath, argName, argName) + + if hint != "" { + msg += fmt.Sprintf("\n\nDid you mean?\n %s %s", commandPath, hint) + } + + return errors.New(msg) +} + +func positionalArgName(use string) string { + start := strings.Index(use, "[") + end := strings.Index(use, "]") + if start < 0 || end <= start { + return "" + } + return use[start+1 : end] +} + +// inferAppNameHint tries to suggest an app name from the local environment. +// Only returns a hint if the current directory looks like a Databricks app +// (contains app.yml or app.yaml), using the directory name as the suggestion. +func inferAppNameHint() string { + wd, err := os.Getwd() + if err != nil { + return "" + } + + for _, filename := range []string{"app.yml", "app.yaml"} { + info, err := os.Stat(filepath.Join(wd, filename)) + if err == nil && info.Mode().IsRegular() { + return filepath.Base(wd) + } + } + + return "" +} + // getAppNameFromArgs returns the app name from args or detects it from the bundle. // Returns (appName, fromBundle, error). func getAppNameFromArgs(cmd *cobra.Command, args []string) (string, bool, error) { diff --git a/cmd/apps/bundle_helpers_test.go b/cmd/apps/bundle_helpers_test.go index f772d4f545e..9e798d0f830 100644 --- a/cmd/apps/bundle_helpers_test.go +++ b/cmd/apps/bundle_helpers_test.go @@ -2,6 +2,8 @@ package apps import ( "errors" + "os" + "path/filepath" "testing" "github.com/databricks/databricks-sdk-go/service/apps" @@ -105,6 +107,112 @@ func TestFormatAppStatusMessage(t *testing.T) { }) } +func TestInferAppNameHint(t *testing.T) { + t.Run("returns empty when no app config exists", func(t *testing.T) { + t.Chdir(t.TempDir()) + + assert.Equal(t, "", inferAppNameHint()) + }) + + t.Run("returns dir name when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns dir name when app.yaml exists", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, err) + + assert.Equal(t, filepath.Base(dir), inferAppNameHint()) + }) + + t.Run("returns empty when cwd has been deleted", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + os.Remove(dir) + + assert.Equal(t, "", inferAppNameHint()) + }) +} + +func TestMissingAppNameError(t *testing.T) { + t.Run("includes APP_NAME and usage info", func(t *testing.T) { + t.Chdir(t.TempDir()) + + err := missingAppNameError(nil) + assert.Contains(t, err.Error(), "APP_NAME") + assert.Contains(t, err.Error(), "databricks.yml") + assert.NotContains(t, err.Error(), "Did you mean") + }) + + t.Run("includes hint when app.yml exists", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, writeErr) + + err := missingAppNameError(nil) + assert.Contains(t, err.Error(), "Did you mean") + assert.Contains(t, err.Error(), filepath.Base(dir)) + }) + + t.Run("gracefully handles deleted cwd", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + os.Remove(dir) + + err := missingAppNameError(nil) + assert.Contains(t, err.Error(), "APP_NAME") + assert.NotContains(t, err.Error(), "Did you mean") + }) + + t.Run("renders usage and hint from cmd path per verb", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + writeErr := os.WriteFile(filepath.Join(dir, "app.yml"), []byte("command: [\"python\"]"), 0o644) + assert.NoError(t, writeErr) + + for _, tc := range []struct { + verb string + use string + arg string + }{ + {"deploy", "deploy [APP_NAME]", "APP_NAME"}, + {"start", "start [NAME]", "NAME"}, + {"stop", "stop [NAME]", "NAME"}, + {"delete", "delete [NAME]", "NAME"}, + } { + t.Run(tc.verb, func(t *testing.T) { + root := &cobra.Command{Use: "databricks"} + apps := &cobra.Command{Use: "apps"} + sub := &cobra.Command{Use: tc.use} + root.AddCommand(apps) + apps.AddCommand(sub) + + err := missingAppNameError(sub) + assert.Contains(t, err.Error(), "missing required argument: "+tc.arg) + assert.Contains(t, err.Error(), "Usage: databricks apps "+tc.verb+" "+tc.arg) + assert.Contains(t, err.Error(), "databricks apps "+tc.verb+" "+filepath.Base(dir)) + }) + } + }) + + t.Run("ignores non-regular app.yml entries", func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + assert.NoError(t, os.Mkdir(filepath.Join(dir, "app.yml"), 0o755)) + + err := missingAppNameError(nil) + assert.NotContains(t, err.Error(), "Did you mean") + }) +} + func TestMakeArgsOptionalWithBundle(t *testing.T) { t.Run("updates command usage", func(t *testing.T) { cmd := &cobra.Command{} @@ -117,6 +225,17 @@ func TestMakeArgsOptionalWithBundle(t *testing.T) { makeArgsOptionalWithBundle(cmd, "test [NAME]") assert.NotNil(t, cmd.Args) }) + + t.Run("returns missing app name error when no bundle config exists", func(t *testing.T) { + t.Chdir(t.TempDir()) + + cmd := &cobra.Command{} + makeArgsOptionalWithBundle(cmd, "test [NAME]") + + err := cmd.Args(cmd, nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "missing required argument: NAME") + }) } func TestGetAppNameFromArgs(t *testing.T) { diff --git a/cmd/apps/dev.go b/cmd/apps/dev.go index 59d000ef79c..4fb86527c25 100644 --- a/cmd/apps/dev.go +++ b/cmd/apps/dev.go @@ -120,9 +120,8 @@ func newDevRemoteCmd() *cobra.Command { ) cmd := &cobra.Command{ - Use: "dev-remote", - Short: "Run AppKit app locally with WebSocket bridge to remote server", - Hidden: true, + Use: "dev-remote", + Short: "Run AppKit app locally with WebSocket bridge to remote server", Long: `Run AppKit app locally with WebSocket bridge to remote server. Starts a local Vite development server and establishes a WebSocket bridge diff --git a/cmd/apps/import.go b/cmd/apps/import.go index 67cf4ae9d9e..42f9da4ec14 100644 --- a/cmd/apps/import.go +++ b/cmd/apps/import.go @@ -34,6 +34,7 @@ import ( "github.com/databricks/cli/libs/textutil" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/apps" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/spf13/cobra" ) @@ -89,7 +90,7 @@ Examples: w := cmdctx.WorkspaceClient(ctx) // Get current user to filter apps - me, err := w.CurrentUser.Me(ctx) + me, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return fmt.Errorf("failed to get current user: %w", err) } diff --git a/cmd/apps/init.go b/cmd/apps/init.go index 6f7269e6fd0..917da89d80f 100644 --- a/cmd/apps/init.go +++ b/cmd/apps/init.go @@ -17,12 +17,13 @@ import ( "github.com/charmbracelet/huh" "github.com/databricks/cli/cmd/root" - "github.com/databricks/cli/experimental/aitools/lib/agents" - "github.com/databricks/cli/experimental/aitools/lib/installer" + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/aitools/installer" "github.com/databricks/cli/libs/apps/generator" "github.com/databricks/cli/libs/apps/initializer" "github.com/databricks/cli/libs/apps/manifest" "github.com/databricks/cli/libs/apps/prompt" + "github.com/databricks/cli/libs/clicompat" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" @@ -38,7 +39,6 @@ const ( appkitTemplateDir = "template" appkitDefaultBranch = "main" appkitTemplateTagPfx = "template-v" - appkitDefaultVersion = "template-v0.24.0" defaultProfile = "DEFAULT" ) @@ -81,9 +81,8 @@ func newInitCmd() *cobra.Command { ) cmd := &cobra.Command{ - Use: "init", - Short: "Initialize a new AppKit application from a template", - Hidden: true, + Use: "init", + Short: "Initialize a new AppKit application from a template", Long: `Initialize a new AppKit application from a template. When run without arguments, uses the default AppKit template and an interactive prompt @@ -169,7 +168,7 @@ Environment variables: cmd.Flags().StringVar(&templatePath, "template", "", "Template path (local directory or GitHub URL)") cmd.Flags().StringVar(&branch, "branch", "", "Git branch or tag (for GitHub templates, mutually exclusive with --version)") - cmd.Flags().StringVar(&version, "version", "", fmt.Sprintf("AppKit version to use (default: %s, use 'latest' for main branch)", appkitDefaultVersion)) + cmd.Flags().StringVar(&version, "version", "", "AppKit version to use (default: auto-detected, use 'latest' for main branch)") cmd.Flags().StringVar(&name, "name", "", "Project name (prompts if not provided)") cmd.Flags().StringVar(&warehouseID, "warehouse-id", "", "SQL warehouse ID") _ = cmd.Flags().MarkDeprecated("warehouse-id", "use --set .sql-warehouse.id= instead") @@ -282,6 +281,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 @@ -526,7 +547,7 @@ func cloneRepo(ctx context.Context, repoURL, branch string) (string, error) { // Used by commands that don't benefit from background cloning (e.g., manifest). func resolveTemplate(ctx context.Context, templatePath, branch, subdir string) (string, func(), error) { ch := resolveTemplateAsync(ctx, templatePath, branch, subdir) - return awaitTemplate(ctx, ch) + return awaitTemplate(ctx, ch, "") } // templateResult holds the outcome of a background template resolution. @@ -577,18 +598,24 @@ func resolveTemplateAsync(ctx context.Context, templatePath, branch, subdir stri // awaitTemplate waits for the background clone to finish. // If the result is already available it returns immediately with a // checkmark; otherwise it shows a spinner while waiting. -func awaitTemplate(ctx context.Context, ch <-chan templateResult) (string, func(), error) { +// refLabel, if non-empty (e.g. "version 0.24.0" or "branch feature-x"), +// is appended to spinner and done messages. +func awaitTemplate(ctx context.Context, ch <-chan templateResult, refLabel string) (string, func(), error) { + suffix := "" + if refLabel != "" { + suffix = " (" + refLabel + ")" + } select { case res := <-ch: // Clone finished while the user was typing — print completion. if res.err == nil && res.cleanup != nil { - prompt.PrintDone(ctx, "Template cloned") + prompt.PrintDone(ctx, "Template cloned"+suffix) } return res.path, res.cleanup, res.err default: // Still cloning — show a spinner for the remaining wait. var res templateResult - err := prompt.RunWithSpinnerCtx(ctx, "Cloning template...", func() error { + err := prompt.RunWithSpinnerCtx(ctx, "Cloning template"+suffix+"...", func() error { res = <-ch return res.err }) @@ -596,6 +623,20 @@ func awaitTemplate(ctx context.Context, ch <-chan templateResult) (string, func( } } +// commitInPlace derives the app name from the cwd basename and verifies that +// the cwd is suitable for in-place scaffolding (empty modulo .git). +// Returns the derived app name on success. +func commitInPlace() (string, error) { + appName, err := prompt.DeriveInPlaceAppName(".") + if err != nil { + return "", err + } + if err := prompt.CheckInPlaceDirectory("."); err != nil { + return "", err + } + return appName, nil +} + // findProjectSrcDir locates the actual source directory inside a template. // Templates may nest their content inside a {{.project_name}} directory. func findProjectSrcDir(templateDir string) string { @@ -772,21 +813,36 @@ func runCreate(ctx context.Context, opts createOptions) error { templateSrc = env.Get(ctx, templatePathEnvVar) } - // Resolve the git reference (branch/tag) to use for default appkit template + // Resolve the git reference (branch/tag) to use for default appkit template. + // refLabel is a human-readable description of the ref we're cloning + // (e.g. "version 0.24.0", "branch feature-x"). It's surfaced in the + // interactive header and the clone spinner so the user can cancel before + // naming the project. Empty when there's nothing meaningful to show + // (e.g. a custom --template URL with no explicit branch). gitRef := opts.branch + var refLabel string usingDefaultTemplate := templateSrc == "" if usingDefaultTemplate { // Using default appkit template - resolve version switch { case opts.branch != "": // --branch takes precedence (already set in gitRef) + refLabel = "branch " + opts.branch case opts.version != "": gitRef = normalizeVersion(opts.version) + refLabel = "version " + opts.version default: - // Default: use pinned version - gitRef = appkitDefaultVersion + resolved, err := clicompat.ResolveAppKitVersion(ctx) + if err != nil { + return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err) + } + gitRef = normalizeVersion(resolved) + refLabel = "version " + resolved } templateSrc = appkitRepoURL + } else if opts.branch != "" { + // Custom template with an explicit branch — show it for traceability. + refLabel = "branch " + opts.branch } // Start cloning in the background so it runs while the user types the name. @@ -808,35 +864,106 @@ func runCreate(ctx context.Context, opts createOptions) error { }() // Step 1: Get project name (clone runs in parallel for remote templates) - destDir := opts.name - if opts.outputDir != "" { - destDir = filepath.Join(opts.outputDir, opts.name) + if opts.name == prompt.InPlaceName && opts.outputDir != "" { + return prompt.ErrNameDotWithOutputDir } - if opts.name == "" { - if !isInteractive { - return errors.New("--name is required in non-interactive mode") - } - name, err := prompt.PromptForProjectName(ctx, opts.outputDir) + var ( + destDir string + inPlace bool + ) + switch { + case opts.name == prompt.InPlaceName: + appName, err := commitInPlace() if err != nil { return err } - opts.name = name + opts.name = appName + destDir = "." + inPlace = true + case opts.name != "": + if err := prompt.ValidateProjectName(opts.name); err != nil { + return err + } destDir = opts.name if opts.outputDir != "" { destDir = filepath.Join(opts.outputDir, opts.name) } - } else { - if err := prompt.ValidateProjectName(opts.name); err != nil { - return err - } if _, err := os.Stat(destDir); err == nil { return fmt.Errorf("directory %s already exists", destDir) } + default: + if !isInteractive { + return errors.New("--name is required in non-interactive mode") + } + // Print the AppKit header once so it covers both the in-place + // scaffold-location prompt below and the project-name prompt that + // may follow, and so the resolved template ref is visible before + // the user commits to either path. + prompt.PrintHeader(ctx, refLabel) + // Offer in-place scaffolding when the current directory is empty + // (modulo .git) and its basename is a valid app name. Skipped when + // --output-dir was set, since in-place targets cwd and would silently + // drop the flag — same reasoning as the --name . / --output-dir mutex + // above. + if opts.outputDir == "" { + if basename, ok := prompt.ShouldOfferInPlace("."); ok { + useCurrent, err := prompt.PromptScaffoldLocation(ctx, basename) + if err != nil { + return err + } + if useCurrent { + // Re-check immediately before committing — the directory may + // have changed between offer and answer. + if err := prompt.CheckInPlaceDirectory("."); err != nil { + return err + } + opts.name = basename + destDir = "." + inPlace = true + } + } + } + if !inPlace { + name, err := prompt.PromptForProjectName(ctx, opts.outputDir) + if err != nil { + return err + } + if name == prompt.InPlaceName { + appName, err := commitInPlace() + if err != nil { + return err + } + opts.name = appName + destDir = "." + inPlace = true + } else { + opts.name = name + destDir = name + if opts.outputDir != "" { + destDir = filepath.Join(opts.outputDir, name) + } + } + } } // Step 2: Wait for template (may already be done if the user took time typing the name) - resolvedPath, cleanup, err := awaitTemplate(ctx, templateCh) + resolvedPath, cleanup, err := awaitTemplate(ctx, templateCh, refLabel) + // Only fall back to the embedded version when the version was auto-resolved + // from the manifest, not when the user explicitly passed --version or --branch. + versionAutoResolved := opts.version == "" && opts.branch == "" + if err != nil && usingDefaultTemplate && versionAutoResolved && clicompat.IsNotFoundError(err) { + fallbackVersion, fbErr := clicompat.ResolveEmbeddedAppKitVersion() + if fbErr == nil && fallbackVersion != "" && normalizeVersion(fallbackVersion) != branchForClone { + log.Warnf(ctx, "Template version not found, falling back to embedded version %s", fallbackVersion) + fallbackRef := normalizeVersion(fallbackVersion) + templateCh = resolveTemplateAsync(ctx, templateSrc, fallbackRef, appkitTemplateDir) + refLabel = "version " + fallbackVersion + resolvedPath, cleanup, err = awaitTemplate(ctx, templateCh, refLabel) + } else if fbErr != nil { + log.Warnf(ctx, "Could not resolve embedded AppKit version: %v", fbErr) + } + } if err != nil { return err } @@ -967,21 +1094,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 } } @@ -999,9 +1113,17 @@ func runCreate(ctx context.Context, opts createOptions) error { var projectCreated bool var runErr error defer func() { - if runErr != nil && (projectCreated || npmInstallCh != nil) { - os.RemoveAll(destDir) + if runErr == nil || (!projectCreated && npmInstallCh == nil) { + return + } + if inPlace { + // destDir is "." here; a wholesale RemoveAll would wipe the + // user's current directory (including any pre-existing .git). + // Leave the partial scaffold and tell the user to clean up. + log.Warnf(ctx, "scaffold failed in current directory; review and clean up generated files manually (e.g. with git status / git clean -fd)") + return } + os.RemoveAll(destDir) }() // Set description default @@ -1124,9 +1246,9 @@ func runCreate(ctx context.Context, opts createOptions) error { // Show next steps only if user didn't choose to deploy or run showNextSteps := !shouldDeploy && runMode == prompt.RunModeNone if showNextSteps { - prompt.PrintSuccess(ctx, opts.name, absOutputDir, fileCount, nextStepsCmd) + prompt.PrintSuccess(ctx, opts.name, absOutputDir, fileCount, nextStepsCmd, inPlace) } else { - prompt.PrintSuccess(ctx, opts.name, absOutputDir, fileCount, "") + prompt.PrintSuccess(ctx, opts.name, absOutputDir, fileCount, "", inPlace) } // Print any onSetupMessage declared by selected plugins in the template manifest. @@ -1146,7 +1268,7 @@ func runCreate(ctx context.Context, opts createOptions) error { // In flags mode, only print a hint — never prompt interactively. if flagsMode { if !agents.HasDatabricksSkillsInstalled(ctx) { - cmdio.LogString(ctx, "Tip: coding agents detected without Databricks skills. Run 'databricks experimental aitools skills install' to install them.") + cmdio.LogString(ctx, "Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.") } } else if err := agents.RecommendSkillsInstall(ctx, installer.InstallAllSkills); err != nil { log.Warnf(ctx, "Skills recommendation failed: %v", err) @@ -1447,12 +1569,19 @@ func copyTemplate(ctx context.Context, src, dest string, vars templateVars) (int // removeEmptyDirs removes empty directories under root, deepest-first. // It is used to clean up directories that were created eagerly but ended up // with no files after conditional template rendering skipped their contents. +// +// .git is skipped so in-place scaffolding (root == ".") never walks into a +// pre-existing repo and deletes its empty subdirectories (refs/heads, +// refs/tags, objects/info, objects/pack are all empty after `git init`). func removeEmptyDirs(root string) error { var dirs []string err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } + if d.IsDir() && d.Name() == ".git" && path != root { + return filepath.SkipDir + } if d.IsDir() && path != root { dirs = append(dirs, path) } diff --git a/cmd/apps/init_test.go b/cmd/apps/init_test.go index b8a9a8f443c..ce3d2e33b04 100644 --- a/cmd/apps/init_test.go +++ b/cmd/apps/init_test.go @@ -13,6 +13,7 @@ import ( "github.com/databricks/cli/libs/apps/manifest" "github.com/databricks/cli/libs/apps/prompt" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -443,7 +444,7 @@ func TestNormalizeVersion(t *testing.T) { {"", ""}, {"main", "main"}, {"feat/something", "feat/something"}, - {appkitDefaultVersion, appkitDefaultVersion}, + {"template-v0.24.0", "template-v0.24.0"}, } for _, tt := range tests { @@ -773,6 +774,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) @@ -1019,3 +1073,78 @@ func TestStartBackgroundNpmInstall_TemplateSubstitution(t *testing.T) { assert.Contains(t, string(got), `"cool-project"`) assert.NotContains(t, string(got), "{{.projectName}}") } + +// makeChildDir creates and returns an empty subdirectory of t.TempDir() with +// the requested name. Used to control filepath.Base(cwd) for in-place tests. +func makeChildDir(t *testing.T, name string) string { + t.Helper() + dir := filepath.Join(t.TempDir(), name) + require.NoError(t, os.MkdirAll(dir, 0o755)) + return dir +} + +func TestCommitInPlace_Success(t *testing.T) { + dir := makeChildDir(t, "my-app") + t.Chdir(dir) + + name, err := commitInPlace() + require.NoError(t, err) + assert.Equal(t, "my-app", name) +} + +func TestCommitInPlace_AllowsDotGit(t *testing.T) { + dir := makeChildDir(t, "my-app") + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) + t.Chdir(dir) + + name, err := commitInPlace() + require.NoError(t, err) + assert.Equal(t, "my-app", name) +} + +func TestCommitInPlace_RejectsPreExistingGitignore(t *testing.T) { + // The template ships _gitignore that renames to .gitignore on copy. + // Allowing a pre-existing .gitignore would silently destroy the user's + // file via os.WriteFile, so we refuse the directory up front. + dir := makeChildDir(t, "my-app") + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("node_modules\n"), 0o644)) + t.Chdir(dir) + + _, err := commitInPlace() + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") +} + +func TestCommitInPlace_RejectsStrayFiles(t *testing.T) { + dir := makeChildDir(t, "my-app") + require.NoError(t, os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi"), 0o644)) + t.Chdir(dir) + + _, err := commitInPlace() + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") +} + +func TestCommitInPlace_RejectsInvalidBasename(t *testing.T) { + dir := makeChildDir(t, "My_App") + t.Chdir(dir) + + _, err := commitInPlace() + require.Error(t, err) + assert.Contains(t, err.Error(), "My_App") +} + +func TestRunCreate_NameDotAndOutputDirAreMutuallyExclusive(t *testing.T) { + dir := makeChildDir(t, "my-app") + t.Chdir(dir) + + ctx := cmdio.MockDiscard(t.Context()) + err := runCreate(ctx, createOptions{ + name: prompt.InPlaceName, + nameProvided: true, + outputDir: "elsewhere", + }) + require.Error(t, err) + assert.ErrorIs(t, err, prompt.ErrNameDotWithOutputDir) +} diff --git a/cmd/apps/logs.go b/cmd/apps/logs.go index c6cab936521..777a9f3621b 100644 --- a/cmd/apps/logs.go +++ b/cmd/apps/logs.go @@ -46,7 +46,6 @@ func newLogsCommand() *cobra.Command { ) cmd := &cobra.Command{ - Use: "logs [NAME]", Short: "Show Databricks app logs", Long: `Show Databricks app logs. @@ -78,15 +77,6 @@ Examples: # Mirror streamed logs to a local file while following for up to 5 minutes databricks apps logs my-app --follow --timeout 5m --output-file /tmp/my-app.log`, - Args: func(cmd *cobra.Command, args []string) error { - if len(args) > 1 { - return fmt.Errorf("accepts at most 1 arg(s), received %d", len(args)) - } - if !hasBundleConfig() && len(args) != 1 { - return fmt.Errorf("accepts 1 arg(s), received %d", len(args)) - } - return nil - }, PreRunE: root.MustWorkspaceClient, RunE: func(cmd *cobra.Command, args []string) error { appName, fromBundle, err := getAppNameFromArgs(cmd, args) @@ -207,6 +197,7 @@ Examples: }) }, } + makeArgsOptionalWithBundle(cmd, "logs [NAME]") streamGroup := cmdgroup.NewFlagGroup("Streaming") streamGroup.FlagSet().IntVar(&tailLines, "tail-lines", defaultTailLines, "Number of recent log lines to show before streaming. Set to 0 to show everything.") diff --git a/cmd/apps/logs_test.go b/cmd/apps/logs_test.go index ca7ea5e7ef9..2f8d74b11a7 100644 --- a/cmd/apps/logs_test.go +++ b/cmd/apps/logs_test.go @@ -64,6 +64,17 @@ func TestBuildLogsURLRejectsUnknownScheme(t *testing.T) { require.Error(t, err) } +func TestLogsMissingNameError(t *testing.T) { + t.Chdir(t.TempDir()) + + cmd := newLogsCommand() + err := cmd.Args(cmd, nil) + + require.Error(t, err) + assert.Contains(t, err.Error(), "missing required argument: NAME") + assert.Contains(t, err.Error(), "Usage: logs NAME") +} + func TestNormalizeOrigin(t *testing.T) { assert.Equal(t, "https://example.com", normalizeOrigin("https://example.com/foo")) assert.Equal(t, "http://example.com", normalizeOrigin("ws://example.com/foo")) diff --git a/cmd/apps/manifest.go b/cmd/apps/manifest.go index 38df201acc0..0191700fbc7 100644 --- a/cmd/apps/manifest.go +++ b/cmd/apps/manifest.go @@ -9,7 +9,9 @@ import ( "path/filepath" "github.com/databricks/cli/libs/apps/manifest" + "github.com/databricks/cli/libs/clicompat" "github.com/databricks/cli/libs/env" + "github.com/databricks/cli/libs/log" "github.com/spf13/cobra" ) @@ -27,7 +29,11 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string) case version != "": gitRef = normalizeVersion(version) default: - gitRef = appkitDefaultVersion + appkitVersion, err := clicompat.ResolveAppKitVersion(ctx) + if err != nil { + return fmt.Errorf("could not resolve AppKit template version: %w; use --version to specify a version manually", err) + } + gitRef = normalizeVersion(appkitVersion) } templateSrc = appkitRepoURL } @@ -39,6 +45,17 @@ func runManifestOnly(ctx context.Context, templatePath, branch, version string) subdirForClone = appkitTemplateDir } resolvedPath, cleanup, err := resolveTemplate(ctx, templateSrc, branchForClone, subdirForClone) + versionAutoResolved := version == "" && branch == "" + if err != nil && usingDefaultTemplate && versionAutoResolved && clicompat.IsNotFoundError(err) { + fallbackVersion, fbErr := clicompat.ResolveEmbeddedAppKitVersion() + if fbErr == nil && fallbackVersion != "" && normalizeVersion(fallbackVersion) != gitRef { + log.Warnf(ctx, "Template version not found, falling back to embedded version %s", fallbackVersion) + fallbackRef := normalizeVersion(fallbackVersion) + resolvedPath, cleanup, err = resolveTemplate(ctx, templateSrc, fallbackRef, appkitTemplateDir) + } else if fbErr != nil { + log.Warnf(ctx, "Could not resolve embedded AppKit version: %v", fbErr) + } + } if err != nil { return err } @@ -79,9 +96,8 @@ func newManifestCmd() *cobra.Command { ) cmd := &cobra.Command{ - Use: "manifest", - Short: "Print template manifest with available plugins and required resources", - Hidden: true, + Use: "manifest", + Short: "Print template manifest with available plugins and required resources", Long: `Resolves a template (default AppKit repo or --template URL), locates appkit.plugins.json, and prints its raw contents to stdout. No workspace authentication is required. diff --git a/cmd/apps/run_local.go b/cmd/apps/run_local.go index d4bc546ab7c..8d42783c47e 100644 --- a/cmd/apps/run_local.go +++ b/cmd/apps/run_local.go @@ -18,6 +18,7 @@ import ( "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/spf13/cobra" ) @@ -116,7 +117,7 @@ func setupProxy(ctx context.Context, cmd *cobra.Command, config *runlocal.Config return err } - me, err := w.CurrentUser.Me(ctx) + me, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return err } diff --git a/cmd/apps/validate.go b/cmd/apps/validate.go index f6490d03acf..34340192798 100644 --- a/cmd/apps/validate.go +++ b/cmd/apps/validate.go @@ -12,9 +12,8 @@ import ( func newValidateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "validate", - Short: "Validate a Databricks App project", - Hidden: true, + Use: "validate", + Short: "Validate a Databricks App project", Long: `Validate a Databricks App project by running build, typecheck, and lint checks. This command detects the project type and runs the appropriate validation: diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index 348c2138560..7ef3a9f72ac 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -45,9 +45,14 @@ 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() + // The hint is printed separately so the prompt label stays short. + // promptui's screenbuf does not account for terminal line wrapping, and a + // long "label + value" line that wraps causes each keystroke to leave a + // stale render on screen instead of overwriting the previous one. + cmdio.LogString(ctx, "Example: https://.cloud.databricks.com") + return cmdio.RunPrompt(ctx, cmdio.PromptOptions{ + Label: "Databricks host", + }) } func promptForAccountID(ctx context.Context) (string, error) { @@ -55,11 +60,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..9dbf04fc664 100644 --- a/cmd/auth/auth_test.go +++ b/cmd/auth/auth_test.go @@ -1,6 +1,8 @@ package auth import ( + "os" + "path/filepath" "testing" "github.com/databricks/cli/cmd/root" @@ -70,23 +72,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,19 +103,26 @@ 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 // pass the conflict check (the command will fail later for other reasons, but // NOT with a conflict error). func TestProfileHostCompatibleViaCobra(t *testing.T) { - t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg") + // Copy the fixture into a temp directory so the auth login flow's writes + // (e.g. silent plaintext-fallback persistence on CI runners without a + // usable keyring) cannot dirty the checked-in fixture. + configPath := filepath.Join(t.TempDir(), ".databrickscfg") + fixture, err := os.ReadFile("./testdata/.databrickscfg") + require.NoError(t, err) + require.NoError(t, os.WriteFile(configPath, fixture, 0o600)) + t.Setenv("DATABRICKS_CONFIG_FILE", configPath) ctx := cmdctx.GenerateExecId(t.Context()) cli := root.New(ctx) @@ -122,10 +131,10 @@ 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) + _, err = cli.ExecuteContextC(ctx) // The command may fail for other reasons (no browser, non-interactive, etc.) // but it should NOT fail with a conflict error. if err != nil { diff --git a/cmd/auth/describe.go b/cmd/auth/describe.go index 9199aac9e6e..b4abe63d65d 100644 --- a/cmd/auth/describe.go +++ b/cmd/auth/describe.go @@ -4,12 +4,17 @@ 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/databricks/databricks-sdk-go/service/iam" "github.com/spf13/cobra" ) @@ -21,10 +26,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 +91,12 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try cfg, isAccount, err := fn(cmd, args) ctx := cmd.Context() if err != nil { + details := getAuthDetails(cmd, cfg, showSensitive) return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + Status: "error", + Error: err, + Details: details, + TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType), }, nil } @@ -93,37 +106,45 @@ 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 { + details := getAuthDetails(cmd, cfg, showSensitive) return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + 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 } w := cmdctx.WorkspaceClient(ctx) - me, err := w.CurrentUser.Me(ctx) + me, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { + details := getAuthDetails(cmd, cfg, showSensitive) return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct - Status: "error", - Error: err, - Details: getAuthDetails(cmd, cfg, showSensitive), + 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 +174,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..3fb26c06d93 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" ) @@ -24,7 +27,7 @@ func TestGetWorkspaceAuthStatus(t *testing.T) { showSensitive := false currentUserApi := m.GetMockCurrentUserAPI() - currentUserApi.EXPECT().Me(mock.Anything).Return(&iam.User{ + currentUserApi.EXPECT().Me(mock.Anything, mock.Anything).Return(&iam.User{ UserName: "test-user", }, nil) @@ -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 secure", + authType: authTypeDatabricksCLI, + want: &tokenStorageInfo{ + Mode: "secure", + Location: secureLocation, + Source: "default", + }, + }, + { + name: "databricks-cli with plaintext from env", + authType: authTypeDatabricksCLI, + envValue: "plaintext", + want: &tokenStorageInfo{ + Mode: "plaintext", + Location: plaintextLocation, + 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, 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, 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/login.go b/cmd/auth/login.go index b7700e1cde2..475a72ed36e 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -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 @@ -49,6 +46,11 @@ const ( defaultTimeout = 1 * time.Hour authTypeDatabricksCLI = "databricks-cli" discoveryFallbackTip = "\n\nTip: you can specify a workspace directly with: databricks auth login --host " + // discoveryHostEnvVar overrides the default https://login.databricks.com + // host used by the discovery login flow. Intended for testing and + // development against non-production environments. See WithDiscoveryHost + // in github.com/databricks/databricks-sdk-go/credentials/u2m. + discoveryHostEnvVar = "DATABRICKS_DISCOVERY_HOST" ) // discoveryErr wraps an error (or creates a new one) and appends the @@ -147,11 +149,6 @@ a new profile is created. ctx := cmd.Context() profileName := cmd.Flag("profile").Value.String() - tokenCache, mode, err := storage.ResolveCache(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") @@ -177,6 +174,53 @@ a new profile is created. } } + // Resolve the cache before the browser step so an unavailable + // keyring surfaces here rather than after OAuth. The probe also + // triggers the OS unlock prompt, which the user can answer during + // OAuth. Run after input validation so trivially-invalid commands + // fail without probing. + tokenCache, mode, err := storage.ResolveCacheForLogin(ctx, "") + if err != nil { + return err + } + + // 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 @@ -256,6 +300,11 @@ a new profile is created. if err = persistentAuth.Challenge(); err != nil { return err } + // Lock secure mode in after a successful keyring write so a later + // transient keyring probe failure cannot silently demote this user + // to plaintext. + storage.PinSecureMode(ctx, mode, storage.StorageModeUnknown) + // At this point, an OAuth token has been successfully minted and stored // in the CLI cache. The rest of the command focuses on: // 1. Workspace selection for SPOG hosts (best-effort); @@ -580,6 +629,10 @@ func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error { if len(scopesList) > 0 { opts = append(opts, u2m.WithScopes(scopesList)) } + discoveryHost := env.Get(ctx, discoveryHostEnvVar) + if discoveryHost != "" { + opts = append(opts, u2m.WithDiscoveryHost(discoveryHost)) + } // Apply timeout before creating PersistentAuth so Challenge() respects it. ctx, cancel := context.WithTimeout(ctx, in.timeout) @@ -591,10 +644,15 @@ func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error { } defer persistentAuth.Close() - cmdio.LogString(ctx, "Opening login.databricks.com in your browser...") + displayHost := "login.databricks.com" + if discoveryHost != "" { + displayHost = discoveryHost + } + cmdio.LogString(ctx, fmt.Sprintf("Opening %s in your browser...", displayHost)) if err := persistentAuth.Challenge(); err != nil { return discoveryErr("login via login.databricks.com failed", err) } + storage.PinSecureMode(ctx, in.mode, storage.StorageModeUnknown) discoveredHost := arg.GetDiscoveredHost() if discoveredHost == "" { @@ -756,10 +814,9 @@ 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 } diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index e057c979c3b..86820881cf7 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -145,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 = "" @@ -158,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 = "" @@ -275,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", }, { @@ -1082,6 +1082,40 @@ auth_type = databricks-cli assert.Equal(t, "222222", savedProfile.WorkspaceID, "workspace_id should be updated to fresh introspection value") } +func TestDiscoveryLogin_OverridesHostFromEnv(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, ".databrickscfg") + err := os.WriteFile(configPath, []byte(""), 0o600) + require.NoError(t, err) + t.Setenv("DATABRICKS_CONFIG_FILE", configPath) + + oauthArg, err := u2m.NewBasicDiscoveryOAuthArgument("DISCOVERY") + require.NoError(t, err) + oauthArg.SetDiscoveredHost("https://workspace.example.com") + + dc := &fakeDiscoveryClient{ + oauthArg: oauthArg, + persistentAuth: &fakeDiscoveryPersistentAuth{ + token: &oauth2.Token{AccessToken: "test-token"}, + }, + introspectionErr: errors.New("introspection failed"), + } + + ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) + ctx = env.Set(ctx, "DATABRICKS_DISCOVERY_HOST", "https://login.staging.test") + err = discoveryLogin(ctx, discoveryLoginInputs{ + dc: dc, + profileName: "DISCOVERY", + timeout: time.Second, + browserFunc: func(string) error { return nil }, + tokenCache: newTestTokenCache(), + }) + require.NoError(t, err) + + assert.Contains(t, stderr.String(), "Opening https://login.staging.test in your browser...") + assert.NotContains(t, stderr.String(), "Opening login.databricks.com in your browser...") +} + func TestLoginRejectsPositionalArgWithHostFlag(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) authArgs := &auth.AuthArguments{Host: "https://example.com"} diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index a8cd14be0a4..cba94fd2752 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -119,17 +119,19 @@ 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 } diff --git a/cmd/auth/profile_picker.go b/cmd/auth/profile_picker.go new file mode 100644 index 00000000000..79fc440c566 --- /dev/null +++ b/cmd/auth/profile_picker.go @@ -0,0 +1,144 @@ +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: true, + Searcher: func(input string, index int) bool { + // Action entries (Create new / Enter host) stay visible regardless + // of the search query so the user can always reach them, including + // when the typed query doesn't match any profile. + if items[index].IsExtra { + return true + } + 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: `▸ {{if .IsExtra}}{{.Name | faint | bold}}{{else}}{{.Name | bold}}{{if .IsDefault}} {{ "[default]" | green }}{{end}}{{if .AccountID}} (account: {{.AccountID|faint}}){{else if .Host}} ({{.Host|faint}}){{end}}{{end}}`, + Inactive: ` {{if .IsExtra}}{{.Name | faint}}{{else}}{{.Name}}{{if .IsDefault}} [default]{{end}}{{if .AccountID}} (account: {{.AccountID|faint}}){{else if .Host}} ({{.Host|faint}}){{end}}{{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 51c397a9ea9..09d52d7da00 100644 --- a/cmd/auth/profiles.go +++ b/cmd/auth/profiles.go @@ -16,6 +16,7 @@ import ( "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/service/iam" "github.com/spf13/cobra" "gopkg.in/ini.v1" ) @@ -80,7 +81,7 @@ func (c *profileMetadata) Load(ctx context.Context, configFilePath string, skipV if err != nil { return } - _, err = w.CurrentUser.Me(ctx) + _, err = w.CurrentUser.Me(ctx, iam.MeRequest{}) c.Host = cfg.Host c.AuthType = cfg.AuthType if err != nil { 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 9433b6238da..e789346e0fa 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -18,10 +18,10 @@ import ( "github.com/databricks/cli/libs/databrickscfg/profile" "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/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" ) @@ -31,16 +31,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" -) - func newTokenCommand(authArguments *auth.AuthArguments) *cobra.Command { cmd := &cobra.Command{ Use: "token [PROFILE]", @@ -295,10 +285,22 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { // // Older SDK versions check for a particular substring to determine if // the OAuth authentication type can fall through or if it is a real error. - // This means we need to keep this error message constant for backwards compatibility. + // This means we need to keep "databricks OAuth is not configured for + // this host" present in the error for backwards compatibility. // // This is captured in an acceptance test under "cmd/auth/token". - err = errors.New("cache: databricks OAuth is not configured for this host") + const compatSubstring = "databricks OAuth is not configured for this host" + // When storage's notFoundHintCache wrapped the ErrNotFound with + // an actionable hint (e.g. the post-upgrade "stored credentials + // from older CLI versions are no longer used; run `databricks + // auth login`..." copy), surface it instead of the generic + // "Try logging in again with ... If this fails, please report + // this issue" trailer. The hint is more specific and avoids + // users reporting expected post-upgrade behavior as a bug. + if hint := storage.HintForNotFound(err); hint != "" { + return nil, fmt.Errorf("cache: %s. %s", compatSubstring, hint) + } + err = errors.New("cache: " + compatSubstring) } if rewritten, rewrittenErr := auth.RewriteAuthError(ctx, args.authArguments.Host, args.authArguments.AccountID, args.profileName, err); rewritten { return nil, rewrittenErr @@ -338,6 +340,18 @@ func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs return envProfile, p, nil } + // Step 2.5: Try [__settings__].default_profile from the config file. + // default_profile is advisory: if it points at a profile that no longer + // exists, fall through to the interactive picker rather than erroring. + if defaultProfile := databrickscfg.ResolveDefaultProfile(ctx); defaultProfile != "" { + p, err := loadProfileByName(ctx, defaultProfile, profiler) + if err != nil { + log.Warnf(ctx, "default_profile %q not loadable: %v", defaultProfile, err) + } else if p != nil { + return defaultProfile, p, nil + } + } + // Step 3: No env vars resolved. Load all profiles for interactive selection // or non-interactive error. allProfiles, err := profiler.LoadProfiles(ctx, profile.MatchAllProfiles) @@ -353,15 +367,20 @@ 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: + case profilePickerCreateNew: return runInlineLogin(ctx, profiler, tokenCache, mode) default: p, err := loadProfileByName(ctx, selectedName, profiler) @@ -372,57 +391,6 @@ 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. @@ -477,6 +445,7 @@ func runInlineLogin(ctx context.Context, profiler profile.Profiler, tokenCache c if err = persistentAuth.Challenge(); err != nil { return "", nil, err } + storage.PinSecureMode(ctx, mode, storage.StorageModeUnknown) clearKeys := oauthLoginClearKeys() clearKeys = append(clearKeys, databrickscfg.ExperimentalIsUnifiedHostKey) diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index 3dfa4e5d21a..40ca29f588b 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -10,15 +10,34 @@ import ( "time" "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/profile" "github.com/databricks/cli/libs/env" "github.com/databricks/databricks-sdk-go/credentials/u2m" + "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" "github.com/databricks/databricks-sdk-go/httpclient/fixtures" "github.com/stretchr/testify/assert" "golang.org/x/oauth2" ) +// upgradeHintTokenCache returns a notFoundHint-wrapped ErrNotFound on +// Lookup, mirroring what storage.notFoundHintCache produces in +// production when ~/.databricks/token-cache.json has entries and the +// resolver picked secure mode by default. Used by TestToken_loadToken +// to verify that auth token surfaces the upgrade-specific hint instead +// of dropping it for the SDK-compat constant string. +type upgradeHintTokenCache struct{} + +func (upgradeHintTokenCache) Store(string, *oauth2.Token) error { return nil } +func (upgradeHintTokenCache) Lookup(string) (*oauth2.Token, error) { + return nil, storage.NewNotFoundHint( + "stored credentials from older CLI versions are no longer used; run `databricks auth login` to sign in again, or set DATABRICKS_AUTH_STORAGE=plaintext to keep using the file cache", + ) +} + +var _ cache.TokenCache = upgradeHintTokenCache{} + type failOnCallTransport struct{} func (failOnCallTransport) RoundTrip(*http.Request) (*http.Response, error) { @@ -408,6 +427,35 @@ func TestToken_loadToken(t *testing.T) { "Try logging in again with `databricks auth login --host https://nonexistent.cloud.databricks.com` before retrying. " + "If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new", }, + { + // Regression test: when notFoundHintCache wraps ErrNotFound + // with the upgrade copy (post-upgrade default-secure user + // with a populated token-cache.json), `auth token` must + // surface that hint instead of dropping it for the SDK-compat + // constant string. The combined message keeps the + // "OAuth is not configured for this host" substring older + // SDK versions look for and skips the generic "Try logging + // in again ... If this fails, please report this issue" + // trailer, which would mislead users into reporting expected + // post-upgrade behavior. + name: "ErrNotFound carrying upgrade hint surfaces it", + args: loadTokenArgs{ + authArguments: &auth.AuthArguments{}, + profileName: "", + args: []string{"nonexistent.cloud.databricks.com"}, + tokenTimeout: 1 * time.Hour, + profiler: profiler, + tokenCache: upgradeHintTokenCache{}, + persistentAuthOpts: []u2m.PersistentAuthOption{ + u2m.WithTokenCache(upgradeHintTokenCache{}), + u2m.WithOAuthEndpointSupplier(&MockApiClient{}), + }, + }, + wantErr: "cache: databricks OAuth is not configured for this host. " + + "stored credentials from older CLI versions are no longer used; " + + "run `databricks auth login` to sign in again, " + + "or set DATABRICKS_AUTH_STORAGE=plaintext to keep using the file cache", + }, { name: "errors with clear message for non-host non-profile positional arg", args: loadTokenArgs{ @@ -533,25 +581,6 @@ func TestToken_loadToken(t *testing.T) { }, validateToken: validateToken, }, - { - name: "host with one matching profile and host-key-only token found via SDK fallback", - args: loadTokenArgs{ - authArguments: &auth.AuthArguments{ - Host: "https://legacy-ws.cloud.databricks.com", - }, - profileName: "", - args: []string{}, - tokenTimeout: 1 * time.Hour, - profiler: profiler, - tokenCache: tokenCache, - persistentAuthOpts: []u2m.PersistentAuthOption{ - u2m.WithTokenCache(tokenCache), - u2m.WithOAuthEndpointSupplier(&MockApiClient{}), - u2m.WithHttpClient(&http.Client{Transport: fixtures.SliceTransport{refreshSuccessTokenResponse}}), - }, - }, - validateToken: validateToken, - }, { name: "profile flag + positional non-host arg still errors", args: loadTokenArgs{ 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/list_targets.go b/cmd/bundle/debug/list_targets.go index 02081a93710..6349ba46725 100644 --- a/cmd/bundle/debug/list_targets.go +++ b/cmd/bundle/debug/list_targets.go @@ -35,6 +35,12 @@ func collectTargets(targets map[string]*config.Target) []targetInfo { 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, diff --git a/cmd/bundle/deployment/migrate.go b/cmd/bundle/deployment/migrate.go index 5020d88e73a..77d95e3533e 100644 --- a/cmd/bundle/deployment/migrate.go +++ b/cmd/bundle/deployment/migrate.go @@ -227,12 +227,8 @@ To start using direct engine, set "engine: direct" under bundle in your databric migratedDB := dstate.NewDatabase(stateDesc.Lineage, stateDesc.Serial+1) migratedDB.State = state - deploymentBundle := &direct.DeploymentBundle{ - StateDB: dstate.DeploymentState{ - Path: tempStatePath, - Data: migratedDB, - }, - } + deploymentBundle := &direct.DeploymentBundle{} + deploymentBundle.StateDB.OpenWithData(tempStatePath, migratedDB) tempStatePathAutoRemove := true @@ -281,8 +277,12 @@ To start using direct engine, set "engine: direct" under bundle in your databric } } + if err := deploymentBundle.StateDB.UpgradeToWrite(); err != nil { + return fmt.Errorf("upgrading state for apply: %w", err) + } + deploymentBundle.Apply(ctx, b.WorkspaceClient(ctx), plan, direct.MigrateMode(true)) - if err := deploymentBundle.StateDB.Finalize(); err != nil { + if _, err := deploymentBundle.StateDB.Finalize(ctx); err != nil { logdiag.LogError(ctx, err) } if logdiag.HasError(ctx) { @@ -310,7 +310,7 @@ Validate the migration by running "databricks bundle plan%s", there should be no The state file is not synchronized to the workspace yet. To do that and finalize the migration, run "bundle deploy%s". To undo the migration, remove %s and rename %s to %s -`, len(deploymentBundle.StateDB.Data.State), localPath, extraArgsStr, extraArgsStr, localPath, localTerraformBackupPath, localTerraformPath)) +`, len(state), localPath, extraArgsStr, extraArgsStr, localPath, localTerraformBackupPath, localTerraformPath)) return nil } diff --git a/cmd/bundle/destroy.go b/cmd/bundle/destroy.go index bd69e1a7513..a4f85667a27 100644 --- a/cmd/bundle/destroy.go +++ b/cmd/bundle/destroy.go @@ -11,6 +11,7 @@ import ( "github.com/databricks/cli/bundle/statemgmt" "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/agent" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" @@ -50,7 +51,10 @@ Typical use cases: func CommandBundleDestroy(cmd *cobra.Command, args []string, autoApprove, forceDestroy bool) error { // We require auto-approve for non-interactive terminals since prompts are not possible. if !cmdio.IsPromptSupported(cmd.Context()) && !autoApprove { - return errors.New("please specify --auto-approve since terminal does not support interactive prompts") + return errors.New("this command will destroy all resources deployed by this bundle, " + + "including workspace files in the deployment directory.\n" + + phases.DataLossWarning + "\n" + + "To proceed, use --auto-approve." + agent.AgentNotice()) } // Check if context is already initialized (e.g., when called from apps delete override) diff --git a/cmd/bundle/generate/dashboard.go b/cmd/bundle/generate/dashboard.go index 70de46225c2..7af4e01e92f 100644 --- a/cmd/bundle/generate/dashboard.go +++ b/cmd/bundle/generate/dashboard.go @@ -16,6 +16,8 @@ import ( "time" "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/deploy/terraform" + "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/bundle/generate" "github.com/databricks/cli/bundle/phases" "github.com/databricks/cli/bundle/resources" @@ -389,16 +391,25 @@ func (d *dashboard) runForResource(ctx context.Context, b *bundle.Bundle) { return } + var state statemgmt.ExportedResourcesMap if stateDesc.Engine.IsDirect() { _, localPath := b.StateFilenameDirect(ctx) - if err := b.DeploymentBundle.StateDB.Open(localPath); err != nil { + if err := b.DeploymentBundle.StateDB.Open(ctx, localPath, dstate.WithRecovery(true), dstate.WithWrite(false)); err != nil { + logdiag.LogError(ctx, err) + return + } + state = b.DeploymentBundle.ExportState(ctx) + } else { + var err error + state, err = terraform.ParseResourcesState(ctx, b) + if err != nil { logdiag.LogError(ctx, err) return } } bundle.ApplySeqContext(ctx, b, - statemgmt.Load(stateDesc.Engine), + statemgmt.Load(state), ) if logdiag.HasError(ctx) { return diff --git a/cmd/bundle/generate/job.go b/cmd/bundle/generate/job.go index 56bc8d582b7..c3aba49c5f2 100644 --- a/cmd/bundle/generate/job.go +++ b/cmd/bundle/generate/job.go @@ -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/open.go b/cmd/bundle/open.go index ca2bec04d0f..d357b4f39e1 100644 --- a/cmd/bundle/open.go +++ b/cmd/bundle/open.go @@ -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 diff --git a/cmd/bundle/summary.go b/cmd/bundle/summary.go index 9f2aa3e54bb..b3a55a607cc 100644 --- a/cmd/bundle/summary.go +++ b/cmd/bundle/summary.go @@ -1,14 +1,10 @@ package bundle import ( - "context" - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/render" "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" - "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/logdiag" "github.com/spf13/cobra" @@ -25,37 +21,23 @@ Useful after deployment to see what was created and where to find it.`, var forcePull bool var includeLocations bool - var shouldShowFullConfig bool cmd.Flags().BoolVar(&forcePull, "force-pull", false, "Skip local cache and load the state from the remote workspace") cmd.Flags().BoolVar(&includeLocations, "include-locations", false, "Include location information in the output") cmd.Flags().MarkHidden("include-locations") - cmd.Flags().BoolVar(&shouldShowFullConfig, "show-full-config", false, "Load and output the full bundle config") - cmd.Flags().MarkHidden("show-full-config") cmd.RunE = func(cmd *cobra.Command, args []string) error { - var err error - if shouldShowFullConfig { - ctx := logdiag.InitContext(cmd.Context()) - cmd.SetContext(ctx) - logdiag.SetSeverity(ctx, diag.Warning) - - err = showFullConfig(ctx, cmd, includeLocations) - if err != nil { - return err - } - } else { - b, err := utils.ProcessBundle(cmd, utils.ProcessOptions{ - ReadState: true, - IncludeLocations: includeLocations, - InitIDs: true, - }) - if err != nil { - return err - } - err = showSummary(cmd, b) - if err != nil { - return err - } + b, err := utils.ProcessBundle(cmd, utils.ProcessOptions{ + ReadState: true, + AlwaysPull: forcePull, + IncludeLocations: includeLocations, + InitIDs: true, + }) + if err != nil { + return err + } + err = showSummary(cmd, b) + if err != nil { + return err } if logdiag.HasError(cmd.Context()) { @@ -68,35 +50,6 @@ Useful after deployment to see what was created and where to find it.`, return cmd } -func showFullConfig(ctx context.Context, cmd *cobra.Command, includeLocations bool) error { - // call `MustLoad` directly instead of `MustConfigureBundle` because the latter does - // validation that we're not interested in here - b := bundle.MustLoad(ctx) - if b == nil || logdiag.HasError(ctx) { - return nil - } - - mutator.DefaultMutators(ctx, b) - if logdiag.HasError(ctx) { - return nil - } - - if includeLocations { - // Include location information in the output - bundle.ApplyContext(ctx, b, mutator.PopulateLocations()) - if logdiag.HasError(ctx) { - return nil - } - } - - err := renderJsonOutput(cmd, b) - if err != nil { - return err - } - - return nil -} - func showSummary(cmd *cobra.Command, b *bundle.Bundle) error { if root.OutputType(cmd) == flags.OutputText { return render.RenderSummary(cmd.Context(), cmd.OutOrStdout(), b) diff --git a/cmd/bundle/utils/process.go b/cmd/bundle/utils/process.go index a6f48d99fa2..5f43cff6acd 100644 --- a/cmd/bundle/utils/process.go +++ b/cmd/bundle/utils/process.go @@ -11,8 +11,10 @@ import ( "github.com/databricks/cli/bundle/config/engine" "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/validate" + "github.com/databricks/cli/bundle/deploy/terraform" "github.com/databricks/cli/bundle/deployplan" "github.com/databricks/cli/bundle/direct" + "github.com/databricks/cli/bundle/direct/dstate" "github.com/databricks/cli/bundle/phases" "github.com/databricks/cli/bundle/statemgmt" "github.com/databricks/cli/cmd/root" @@ -187,7 +189,7 @@ func ProcessBundleRet(cmd *cobra.Command, opts ProcessOptions) (b *bundle.Bundle needDirectState := stateDesc.Engine.IsDirect() && (opts.InitIDs || opts.ErrorOnEmptyState || opts.Deploy || opts.ReadPlanPath != "" || opts.PreDeployChecks || opts.PostStateFunc != nil) if needDirectState { _, localPath := b.StateFilenameDirect(ctx) - if err := b.DeploymentBundle.StateDB.Open(localPath); err != nil { + if err := b.DeploymentBundle.StateDB.Open(ctx, localPath, dstate.WithRecovery(true), dstate.WithWrite(false)); err != nil { logdiag.LogError(ctx, err) return b, stateDesc, root.ErrAlreadyPrinted } @@ -199,8 +201,19 @@ func ProcessBundleRet(cmd *cobra.Command, opts ProcessOptions) (b *bundle.Bundle if opts.ErrorOnEmptyState { modes = append(modes, statemgmt.ErrorOnEmptyState) } + var state statemgmt.ExportedResourcesMap + if stateDesc.Engine.IsDirect() { + state = b.DeploymentBundle.ExportState(ctx) + } else { + var err error + state, err = terraform.ParseResourcesState(ctx, b) + if err != nil { + logdiag.LogError(ctx, err) + return b, stateDesc, root.ErrAlreadyPrinted + } + } mutators := []bundle.Mutator{ - statemgmt.Load(stateDesc.Engine, modes...), + statemgmt.Load(state, modes...), } // InitializeURLs makes an extra API call; only run it when URLs are needed. if opts.InitIDs { diff --git a/cmd/cmd.go b/cmd/cmd.go index 014471f7638..1587fbdc7ea 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -4,6 +4,7 @@ import ( "context" "strings" + aitoolscmd "github.com/databricks/cli/cmd/aitools" "github.com/databricks/cli/cmd/psql" ssh "github.com/databricks/cli/experimental/ssh/cmd" @@ -56,6 +57,13 @@ func New(ctx context.Context) *cobra.Command { // Add workspace subcommands. workspaceCommands := workspace.All() for _, cmd := range workspaceCommands { + // The auto-generated `bundle` workspace service (DMS) shares its name + // with the DAB `bundle` command tree (cmd/bundle). Registering both + // here clobbers the DAB tree's help output. Skip the generated one; + // callers still have `databricks api ...` for the DMS endpoints. + if cmd.Name() == "bundle" { + continue + } // Order the permissions subcommands after the main commands. for _, sub := range cmd.Commands() { // some commands override groups in overrides.go, leave them as-is @@ -93,6 +101,7 @@ func New(ctx context.Context) *cobra.Command { } // Add other subcommands. + cli.AddCommand(aitoolscmd.NewAitoolsCmd()) cli.AddCommand(api.New()) cli.AddCommand(auth.New()) cli.AddCommand(completion.New()) diff --git a/cmd/configure/configure.go b/cmd/configure/configure.go index 0d6ad09def4..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 } diff --git a/cmd/experimental/experimental.go b/cmd/experimental/experimental.go index 36ad8765898..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,7 @@ 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/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 f3d4bc7d6c4..32a74b6808f 100644 --- a/cmd/labs/project/installer.go +++ b/cmd/labs/project/installer.go @@ -20,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" ) @@ -152,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 { @@ -288,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/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/pipelines/history_test.go b/cmd/pipelines/history_test.go index 86d6d7d9d6e..c1baec58327 100644 --- a/cmd/pipelines/history_test.go +++ b/cmd/pipelines/history_test.go @@ -24,25 +24,25 @@ func TestUpdatesBefore(t *testing.T) { }{ { name: "before 700", - timestamp: int64Ptr(700), + timestamp: new(int64(700)), expectedCount: 3, expectedFirst: 600, }, { name: "before 1000", - timestamp: int64Ptr(1000), + timestamp: new(int64(1000)), expectedCount: 5, expectedFirst: 1000, }, { name: "before 200", - timestamp: int64Ptr(200), + timestamp: new(int64(200)), expectedCount: 1, expectedFirst: 200, }, { name: "before 100", - timestamp: int64Ptr(100), + timestamp: new(int64(100)), expectedCount: 0, expectedFirst: 0, }, @@ -79,25 +79,25 @@ func TestUpdatesAfter(t *testing.T) { }{ { name: "after 500", - timestamp: int64Ptr(500), + timestamp: new(int64(500)), expectedCount: 3, expectedFirst: 1000, }, { name: "after 200", - timestamp: int64Ptr(200), + timestamp: new(int64(200)), expectedCount: 5, expectedFirst: 1000, }, { name: "after 1000", - timestamp: int64Ptr(1000), + timestamp: new(int64(1000)), expectedCount: 1, expectedFirst: 1000, }, { name: "after 1200", - timestamp: int64Ptr(1200), + timestamp: new(int64(1200)), expectedCount: 0, expectedFirst: 0, }, @@ -181,7 +181,7 @@ func TestFilterUpdates(t *testing.T) { name: "start time nil, end time set", updates: updates, startTime: nil, - endTime: int64Ptr(700), + endTime: new(int64(700)), expectedCount: 3, expectedFirst: 600, expectedLast: 200, @@ -189,7 +189,7 @@ func TestFilterUpdates(t *testing.T) { { name: "start time set, end time nil", updates: updates, - startTime: int64Ptr(500), + startTime: new(int64(500)), endTime: nil, expectedCount: 3, expectedFirst: 1000, @@ -198,8 +198,8 @@ func TestFilterUpdates(t *testing.T) { { name: "both times set within range", updates: updates, - startTime: int64Ptr(300), - endTime: int64Ptr(900), + startTime: new(int64(300)), + endTime: new(int64(900)), expectedCount: 3, expectedFirst: 800, expectedLast: 400, @@ -207,8 +207,8 @@ func TestFilterUpdates(t *testing.T) { { name: "both times set, no overlap", updates: updates, - startTime: int64Ptr(1200), - endTime: int64Ptr(1500), + startTime: new(int64(1200)), + endTime: new(int64(1500)), expectedCount: 0, expectedFirst: 0, expectedLast: 0, @@ -216,7 +216,7 @@ func TestFilterUpdates(t *testing.T) { { name: "start time after all updates", updates: updates, - startTime: int64Ptr(1200), + startTime: new(int64(1200)), endTime: nil, expectedCount: 0, expectedFirst: 0, @@ -226,7 +226,7 @@ func TestFilterUpdates(t *testing.T) { name: "end time before all updates", updates: updates, startTime: nil, - endTime: int64Ptr(100), + endTime: new(int64(100)), expectedCount: 0, expectedFirst: 0, expectedLast: 0, @@ -234,8 +234,8 @@ func TestFilterUpdates(t *testing.T) { { name: "start time after end time but within range", updates: updates, - startTime: int64Ptr(700), - endTime: int64Ptr(500), + startTime: new(int64(700)), + endTime: new(int64(500)), expectedCount: 0, expectedFirst: 0, expectedLast: 0, @@ -243,8 +243,8 @@ func TestFilterUpdates(t *testing.T) { { name: "start and end time match exact values in list", updates: updates, - startTime: int64Ptr(400), - endTime: int64Ptr(800), + startTime: new(int64(400)), + endTime: new(int64(800)), expectedCount: 3, expectedFirst: 800, expectedLast: 400, @@ -289,6 +289,3 @@ func TestFilterUpdates(t *testing.T) { }) } } - -// Helper function to create int64 pointers -func int64Ptr(v int64) *int64 { return &v } diff --git a/cmd/psql/psql_autoscaling.go b/cmd/psql/psql_autoscaling.go index 00c555e4c12..331e05b55b5 100644 --- a/cmd/psql/psql_autoscaling.go +++ b/cmd/psql/psql_autoscaling.go @@ -10,6 +10,7 @@ import ( "github.com/databricks/cli/libs/cmdio" libpsql "github.com/databricks/cli/libs/psql" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/postgres" ) @@ -37,7 +38,7 @@ func connectAutoscaling(ctx context.Context, projectID, branchID, endpointID str return err } - user, err := w.CurrentUser.Me(ctx) + user, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return fmt.Errorf("failed to get current user: %w", err) } diff --git a/cmd/psql/psql_provisioned.go b/cmd/psql/psql_provisioned.go index 88ca1bb9181..b66f17d1508 100644 --- a/cmd/psql/psql_provisioned.go +++ b/cmd/psql/psql_provisioned.go @@ -10,6 +10,7 @@ import ( libpsql "github.com/databricks/cli/libs/psql" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/database" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/google/uuid" ) @@ -26,7 +27,7 @@ func connectProvisioned(ctx context.Context, instanceName string, retryConfig li return err } - user, err := w.CurrentUser.Me(ctx) + user, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return fmt.Errorf("failed to get current user: %w", err) } diff --git a/cmd/root/auth.go b/cmd/root/auth.go index 0c3c77233f6..f458f0f4695 100644 --- a/cmd/root/auth.go +++ b/cmd/root/auth.go @@ -12,7 +12,6 @@ import ( "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" envlib "github.com/databricks/cli/libs/env" - "github.com/databricks/cli/libs/log" "github.com/databricks/cli/libs/logdiag" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/config" @@ -299,14 +298,8 @@ func resolveDefaultProfile(ctx context.Context, cfg *config.Config) { if cfg.Profile != "" || envlib.Get(ctx, "DATABRICKS_CONFIG_PROFILE") != "" { return } - configFilePath := envlib.Get(ctx, "DATABRICKS_CONFIG_FILE") - resolvedProfile, err := databrickscfg.GetConfiguredDefaultProfile(ctx, configFilePath) - if err != nil { - log.Warnf(ctx, "Failed to load default profile: %v", err) - return - } - if resolvedProfile != "" { - cfg.Profile = resolvedProfile + if resolved := databrickscfg.ResolveDefaultProfile(ctx); resolved != "" { + cfg.Profile = resolved } } diff --git a/cmd/root/auth_test.go b/cmd/root/auth_test.go index b0faadd94bf..c75fd0209f7 100644 --- a/cmd/root/auth_test.go +++ b/cmd/root/auth_test.go @@ -514,6 +514,7 @@ token = named-token w := cmdctx.WorkspaceClient(cmd.Context()) require.NotNil(t, w) - assert.Equal(t, "", w.Config.Profile) + // Pinned so the OAuth cache key matches what `databricks auth login` writes. + assert.Equal(t, "DEFAULT", w.Config.Profile) assert.Equal(t, "https://default.cloud.databricks.com", w.Config.Host) } diff --git a/cmd/root/bundle.go b/cmd/root/bundle.go index 234ca6211bf..a17d88f5fcf 100644 --- a/cmd/root/bundle.go +++ b/cmd/root/bundle.go @@ -70,6 +70,18 @@ func getProfile(cmd *cobra.Command) (value string) { // configureProfile applies the profile flag to the bundle. func configureProfile(cmd *cobra.Command, b *bundle.Bundle) { profile := getProfile(cmd) + + // Fall back to [__settings__].default_profile only when the bundle does + // not pin its own host. The legacy [DEFAULT] section is intentionally + // NOT considered here: a hostless bundle silently routing to whatever + // [DEFAULT] points at could deploy to the wrong workspace and mask a + // missing workspace.host. Auth-only paths use the broader + // databrickscfg.ResolveDefaultProfile, which also accepts [DEFAULT]. + if profile == "" && b.Config.Workspace.Host == "" && b.Config.Workspace.Profile == "" { + configFilePath := envlib.Get(cmd.Context(), "DATABRICKS_CONFIG_FILE") + profile, _ = databrickscfg.GetConfiguredDefaultProfile(cmd.Context(), configFilePath) + } + if profile == "" { return } diff --git a/cmd/root/bundle_test.go b/cmd/root/bundle_test.go index 8c021fe77f2..6116003ea70 100644 --- a/cmd/root/bundle_test.go +++ b/cmd/root/bundle_test.go @@ -26,7 +26,7 @@ func setupDatabricksCfg(t *testing.T) { homeEnvVar = "USERPROFILE" } - cfg := []byte("[PROFILE-1]\nhost = https://a.com\ntoken = a\n[PROFILE-2]\nhost = https://a.com\ntoken = b\n") + cfg := []byte("[PROFILE-1]\nhost = https://a.test\ntoken = a\n[PROFILE-2]\nhost = https://a.test\ntoken = b\n") err := os.WriteFile(filepath.Join(tempHomeDir, ".databrickscfg"), cfg, 0o644) assert.NoError(t, err) @@ -63,6 +63,26 @@ workspace: return logdiag.FlushCollected(ctx) } +// setupBundleNameOnly writes a databricks.yml that declares only the bundle +// name (no workspace host, no profile). Used to exercise the +// [__settings__].default_profile fallback in configureProfile. +func setupBundleNameOnly(t *testing.T, cmd *cobra.Command) []diag.Diagnostic { + rootPath := t.TempDir() + t.Chdir(rootPath) + + contents := `bundle: + name: test-default-profile +` + err := os.WriteFile(filepath.Join(rootPath, "databricks.yml"), []byte(contents), 0o644) + require.NoError(t, err) + + ctx := logdiag.InitContext(cmd.Context()) + logdiag.SetCollect(ctx, true) + cmd.SetContext(ctx) + _ = MustConfigureBundle(cmd) + return logdiag.FlushCollected(ctx) +} + func setupWithProfile(t *testing.T, cmd *cobra.Command, profile string) []diag.Diagnostic { setupDatabricksCfg(t) @@ -95,17 +115,17 @@ func TestBundleConfigureDefault(t *testing.T) { } cmd := emptyCommand(t) - diags := setupWithHost(t, cmd, "https://x.com") + diags := setupWithHost(t, cmd, "https://x.test") require.Empty(t, diags) - assert.Equal(t, "https://x.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://x.test", cmdctx.ConfigUsed(cmd.Context()).Host) } func TestBundleConfigureWithMultipleMatches(t *testing.T) { testutil.CleanupEnvironment(t) cmd := emptyCommand(t) - diags := setupWithHost(t, cmd, "https://a.com") + diags := setupWithHost(t, cmd, "https://a.test") require.Len(t, diags, 1) assert.Contains(t, diags[0].Summary, "multiple profiles matched: PROFILE-1, PROFILE-2") assert.Contains(t, diags[0].Summary, "Matching workspace profiles: PROFILE-1, PROFILE-2") @@ -119,7 +139,7 @@ func TestBundleConfigureWithNonExistentProfileFlag(t *testing.T) { err := cmd.Flag("profile").Value.Set("NOEXIST") require.NoError(t, err) - diags := setupWithHost(t, cmd, "https://x.com") + diags := setupWithHost(t, cmd, "https://x.test") require.Len(t, diags, 1) assert.Contains(t, diags[0].Summary, "has no NOEXIST profile configured") } @@ -131,8 +151,8 @@ func TestBundleConfigureWithMismatchedProfile(t *testing.T) { err := cmd.Flag("profile").Value.Set("PROFILE-1") require.NoError(t, err) - diags := setupWithHost(t, cmd, "https://x.com") - assert.Equal(t, []diag.Diagnostic{{Summary: "cannot resolve bundle auth configuration: the host in the profile (https://a.com) doesn’t match the host configured in the bundle (https://x.com)"}}, diags) + diags := setupWithHost(t, cmd, "https://x.test") + assert.Equal(t, []diag.Diagnostic{{Summary: "cannot resolve bundle auth configuration: the host in the profile (https://a.test) doesn’t match the host configured in the bundle (https://x.test)"}}, diags) } func TestBundleConfigureWithCorrectProfile(t *testing.T) { @@ -141,10 +161,10 @@ func TestBundleConfigureWithCorrectProfile(t *testing.T) { cmd := emptyCommand(t) err := cmd.Flag("profile").Value.Set("PROFILE-1") require.NoError(t, err) - diags := setupWithHost(t, cmd, "https://a.com") + diags := setupWithHost(t, cmd, "https://a.test") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "PROFILE-1", cmdctx.ConfigUsed(cmd.Context()).Profile) } @@ -154,8 +174,8 @@ func TestBundleConfigureWithMismatchedProfileEnvVariable(t *testing.T) { t.Setenv("DATABRICKS_CONFIG_PROFILE", "PROFILE-1") cmd := emptyCommand(t) - diags := setupWithHost(t, cmd, "https://x.com") - assert.Equal(t, []diag.Diagnostic{{Summary: "cannot resolve bundle auth configuration: the host in the profile (https://a.com) doesn’t match the host configured in the bundle (https://x.com)"}}, diags) + diags := setupWithHost(t, cmd, "https://x.test") + assert.Equal(t, []diag.Diagnostic{{Summary: "cannot resolve bundle auth configuration: the host in the profile (https://a.test) doesn’t match the host configured in the bundle (https://x.test)"}}, diags) } func TestBundleConfigureWithProfileFlagAndEnvVariable(t *testing.T) { @@ -166,9 +186,9 @@ func TestBundleConfigureWithProfileFlagAndEnvVariable(t *testing.T) { err := cmd.Flag("profile").Value.Set("PROFILE-1") require.NoError(t, err) - diags := setupWithHost(t, cmd, "https://a.com") + diags := setupWithHost(t, cmd, "https://a.test") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "PROFILE-1", cmdctx.ConfigUsed(cmd.Context()).Profile) } @@ -180,7 +200,7 @@ func TestBundleConfigureProfileDefault(t *testing.T) { diags := setupWithProfile(t, cmd, "PROFILE-1") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "a", cmdctx.ConfigUsed(cmd.Context()).Token) assert.Equal(t, "PROFILE-1", cmdctx.ConfigUsed(cmd.Context()).Profile) } @@ -195,7 +215,7 @@ func TestBundleConfigureProfileFlag(t *testing.T) { diags := setupWithProfile(t, cmd, "PROFILE-1") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "b", cmdctx.ConfigUsed(cmd.Context()).Token) assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) } @@ -209,11 +229,99 @@ func TestBundleConfigureProfileEnvVariable(t *testing.T) { diags := setupWithProfile(t, cmd, "PROFILE-1") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "b", cmdctx.ConfigUsed(cmd.Context()).Token) assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) } +// setupDatabricksCfgWithDefault writes a databrickscfg with two profiles +// and an explicit [__settings__].default_profile. +func setupDatabricksCfgWithDefault(t *testing.T, defaultProfile string) { + tempHomeDir := t.TempDir() + homeEnvVar := "HOME" + if runtime.GOOS == "windows" { + homeEnvVar = "USERPROFILE" + } + + cfg := fmt.Sprintf(`[PROFILE-1] +host = https://a.test +token = a + +[PROFILE-2] +host = https://b.test +token = b + +[__settings__] +default_profile = %s +`, defaultProfile) + err := os.WriteFile(filepath.Join(tempHomeDir, ".databrickscfg"), []byte(cfg), 0o644) + require.NoError(t, err) + + t.Setenv("DATABRICKS_CONFIG_FILE", "") + t.Setenv(homeEnvVar, tempHomeDir) +} + +func TestBundleConfigureWithDefaultProfile(t *testing.T) { + testutil.CleanupEnvironment(t) + setupDatabricksCfgWithDefault(t, "PROFILE-1") + + cmd := emptyCommand(t) + diags := setupBundleNameOnly(t, cmd) + require.Empty(t, diags) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "PROFILE-1", cmdctx.ConfigUsed(cmd.Context()).Profile) +} + +func TestBundleConfigureWithDefaultProfile_ProfileFlagOverrides(t *testing.T) { + testutil.CleanupEnvironment(t) + setupDatabricksCfgWithDefault(t, "PROFILE-1") + + cmd := emptyCommand(t) + require.NoError(t, cmd.Flag("profile").Value.Set("PROFILE-2")) + diags := setupBundleNameOnly(t, cmd) + require.Empty(t, diags) + assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) +} + +func TestBundleConfigureWithDefaultProfile_EnvVarOverrides(t *testing.T) { + testutil.CleanupEnvironment(t) + setupDatabricksCfgWithDefault(t, "PROFILE-1") + t.Setenv("DATABRICKS_CONFIG_PROFILE", "PROFILE-2") + + cmd := emptyCommand(t) + diags := setupBundleNameOnly(t, cmd) + require.Empty(t, diags) + assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) +} + +func TestBundleConfigureWithDefaultProfile_BundleHostWins(t *testing.T) { + testutil.CleanupEnvironment(t) + // PROFILE-1 points at https://a.test, but the bundle pins https://b.test. + // The host-empty guard in configureProfile must NOT apply default_profile, + // otherwise the user would silently end up at the wrong host. Instead, the + // SDK matches the bundle's host against PROFILE-2 (which has host=b.test). + setupDatabricksCfgWithDefault(t, "PROFILE-1") + + rootPath := t.TempDir() + t.Chdir(rootPath) + + contents := `workspace: + host: "https://b.test" +` + err := os.WriteFile(filepath.Join(rootPath, "databricks.yml"), []byte(contents), 0o644) + require.NoError(t, err) + + cmd := emptyCommand(t) + ctx := logdiag.InitContext(cmd.Context()) + logdiag.SetCollect(ctx, true) + cmd.SetContext(ctx) + _ = MustConfigureBundle(cmd) + diags := logdiag.FlushCollected(ctx) + require.Empty(t, diags) + assert.Equal(t, "https://b.test", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) +} + func TestBundleConfigureProfileFlagAndEnvVariable(t *testing.T) { testutil.CleanupEnvironment(t) @@ -225,7 +333,7 @@ func TestBundleConfigureProfileFlagAndEnvVariable(t *testing.T) { diags := setupWithProfile(t, cmd, "PROFILE-1") require.Empty(t, diags) - assert.Equal(t, "https://a.com", cmdctx.ConfigUsed(cmd.Context()).Host) + assert.Equal(t, "https://a.test", cmdctx.ConfigUsed(cmd.Context()).Host) assert.Equal(t, "b", cmdctx.ConfigUsed(cmd.Context()).Token) assert.Equal(t, "PROFILE-2", cmdctx.ConfigUsed(cmd.Context()).Profile) } @@ -240,7 +348,7 @@ func TestBundleConfigureMultiMatchInteractivePromptFires(t *testing.T) { contents := ` workspace: - host: "https://a.com" + host: "https://a.test" ` err := os.WriteFile(filepath.Join(rootPath, "databricks.yml"), []byte(contents), 0o644) require.NoError(t, err) @@ -265,7 +373,7 @@ workspace: }() // Verify the prompt fires by reading output from stderr. - // promptui with StartInSearchMode writes a search cursor first. + // cmdio.RunSelect with StartInSearchMode writes a search cursor first. line, _, readErr := io.Stderr.ReadLine() if assert.NoError(t, readErr, "expected prompt output on stderr") { assert.Contains(t, string(line), "Search:") diff --git a/cmd/selftest/tui/ask.go b/cmd/selftest/tui/ask.go new file mode 100644 index 00000000000..1d64eecde64 --- /dev/null +++ b/cmd/selftest/tui/ask.go @@ -0,0 +1,45 @@ +package tui + +import ( + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func newAskCmd() *cobra.Command { + var defaultVal string + cmd := &cobra.Command{ + Use: "ask", + Short: "cmdio.Ask (single-line text input with optional default)", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + ans, err := cmdio.Ask(ctx, "Enter a value", defaultVal) + if err != nil { + return err + } + cmdio.LogString(ctx, "Entered: "+ans) + return nil + }, + } + cmd.Flags().StringVar(&defaultVal, "default", "", "default returned if the user just presses Enter") + return cmd +} + +func newAskYesOrNoCmd() *cobra.Command { + return &cobra.Command{ + Use: "ask-yes-no", + Short: "cmdio.AskYesOrNo (yes/no question)", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + ans, err := cmdio.AskYesOrNo(ctx, "Continue") + if err != nil { + return err + } + if ans { + cmdio.LogString(ctx, "Answer: yes") + } else { + cmdio.LogString(ctx, "Answer: no") + } + return nil + }, + } +} diff --git a/cmd/selftest/tui/colors.go b/cmd/selftest/tui/colors.go new file mode 100644 index 00000000000..9afc6fd693f --- /dev/null +++ b/cmd/selftest/tui/colors.go @@ -0,0 +1,25 @@ +package tui + +import ( + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func newColorsCmd() *cobra.Command { + return &cobra.Command{ + Use: "colors", + Short: "Print colored text to verify color support", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + swatch := "the quick brown fox jumps over the lazy dog" + cmdio.LogString(ctx, "red: "+cmdio.Red(ctx, swatch)) + cmdio.LogString(ctx, "green: "+cmdio.Green(ctx, swatch)) + cmdio.LogString(ctx, "yellow: "+cmdio.Yellow(ctx, swatch)) + cmdio.LogString(ctx, "blue: "+cmdio.Blue(ctx, swatch)) + cmdio.LogString(ctx, "cyan: "+cmdio.Cyan(ctx, swatch)) + cmdio.LogString(ctx, "hiblack: "+cmdio.HiBlack(ctx, swatch)) + cmdio.LogString(ctx, "hiblue: "+cmdio.HiBlue(ctx, swatch)) + return nil + }, + } +} diff --git a/cmd/selftest/tui/fixtures.go b/cmd/selftest/tui/fixtures.go new file mode 100644 index 00000000000..01cd2873abf --- /dev/null +++ b/cmd/selftest/tui/fixtures.go @@ -0,0 +1,175 @@ +package tui + +import ( + "context" + "fmt" + "time" + + "github.com/databricks/cli/libs/cmdio" +) + +type spinnerMessage struct { + text string + duration time.Duration +} + +var spinnerMessages = []spinnerMessage{ + {"Initializing...", time.Second}, + {"Loading configuration", time.Second}, + {"Connecting to workspace", time.Second}, + {"Processing files", time.Second}, + {"Finalizing", time.Second}, +} + +// databricksFeatures is a stable list of Databricks product / feature names +// used as fixture data for the prompt scenarios. Drawn from the public docs +// (https://docs.databricks.com) so the demo data looks like something a user +// would actually encounter. +var databricksFeatures = []string{ + "unity-catalog", + "delta-lake", + "delta-sharing", + "photon", + "mlflow", + "mosaic-ai", + "genie", + "lakeflow-connect", + "lakeflow-jobs", + "vector-search", + "model-serving", + "feature-store", + "databricks-sql", + "ai-playground", + "foundation-models", + "lakehouse-monitoring", + "liquid-clustering", + "predictive-optimization", + "governed-tags", + "lakeflow-designer", +} + +// buildItems uses zero-padded ids so the alphabetical Select scenario has +// a stable sort order. +func buildItems(n int) []cmdio.Tuple { + n = min(n, len(databricksFeatures)) + items := make([]cmdio.Tuple, 0, n) + for i := range n { + items = append(items, cmdio.Tuple{ + Name: databricksFeatures[i], + Id: fmt.Sprintf("id-%02d", i+1), + }) + } + return items +} + +// buildFilterItems returns 15 items where 5 share the substring "lake", so +// progressive typing narrows the list, and a non-matching substring ("xyz") +// hits the "No results" path. +func buildFilterItems() []cmdio.Tuple { + names := []string{ + "lakehouse-monitoring", + "lakeflow-connect", + "lakeflow-jobs", + "delta-lake", + "lakebase-postgres", + "unity-catalog", + "mosaic-ai", + "vector-search", + "model-serving", + "feature-store", + "ai-playground", + "genie-spaces", + "mlflow-tracking", + "liquid-clustering", + "predictive-optimization", + } + items := make([]cmdio.Tuple, 0, len(names)) + for i, name := range names { + items = append(items, cmdio.Tuple{ + Name: name, + Id: fmt.Sprintf("id-%02d", i+1), + }) + } + return items +} + +// buildLongItems uses fully-qualified workspace URLs as ids so that the +// rendered field overflows a typical terminal width. +func buildLongItems() []cmdio.Tuple { + hosts := []string{ + "https://adb-1234567890123456.78.azuredatabricks.net/?o=1234567890123456", + "https://adb-2345678901234567.89.azuredatabricks.net/?o=2345678901234567", + "https://acme-prod.cloud.databricks.com/?o=3456789012345678", + "https://acme-staging.cloud.databricks.com/?o=4567890123456789", + "https://acme-dev.cloud.databricks.com/?o=5678901234567890", + "https://1234567890123456.7.gcp.databricks.com/?o=6789012345678901", + "https://2345678901234567.8.gcp.databricks.com/?o=7890123456789012", + "https://field-eng-east.cloud.databricks.com/?o=8901234567890123", + } + items := make([]cmdio.Tuple, 0, len(hosts)) + for i, host := range hosts { + items = append(items, cmdio.Tuple{ + Name: fmt.Sprintf("workspace-%02d", i+1), + Id: host, + }) + } + return items +} + +// clusterItem mirrors libs/databrickscfg/cfgpickers/clusters.go's +// compatibleCluster: State, Access, and Runtime are exposed as methods so +// the Active/Inactive templates exercise text/template's method-resolution +// path, and State returns a pre-rendered colored string (matching the +// renderedState cache in production) so the demo also exercises ANSI codes +// emitted from inside a template. +type clusterItem struct { + Name string + Id string + + access string + runtimeName string + renderedState string +} + +func (c clusterItem) Access() string { return c.access } +func (c clusterItem) Runtime() string { return c.runtimeName } +func (c clusterItem) State() string { return c.renderedState } + +func buildClusterItems(ctx context.Context) []clusterItem { + green := func(s string) string { return cmdio.Green(ctx, s) } + red := func(s string) string { return cmdio.Red(ctx, s) } + blue := func(s string) string { return cmdio.Blue(ctx, s) } + return []clusterItem{ + {Name: "shared-autoscaling-prod", Id: "0123-456789-abcdef01", access: "Shared", runtimeName: "DBR 14.3 LTS", renderedState: green("RUNNING")}, + {Name: "ml-gpu-experiments", Id: "0123-456789-abcdef02", access: "Assigned", runtimeName: "DBR 15.0 ML", renderedState: red("TERMINATED")}, + {Name: "job-compute-bronze-etl", Id: "0123-456789-abcdef03", access: "Shared", runtimeName: "DBR 13.3 LTS", renderedState: green("RUNNING")}, + {Name: "interactive-analytics", Id: "0123-456789-abcdef04", access: "Assigned", runtimeName: "DBR 14.3", renderedState: blue("PENDING")}, + {Name: "photon-streaming-realtime", Id: "0123-456789-abcdef05", access: "Shared", runtimeName: "DBR 14.3 Photon", renderedState: green("RUNNING")}, + {Name: "single-node-dev", Id: "0123-456789-abcdef06", access: "Assigned", runtimeName: "DBR 14.3 LTS", renderedState: red("TERMINATED")}, + {Name: "all-purpose-shared", Id: "0123-456789-abcdef07", access: "Shared", runtimeName: "DBR 15.0", renderedState: green("RUNNING")}, + {Name: "legacy-data-eng", Id: "0123-456789-abcdef08", access: "Assigned", runtimeName: "DBR 12.2 LTS", renderedState: red("TERMINATED")}, + } +} + +// profileItem mirrors the profile picker in cmd/auth/token.go: regular items +// have a Host, the trailing meta items do not (so the {{if .Host}} branch fires). +type profileItem struct { + Name string + Host string +} + +// buildProfileItems returns 6 profile-shaped items across AWS / Azure / GCP +// hosts plus the two trailing meta-rows ("Create a new profile", "Enter a +// host URL manually") used by the real profile picker. +func buildProfileItems() []profileItem { + return []profileItem{ + {Name: "DEFAULT", Host: "https://acme.cloud.databricks.com"}, + {Name: "production", Host: "https://acme-prod.cloud.databricks.com"}, + {Name: "staging", Host: "https://acme-stg.cloud.databricks.com"}, + {Name: "field-eng", Host: "https://field-eng.cloud.databricks.com"}, + {Name: "azure-personal", Host: "https://adb-1234567890123456.78.azuredatabricks.net"}, + {Name: "gcp-sandbox", Host: "https://1234567890123456.7.gcp.databricks.com"}, + {Name: "Create a new profile"}, + {Name: "Enter a host URL manually"}, + } +} diff --git a/cmd/selftest/tui/prompt.go b/cmd/selftest/tui/prompt.go new file mode 100644 index 00000000000..9c5be5319a3 --- /dev/null +++ b/cmd/selftest/tui/prompt.go @@ -0,0 +1,67 @@ +package tui + +import ( + "errors" + "fmt" + "strings" + + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func newPromptCmd() *cobra.Command { + var ( + mask bool + validate bool + ) + cmd := &cobra.Command{ + Use: "prompt", + Short: "cmdio.RunPrompt (single-line text input)", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + opts := cmdio.PromptOptions{ + Label: "Enter a value", + } + if mask { + opts.Mask = '*' + } + if validate { + opts.Validate = func(input string) error { + if !strings.Contains(input, "://") { + return errors.New("value must contain '://'") + } + return nil + } + } + value, err := cmdio.RunPrompt(ctx, opts) + if err != nil { + return err + } + if mask { + cmdio.LogString(ctx, fmt.Sprintf("Entered %d characters", len(value))) + return nil + } + cmdio.LogString(ctx, "Entered: "+value) + return nil + }, + } + cmd.Flags().BoolVar(&mask, "mask", false, "echo input as '*'") + cmd.Flags().BoolVar(&validate, "validate", false, "require '://' in input") + return cmd +} + +func newSecretCmd() *cobra.Command { + return &cobra.Command{ + Use: "secret", + Short: "cmdio.Secret (masked password input)", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + value, err := cmdio.Secret(ctx, "Personal access token") + if err != nil { + return err + } + cmdio.LogString(ctx, fmt.Sprintf("Entered %d characters", len(value))) + return nil + }, + } +} diff --git a/cmd/selftest/tui/select.go b/cmd/selftest/tui/select.go new file mode 100644 index 00000000000..d7d862a1be4 --- /dev/null +++ b/cmd/selftest/tui/select.go @@ -0,0 +1,168 @@ +package tui + +import ( + "context" + "fmt" + "strings" + + "github.com/databricks/cli/libs/cmdio" + "github.com/spf13/cobra" +) + +func validatePositive(n int) error { + if n < 1 { + return fmt.Errorf("--n must be at least 1, got %d", n) + } + return nil +} + +func newSelectCmd() *cobra.Command { + var n int + cmd := &cobra.Command{ + Use: "select", + Short: "cmdio.Select (map; sorted alphabetically by name)", + PreRunE: func(cmd *cobra.Command, args []string) error { + return validatePositive(n) + }, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + tuples := buildItems(n) + items := make(map[string]string, len(tuples)) + for _, t := range tuples { + items[t.Name] = t.Id + } + id, err := cmdio.Select(ctx, items, "Pick an item") + if err != nil { + return err + } + cmdio.LogString(ctx, "Selected: "+id) + return nil + }, + } + cmd.Flags().IntVar(&n, "n", 5, "number of items") + return cmd +} + +func newSelectOrderedCmd() *cobra.Command { + var ( + n int + long bool + filter bool + ) + cmd := &cobra.Command{ + Use: "select-ordered", + Short: "cmdio.SelectOrdered ([]Tuple; preserves insertion order)", + PreRunE: func(cmd *cobra.Command, args []string) error { + if long || filter { + return nil + } + return validatePositive(n) + }, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + var items []cmdio.Tuple + switch { + case filter: + items = buildFilterItems() + case long: + items = buildLongItems() + default: + items = buildItems(n) + } + id, err := cmdio.SelectOrdered(ctx, items, "Pick an item") + if err != nil { + return err + } + cmdio.LogString(ctx, "Selected: "+id) + return nil + }, + } + cmd.Flags().IntVar(&n, "n", 5, "number of items (ignored with --long or --filter)") + cmd.Flags().BoolVar(&long, "long", false, "use 8 items with 60+ char ids that overflow the terminal") + cmd.Flags().BoolVar(&filter, "filter", false, "use 15 items with overlapping substrings (try typing 'al' or 'xyz')") + cmd.MarkFlagsMutuallyExclusive("long", "filter") + return cmd +} + +func newRunSelectCmd() *cobra.Command { + var ( + rich bool + conditional bool + ) + cmd := &cobra.Command{ + Use: "run-select", + Short: "cmdio.RunSelect (custom SelectOptions)", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + switch { + case rich: + return runSelectRich(ctx) + case conditional: + return runSelectProfile(ctx) + default: + return runSelectPlain(ctx) + } + }, + } + cmd.Flags().BoolVar(&rich, "rich", false, "use cluster-style rich Active/Inactive templates (bold + faint)") + cmd.Flags().BoolVar(&conditional, "conditional", false, "use profile-style {{if .Host}} template branches and trailing meta-rows") + cmd.MarkFlagsMutuallyExclusive("rich", "conditional") + return cmd +} + +func runSelectPlain(ctx context.Context) error { + items := buildItems(5) + i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{ + Label: "Pick an item", + Items: items, + }) + if err != nil { + return err + } + cmdio.LogString(ctx, "Selected: "+items[i].Id) + return nil +} + +func runSelectRich(ctx context.Context) error { + items := buildClusterItems(ctx) + i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{ + Label: "Choose a cluster", + Items: items, + Searcher: func(input string, idx int) bool { + return strings.Contains(strings.ToLower(items[idx].Name), strings.ToLower(input)) + }, + StartInSearchMode: true, + LabelTemplate: `{{ . | faint }}`, + Active: `{{.Name | bold}} ({{.State}} {{.Access}} Runtime {{.Runtime}}) ({{.Id | faint}})`, + Inactive: `{{.Name}} ({{.State}} {{.Access}} Runtime {{.Runtime}})`, + Selected: `{{ "Selected cluster" | faint }}: {{ .Name | bold }} ({{ .Id | faint }})`, + }) + if err != nil { + return err + } + cmdio.LogString(ctx, "Selected: "+items[i].Name+" ("+items[i].Id+")") + return nil +} + +func runSelectProfile(ctx context.Context) error { + items := buildProfileItems() + i, err := cmdio.RunSelect(ctx, cmdio.SelectOptions{ + Label: "Select a profile", + Items: items, + StartInSearchMode: true, + Searcher: func(input string, idx int) bool { + input = strings.ToLower(input) + return strings.Contains(strings.ToLower(items[idx].Name), input) || + strings.Contains(strings.ToLower(items[idx].Host), input) + }, + LabelTemplate: `{{ . | 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 err + } + cmdio.LogString(ctx, "Selected: "+items[i].Name) + return nil +} diff --git a/cmd/selftest/tui/spinner.go b/cmd/selftest/tui/spinner.go index a53591f3159..799ce0585d1 100644 --- a/cmd/selftest/tui/spinner.go +++ b/cmd/selftest/tui/spinner.go @@ -7,35 +7,31 @@ import ( "github.com/spf13/cobra" ) -func newSpinner() *cobra.Command { - return &cobra.Command{ +func newSpinnerCmd() *cobra.Command { + var elapsed bool + cmd := &cobra.Command{ Use: "spinner", - Short: "Test the cmdio spinner component", - Run: func(cmd *cobra.Command, args []string) { + Short: "cmdio.NewSpinner (progress indicator)", + RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - sp := cmdio.NewSpinner(ctx) - - // Test various status messages - messages := []struct { - text string - duration time.Duration - }{ - {"Initializing...", time.Second}, - {"Loading configuration", time.Second}, - {"Connecting to workspace", time.Second}, - {"Processing files", time.Second}, - {"Finalizing", time.Second}, + var opts []cmdio.SpinnerOption + if elapsed { + opts = append(opts, cmdio.WithElapsedTime()) } + sp := cmdio.NewSpinner(ctx, opts...) - for _, msg := range messages { + for _, msg := range spinnerMessages { sp.Update(msg.text) time.Sleep(msg.duration) } sp.Close() - cmdio.LogString(ctx, "✓ Spinner test complete") + cmdio.LogString(ctx, "Spinner test complete") + return nil }, } + cmd.Flags().BoolVar(&elapsed, "elapsed", false, "show an MM:SS elapsed-time prefix on the spinner") + return cmd } diff --git a/cmd/selftest/tui/tui.go b/cmd/selftest/tui/tui.go index 5a3f9aad1bc..f1b807fc930 100644 --- a/cmd/selftest/tui/tui.go +++ b/cmd/selftest/tui/tui.go @@ -8,6 +8,17 @@ func New() *cobra.Command { Short: "Test terminal UI components (spinners, prompts, etc.)", } - cmd.AddCommand(newSpinner()) + cmd.AddCommand( + newAskCmd(), + newAskYesOrNoCmd(), + newColorsCmd(), + newPromptCmd(), + newRunSelectCmd(), + newSecretCmd(), + newSelectCmd(), + newSelectOrderedCmd(), + newSpinnerCmd(), + ) + return cmd } diff --git a/cmd/sync/completion.go b/cmd/sync/completion.go index 52d49d6cb1c..bc34c1581ac 100644 --- a/cmd/sync/completion.go +++ b/cmd/sync/completion.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/spf13/cobra" ) @@ -44,7 +45,7 @@ func completeRemotePath( wsc *databricks.WorkspaceClient, toComplete string, ) ([]string, cobra.ShellCompDirective) { - me, err := wsc.CurrentUser.Me(ctx) + me, err := wsc.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return nil, cobra.ShellCompDirectiveError } diff --git a/cmd/workspace/access-control/access-control.go b/cmd/workspace/access-control/access-control.go index b5db3d2a313..acb37919c49 100755 --- a/cmd/workspace/access-control/access-control.go +++ b/cmd/workspace/access-control/access-control.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCheckPolicy()) @@ -64,6 +68,8 @@ func newCheckPolicy() *cobra.Command { cmd.Long = `Check access policy to a resource.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/agent-bricks/agent-bricks.go b/cmd/workspace/agent-bricks/agent-bricks.go index 0a043ce8203..2a9580646af 100755 --- a/cmd/workspace/agent-bricks/agent-bricks.go +++ b/cmd/workspace/agent-bricks/agent-bricks.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCancelOptimize()) cmd.AddCommand(newCreateCustomLlm()) @@ -65,6 +69,8 @@ func newCancelOptimize() *cobra.Command { cmd.Long = `Cancel a Custom LLM Optimization Run.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -127,6 +133,8 @@ func newCreateCustomLlm() *cobra.Command { INSTRUCTIONS: Instructions for the custom LLM to follow` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -206,6 +214,8 @@ func newDeleteCustomLlm() *cobra.Command { ID: The id of the custom llm` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -260,6 +270,8 @@ func newGetCustomLlm() *cobra.Command { ID: The id of the custom llm` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -315,6 +327,8 @@ func newStartOptimize() *cobra.Command { ID: The Id of the tile.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -373,6 +387,8 @@ func newUpdateCustomLlm() *cobra.Command { ID: The id of the custom llm` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/aibi-dashboard-embedding-access-policy/aibi-dashboard-embedding-access-policy.go b/cmd/workspace/aibi-dashboard-embedding-access-policy/aibi-dashboard-embedding-access-policy.go index 4c063bb9c73..1d4e6741fc5 100755 --- a/cmd/workspace/aibi-dashboard-embedding-access-policy/aibi-dashboard-embedding-access-policy.go +++ b/cmd/workspace/aibi-dashboard-embedding-access-policy/aibi-dashboard-embedding-access-policy.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "aibi-dashboard-embedding-access-policy", - Short: `Controls whether AI/BI published dashboard embedding is enabled, conditionally enabled, or disabled at the workspace level.`, - Long: `Controls whether AI/BI published dashboard embedding is enabled, conditionally + Short: `*Public Preview* Controls whether AI/BI published dashboard embedding is enabled, conditionally enabled, or disabled at the workspace level.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether AI/BI published dashboard embedding is enabled, conditionally enabled, or disabled at the workspace level. By default, this setting is conditionally enabled (ALLOW_APPROVED_DOMAINS).`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -57,13 +63,17 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete the AI/BI dashboard embedding access policy.` - cmd.Long = `Delete the AI/BI dashboard embedding access policy. + cmd.Short = `*Public Preview* Delete the AI/BI dashboard embedding access policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete the AI/BI dashboard embedding access policy. Delete the AI/BI dashboard embedding access policy, reverting back to the default.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -112,14 +122,18 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Retrieve the AI/BI dashboard embedding access policy.` - cmd.Long = `Retrieve the AI/BI dashboard embedding access policy. + cmd.Short = `*Public Preview* Retrieve the AI/BI dashboard embedding access policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Retrieve the AI/BI dashboard embedding access policy. Retrieves the AI/BI dashboard embedding access policy. The default setting is ALLOW_APPROVED_DOMAINS, permitting AI/BI dashboards to be embedded on approved domains.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -169,12 +183,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the AI/BI dashboard embedding access policy.` - cmd.Long = `Update the AI/BI dashboard embedding access policy. + cmd.Short = `*Public Preview* Update the AI/BI dashboard embedding access policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the AI/BI dashboard embedding access policy. Updates the AI/BI dashboard embedding access policy at the workspace level.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/aibi-dashboard-embedding-approved-domains/aibi-dashboard-embedding-approved-domains.go b/cmd/workspace/aibi-dashboard-embedding-approved-domains/aibi-dashboard-embedding-approved-domains.go index 148905979f7..8cbd6dff101 100755 --- a/cmd/workspace/aibi-dashboard-embedding-approved-domains/aibi-dashboard-embedding-approved-domains.go +++ b/cmd/workspace/aibi-dashboard-embedding-approved-domains/aibi-dashboard-embedding-approved-domains.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "aibi-dashboard-embedding-approved-domains", - Short: `Controls the list of domains approved to host the embedded AI/BI dashboards.`, - Long: `Controls the list of domains approved to host the embedded AI/BI dashboards. + Short: `*Public Preview* Controls the list of domains approved to host the embedded AI/BI dashboards.`, + Long: `This command is in Public Preview and may change without notice. + +Controls the list of domains approved to host the embedded AI/BI dashboards. The approved domains list can't be mutated when the current access policy is not set to ALLOW_APPROVED_DOMAINS.`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -57,13 +63,17 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete AI/BI dashboard embedding approved domains.` - cmd.Long = `Delete AI/BI dashboard embedding approved domains. + cmd.Short = `*Public Preview* Delete AI/BI dashboard embedding approved domains.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete AI/BI dashboard embedding approved domains. Delete the list of domains approved to host embedded AI/BI dashboards, reverting back to the default empty list.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -112,12 +122,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Retrieve the list of domains approved to host embedded AI/BI dashboards.` - cmd.Long = `Retrieve the list of domains approved to host embedded AI/BI dashboards. + cmd.Short = `*Public Preview* Retrieve the list of domains approved to host embedded AI/BI dashboards.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Retrieve the list of domains approved to host embedded AI/BI dashboards. Retrieves the list of domains approved to host embedded AI/BI dashboards.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -167,14 +181,18 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the list of domains approved to host embedded AI/BI dashboards.` - cmd.Long = `Update the list of domains approved to host embedded AI/BI dashboards. + cmd.Short = `*Public Preview* Update the list of domains approved to host embedded AI/BI dashboards.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the list of domains approved to host embedded AI/BI dashboards. Updates the list of domains approved to host embedded AI/BI dashboards. This update will fail if the current workspace access policy is not ALLOW_APPROVED_DOMAINS.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/alerts-legacy/alerts-legacy.go b/cmd/workspace/alerts-legacy/alerts-legacy.go index 1f211a621d0..1863f7db6de 100755 --- a/cmd/workspace/alerts-legacy/alerts-legacy.go +++ b/cmd/workspace/alerts-legacy/alerts-legacy.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "alerts-legacy", - Short: `The alerts API can be used to perform CRUD operations on alerts.`, - Long: `The alerts API can be used to perform CRUD operations on alerts. An alert is a + Short: `*Public Preview* The alerts API can be used to perform CRUD operations on alerts.`, + Long: `This command is in Public Preview and may change without notice. + +The alerts API can be used to perform CRUD operations on alerts. An alert is a Databricks SQL object that periodically runs a query, evaluates a condition of its result, and notifies one or more users and/or notification destinations if the condition was met. Alerts can be scheduled using the sql_task type of @@ -35,6 +37,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -71,8 +77,10 @@ func newCreate() *cobra.Command { cmd.Flags().IntVar(&createReq.Rearm, "rearm", createReq.Rearm, `Number of seconds after being triggered before the alert rearms itself and can be triggered again.`) cmd.Use = "create" - cmd.Short = `Create an alert.` - cmd.Long = `Create an alert. + cmd.Short = `*Public Preview* Create an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an alert. Creates an alert. An alert is a Databricks SQL object that periodically runs a query, evaluates a condition of its result, and notifies users or notification @@ -84,6 +92,8 @@ func newCreate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -140,8 +150,10 @@ func newDelete() *cobra.Command { var deleteReq sql.DeleteAlertsLegacyRequest cmd.Use = "delete ALERT_ID" - cmd.Short = `Delete an alert.` - cmd.Long = `Delete an alert. + cmd.Short = `*Public Preview* Delete an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an alert. Deletes an alert. Deleted alerts are no longer accessible and cannot be restored. **Note**: Unlike queries and dashboards, alerts cannot be moved to @@ -153,6 +165,8 @@ func newDelete() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -200,8 +214,10 @@ func newGet() *cobra.Command { var getReq sql.GetAlertsLegacyRequest cmd.Use = "get ALERT_ID" - cmd.Short = `Get an alert.` - cmd.Long = `Get an alert. + cmd.Short = `*Public Preview* Get an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an alert. Gets an alert. @@ -211,6 +227,8 @@ func newGet() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -256,8 +274,10 @@ func newList() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "list" - cmd.Short = `Get alerts.` - cmd.Long = `Get alerts. + cmd.Short = `*Public Preview* Get alerts.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get alerts. Gets a list of alerts. @@ -267,6 +287,8 @@ func newList() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -312,8 +334,10 @@ func newUpdate() *cobra.Command { cmd.Flags().IntVar(&updateReq.Rearm, "rearm", updateReq.Rearm, `Number of seconds after being triggered before the alert rearms itself and can be triggered again.`) cmd.Use = "update ALERT_ID" - cmd.Short = `Update an alert.` - cmd.Long = `Update an alert. + cmd.Short = `*Public Preview* Update an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an alert. Updates an alert. @@ -323,6 +347,8 @@ func newUpdate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/alerts-v2/alerts-v2.go b/cmd/workspace/alerts-v2/alerts-v2.go index 11ec795d8ba..2e929120a4b 100755 --- a/cmd/workspace/alerts-v2/alerts-v2.go +++ b/cmd/workspace/alerts-v2/alerts-v2.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "alerts-v2", - Short: `New version of SQL Alerts.`, - Long: `New version of SQL Alerts`, + Use: "alerts-v2", + Short: `*Public Preview* New version of SQL Alerts.`, + Long: `This command is in Public Preview and may change without notice. + +New version of SQL Alerts`, GroupID: "sql", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreateAlert()) cmd.AddCommand(newGetAlert()) @@ -67,8 +73,10 @@ func newCreateAlert() *cobra.Command { cmd.Flags().StringVar(&createAlertReq.Alert.RunAsUserName, "run-as-user-name", createAlertReq.Alert.RunAsUserName, `The run as username or application ID of service principal.`) cmd.Use = "create-alert DISPLAY_NAME QUERY_TEXT WAREHOUSE_ID EVALUATION SCHEDULE" - cmd.Short = `Create an alert.` - cmd.Long = `Create an alert. + cmd.Short = `*Public Preview* Create an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an alert. Create Alert @@ -80,6 +88,8 @@ func newCreateAlert() *cobra.Command { SCHEDULE: ` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -169,12 +179,16 @@ func newGetAlert() *cobra.Command { var getAlertReq sql.GetAlertV2Request cmd.Use = "get-alert ID" - cmd.Short = `Get an alert.` - cmd.Long = `Get an alert. + cmd.Short = `*Public Preview* Get an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an alert. Gets an alert.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -248,12 +262,16 @@ func newListAlerts() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-alerts" - cmd.Short = `List alerts.` - cmd.Long = `List alerts. + cmd.Short = `*Public Preview* List alerts.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List alerts. Gets a list of alerts accessible to the user, ordered by creation time.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -305,14 +323,18 @@ func newTrashAlert() *cobra.Command { cmd.Flags().BoolVar(&trashAlertReq.Purge, "purge", trashAlertReq.Purge, `Whether to permanently delete the alert.`) cmd.Use = "trash-alert ID" - cmd.Short = `Delete an alert (legacy TrashAlert).` - cmd.Long = `Delete an alert (legacy TrashAlert). + cmd.Short = `*Public Preview* Delete an alert (legacy TrashAlert).` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an alert (legacy TrashAlert). Moves an alert to the trash. Trashed alerts immediately disappear from list views, and can no longer trigger. You can restore a trashed alert through the UI. A trashed alert is permanently deleted after 30 days.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -383,8 +405,10 @@ func newUpdateAlert() *cobra.Command { cmd.Flags().StringVar(&updateAlertReq.Alert.RunAsUserName, "run-as-user-name", updateAlertReq.Alert.RunAsUserName, `The run as username or application ID of service principal.`) cmd.Use = "update-alert ID UPDATE_MASK DISPLAY_NAME QUERY_TEXT WAREHOUSE_ID EVALUATION SCHEDULE" - cmd.Short = `Update an alert.` - cmd.Long = `Update an alert. + cmd.Short = `*Public Preview* Update an alert.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an alert. Update alert @@ -408,6 +432,8 @@ func newUpdateAlert() *cobra.Command { SCHEDULE: ` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/alerts/alerts.go b/cmd/workspace/alerts/alerts.go index 10ef4937fb5..dd9aa1c8877 100755 --- a/cmd/workspace/alerts/alerts.go +++ b/cmd/workspace/alerts/alerts.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,6 +76,8 @@ func newCreate() *cobra.Command { Creates an alert.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -139,6 +145,8 @@ func newDelete() *cobra.Command { alert through the UI. A trashed alert is permanently deleted after 30 days.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -204,6 +212,8 @@ func newGet() *cobra.Command { Gets an alert.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -285,6 +295,8 @@ func newList() *cobra.Command { throttling, service degradation, or a temporary ban.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -360,6 +372,8 @@ func newUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/apps-settings/apps-settings.go b/cmd/workspace/apps-settings/apps-settings.go index 7966bf91108..15b80e01bd6 100755 --- a/cmd/workspace/apps-settings/apps-settings.go +++ b/cmd/workspace/apps-settings/apps-settings.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreateCustomTemplate()) cmd.AddCommand(newDeleteCustomTemplate()) @@ -82,6 +86,8 @@ func newCreateCustomTemplate() *cobra.Command { GIT_PROVIDER: The Git provider of the template.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -176,6 +182,8 @@ func newDeleteCustomTemplate() *cobra.Command { NAME: The name of the custom template.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -233,6 +241,8 @@ func newGetCustomTemplate() *cobra.Command { NAME: The name of the custom template.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -300,6 +310,8 @@ func newListCustomTemplates() *cobra.Command { Lists all custom templates in the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -372,6 +384,8 @@ func newUpdateCustomTemplate() *cobra.Command { GIT_PROVIDER: The Git provider of the template.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/apps/apps.go b/cmd/workspace/apps/apps.go index b2b9c7eb222..15e6d75bc67 100755 --- a/cmd/workspace/apps/apps.go +++ b/cmd/workspace/apps/apps.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newCreateSpace()) @@ -117,6 +121,8 @@ func newCreate() *cobra.Command { characters and hyphens. It must be unique within the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -240,6 +246,8 @@ func newCreateSpace() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -372,6 +380,8 @@ func newCreateUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -469,6 +479,8 @@ func newDelete() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -526,6 +538,8 @@ func newDeleteAppThumbnail() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -596,6 +610,8 @@ func newDeleteSpace() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -703,6 +719,8 @@ func newDeploy() *cobra.Command { APP_NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -790,6 +808,8 @@ func newGet() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -849,6 +869,8 @@ func newGetDeployment() *cobra.Command { DEPLOYMENT_ID: The unique id of the deployment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -907,6 +929,8 @@ func newGetPermissionLevels() *cobra.Command { APP_NAME: The app for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -965,6 +989,8 @@ func newGetPermissions() *cobra.Command { APP_NAME: The app for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1025,6 +1051,8 @@ func newGetSpace() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1085,6 +1113,8 @@ func newGetSpaceOperation() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1142,6 +1172,8 @@ func newGetUpdate() *cobra.Command { APP_NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1210,6 +1242,8 @@ func newList() *cobra.Command { Lists all apps in the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1281,6 +1315,8 @@ func newListDeployments() *cobra.Command { APP_NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1354,6 +1390,8 @@ func newListSpaces() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1419,6 +1457,8 @@ func newSetPermissions() *cobra.Command { APP_NAME: The app for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1494,6 +1534,8 @@ func newStart() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1575,6 +1617,8 @@ func newStop() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1670,6 +1714,8 @@ func newUpdate() *cobra.Command { characters and hyphens. It must be unique within the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1744,6 +1790,8 @@ func newUpdateAppThumbnail() *cobra.Command { NAME: The name of the app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1819,6 +1867,8 @@ func newUpdatePermissions() *cobra.Command { APP_NAME: The app for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1927,6 +1977,8 @@ func newUpdateSpace() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/artifact-allowlists/artifact-allowlists.go b/cmd/workspace/artifact-allowlists/artifact-allowlists.go index 540e754e996..226c203e369 100755 --- a/cmd/workspace/artifact-allowlists/artifact-allowlists.go +++ b/cmd/workspace/artifact-allowlists/artifact-allowlists.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -66,6 +70,8 @@ func newGet() *cobra.Command { Supported values: [INIT_SCRIPT, LIBRARY_JAR, LIBRARY_MAVEN]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -132,6 +138,8 @@ func newUpdate() *cobra.Command { Supported values: [INIT_SCRIPT, LIBRARY_JAR, LIBRARY_MAVEN]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/automatic-cluster-update/automatic-cluster-update.go b/cmd/workspace/automatic-cluster-update/automatic-cluster-update.go index e8978c755da..feffc1595f7 100755 --- a/cmd/workspace/automatic-cluster-update/automatic-cluster-update.go +++ b/cmd/workspace/automatic-cluster-update/automatic-cluster-update.go @@ -20,12 +20,18 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "automatic-cluster-update", - Short: `Controls whether automatic cluster update is enabled for the current workspace.`, - Long: `Controls whether automatic cluster update is enabled for the current + Short: `*Public Preview* Controls whether automatic cluster update is enabled for the current workspace.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether automatic cluster update is enabled for the current workspace. By default, it is turned off.`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -55,12 +61,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the automatic cluster update setting.` - cmd.Long = `Get the automatic cluster update setting. + cmd.Short = `*Public Preview* Get the automatic cluster update setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the automatic cluster update setting. Gets the automatic cluster update setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -110,8 +120,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the automatic cluster update setting.` - cmd.Long = `Update the automatic cluster update setting. + cmd.Short = `*Public Preview* Update the automatic cluster update setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the automatic cluster update setting. Updates the automatic cluster update setting for the workspace. A fresh etag needs to be provided in PATCH requests (as part of the setting field). The @@ -120,6 +132,8 @@ func newUpdate() *cobra.Command { must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/bundle/bundle.go b/cmd/workspace/bundle/bundle.go new file mode 100755 index 00000000000..ee6e38addea --- /dev/null +++ b/cmd/workspace/bundle/bundle.go @@ -0,0 +1,1108 @@ +// Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +package bundle + +import ( + "fmt" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/service/bundle" + "github.com/spf13/cobra" +) + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var cmdOverrides []func(*cobra.Command) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "bundle", + Short: `Service for managing bundle deployment metadata.`, + Long: `Service for managing bundle deployment metadata.`, + GroupID: "bundle", + + // This service is being previewed; hide from help output. + Hidden: true, + RunE: root.ReportUnknownSubcommand, + } + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + // Add methods + cmd.AddCommand(newCompleteVersion()) + cmd.AddCommand(newCreateDeployment()) + cmd.AddCommand(newCreateOperation()) + cmd.AddCommand(newCreateVersion()) + cmd.AddCommand(newDeleteDeployment()) + cmd.AddCommand(newGetDeployment()) + cmd.AddCommand(newGetOperation()) + cmd.AddCommand(newGetResource()) + cmd.AddCommand(newGetVersion()) + cmd.AddCommand(newHeartbeat()) + cmd.AddCommand(newListDeployments()) + cmd.AddCommand(newListOperations()) + cmd.AddCommand(newListResources()) + cmd.AddCommand(newListVersions()) + + // Apply optional overrides to this command. + for _, fn := range cmdOverrides { + fn(cmd) + } + + return cmd +} + +// start complete-version command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var completeVersionOverrides []func( + *cobra.Command, + *bundle.CompleteVersionRequest, +) + +func newCompleteVersion() *cobra.Command { + cmd := &cobra.Command{} + + var completeVersionReq bundle.CompleteVersionRequest + var completeVersionJson flags.JsonFlag + + cmd.Flags().Var(&completeVersionJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().BoolVar(&completeVersionReq.Force, "force", completeVersionReq.Force, `If true, force-completes the version even if the caller is not the original creator.`) + + cmd.Use = "complete-version NAME COMPLETION_REASON" + cmd.Short = `Complete a version.` + cmd.Long = `Complete a version. + + Marks a version as complete and releases the deployment lock. + + The server atomically: 1. Sets the version status to the provided terminal + status. 2. Sets complete_time to the current server timestamp. 3. Releases + the lock on the parent deployment. 4. Updates the parent deployment's status + and last_version_id. + + Arguments: + NAME: The name of the version to complete. Format: + deployments/{deployment_id}/versions/{version_id} + COMPLETION_REASON: The reason for completing the version. Must be a terminal reason: + VERSION_COMPLETE_SUCCESS, VERSION_COMPLETE_FAILURE, or + VERSION_COMPLETE_FORCE_ABORT. + Supported values: [VERSION_COMPLETE_FAILURE, VERSION_COMPLETE_FORCE_ABORT, VERSION_COMPLETE_LEASE_EXPIRED, VERSION_COMPLETE_SUCCESS]` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(1)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME as positional arguments. Provide 'completion_reason' in your JSON input") + } + return nil + } + check := root.ExactArgs(2) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := completeVersionJson.Unmarshal(&completeVersionReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + completeVersionReq.Name = args[0] + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[1], &completeVersionReq.CompletionReason) + if err != nil { + return fmt.Errorf("invalid COMPLETION_REASON: %s", args[1]) + } + + } + + response, err := w.Bundle.CompleteVersion(ctx, completeVersionReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range completeVersionOverrides { + fn(cmd, &completeVersionReq) + } + + return cmd +} + +// start create-deployment command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createDeploymentOverrides []func( + *cobra.Command, + *bundle.CreateDeploymentRequest, +) + +func newCreateDeployment() *cobra.Command { + cmd := &cobra.Command{} + + var createDeploymentReq bundle.CreateDeploymentRequest + createDeploymentReq.Deployment = bundle.Deployment{} + var createDeploymentJson flags.JsonFlag + + cmd.Flags().Var(&createDeploymentJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&createDeploymentReq.Deployment.DisplayName, "display-name", createDeploymentReq.Deployment.DisplayName, `Human-readable name for the deployment.`) + cmd.Flags().StringVar(&createDeploymentReq.Deployment.TargetName, "target-name", createDeploymentReq.Deployment.TargetName, `The bundle target name associated with this deployment.`) + + cmd.Use = "create-deployment DEPLOYMENT_ID" + cmd.Short = `Create a deployment.` + cmd.Long = `Create a deployment. + + Creates a new deployment in the workspace. + + The caller must provide a deployment_id which becomes the final component of + the deployment's resource name. If a deployment with the same ID already + exists, the server returns ALREADY_EXISTS. + + Arguments: + DEPLOYMENT_ID: The ID to use for the deployment, which will become the final component of + the deployment's resource name (i.e. deployments/{deployment_id}).` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createDeploymentJson.Unmarshal(&createDeploymentReq.Deployment) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createDeploymentReq.DeploymentId = args[0] + + response, err := w.Bundle.CreateDeployment(ctx, createDeploymentReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createDeploymentOverrides { + fn(cmd, &createDeploymentReq) + } + + return cmd +} + +// start create-operation command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createOperationOverrides []func( + *cobra.Command, + *bundle.CreateOperationRequest, +) + +func newCreateOperation() *cobra.Command { + cmd := &cobra.Command{} + + var createOperationReq bundle.CreateOperationRequest + createOperationReq.Operation = bundle.Operation{} + var createOperationJson flags.JsonFlag + + cmd.Flags().Var(&createOperationJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Use = "create-operation PARENT" + cmd.Short = `Create an operation.` + cmd.Long = `Create an operation. + + Creates a resource operation under a version. + + The caller must provide a resource_key which becomes the final component of + the operation's name. If an operation with the same key already exists under + the version, the server returns ALREADY_EXISTS. + + On success the server also updates the corresponding deployment-level Resource + (creating it if this is the first operation for that resource_key, or removing + it if action_type is DELETE). + + Arguments: + PARENT: The parent version where this operation will be recorded. Format: + deployments/{deployment_id}/versions/{version_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT, RESOURCE_KEY as positional arguments. Provide 'action_type', 'resource_id', 'status' in your JSON input") + } + return nil + } + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createOperationJson.Unmarshal(&createOperationReq.Operation) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } else { + return fmt.Errorf("please provide command input in JSON format by specifying the --json flag") + } + createOperationReq.Parent = args[0] + + response, err := w.Bundle.CreateOperation(ctx, createOperationReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createOperationOverrides { + fn(cmd, &createOperationReq) + } + + return cmd +} + +// start create-version command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createVersionOverrides []func( + *cobra.Command, + *bundle.CreateVersionRequest, +) + +func newCreateVersion() *cobra.Command { + cmd := &cobra.Command{} + + var createVersionReq bundle.CreateVersionRequest + createVersionReq.Version = bundle.Version{} + var createVersionJson flags.JsonFlag + + cmd.Flags().Var(&createVersionJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&createVersionReq.Version.DisplayName, "display-name", createVersionReq.Version.DisplayName, `Display name for the deployment, captured at the time of this version.`) + cmd.Flags().StringVar(&createVersionReq.Version.TargetName, "target-name", createVersionReq.Version.TargetName, `Target name of the deployment, captured at the time of this version.`) + + cmd.Use = "create-version PARENT VERSION_ID CLI_VERSION VERSION_TYPE" + cmd.Short = `Create a version.` + cmd.Long = `Create a version. + + Creates a new version under a deployment. + + Creating a version acquires an exclusive lock on the deployment, preventing + concurrent deploys. The caller provides a version_id which the server + validates equals last_version_id + 1 on the deployment. + + Arguments: + PARENT: The parent deployment where this version will be created. Format: + deployments/{deployment_id} + VERSION_ID: The version ID the caller expects to create. The server validates this + equals last_version_id + 1 on the deployment. If it doesn't match, the + server returns ABORTED. + CLI_VERSION: CLI version used to initiate the version. + VERSION_TYPE: Type of version (deploy or destroy). + Supported values: [VERSION_TYPE_DEPLOY, VERSION_TYPE_DESTROY]` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT, VERSION_ID as positional arguments. Provide 'cli_version', 'version_type' in your JSON input") + } + return nil + } + check := root.ExactArgs(4) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createVersionJson.Unmarshal(&createVersionReq.Version) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createVersionReq.Parent = args[0] + createVersionReq.VersionId = args[1] + if !cmd.Flags().Changed("json") { + createVersionReq.Version.CliVersion = args[2] + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[3], &createVersionReq.Version.VersionType) + if err != nil { + return fmt.Errorf("invalid VERSION_TYPE: %s", args[3]) + } + + } + + response, err := w.Bundle.CreateVersion(ctx, createVersionReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createVersionOverrides { + fn(cmd, &createVersionReq) + } + + return cmd +} + +// start delete-deployment command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteDeploymentOverrides []func( + *cobra.Command, + *bundle.DeleteDeploymentRequest, +) + +func newDeleteDeployment() *cobra.Command { + cmd := &cobra.Command{} + + var deleteDeploymentReq bundle.DeleteDeploymentRequest + + cmd.Use = "delete-deployment NAME" + cmd.Short = `Delete a deployment.` + cmd.Long = `Delete a deployment. + + Deletes a deployment. + + The deployment is marked as deleted. It and all its children (versions and + their operations) will be permanently deleted after the retention policy + expires. If the deployment has an in-progress version, the server returns + RESOURCE_CONFLICT. + + Arguments: + NAME: Resource name of the deployment to delete. Format: + deployments/{deployment_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteDeploymentReq.Name = args[0] + + err = w.Bundle.DeleteDeployment(ctx, deleteDeploymentReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteDeploymentOverrides { + fn(cmd, &deleteDeploymentReq) + } + + return cmd +} + +// start get-deployment command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getDeploymentOverrides []func( + *cobra.Command, + *bundle.GetDeploymentRequest, +) + +func newGetDeployment() *cobra.Command { + cmd := &cobra.Command{} + + var getDeploymentReq bundle.GetDeploymentRequest + + cmd.Use = "get-deployment NAME" + cmd.Short = `Get a deployment.` + cmd.Long = `Get a deployment. + + Retrieves a deployment by its resource name. + + Arguments: + NAME: Resource name of the deployment to retrieve. Format: + deployments/{deployment_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getDeploymentReq.Name = args[0] + + response, err := w.Bundle.GetDeployment(ctx, getDeploymentReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getDeploymentOverrides { + fn(cmd, &getDeploymentReq) + } + + return cmd +} + +// start get-operation command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getOperationOverrides []func( + *cobra.Command, + *bundle.GetOperationRequest, +) + +func newGetOperation() *cobra.Command { + cmd := &cobra.Command{} + + var getOperationReq bundle.GetOperationRequest + + cmd.Use = "get-operation NAME" + cmd.Short = `Get an operation.` + cmd.Long = `Get an operation. + + Retrieves a resource operation by its resource name. + + Arguments: + NAME: The name of the resource operation to retrieve. Format: + deployments/{deployment_id}/versions/{version_id}/operations/{resource_key}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getOperationReq.Name = args[0] + + response, err := w.Bundle.GetOperation(ctx, getOperationReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getOperationOverrides { + fn(cmd, &getOperationReq) + } + + return cmd +} + +// start get-resource command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getResourceOverrides []func( + *cobra.Command, + *bundle.GetResourceRequest, +) + +func newGetResource() *cobra.Command { + cmd := &cobra.Command{} + + var getResourceReq bundle.GetResourceRequest + + cmd.Use = "get-resource NAME" + cmd.Short = `Get a resource.` + cmd.Long = `Get a resource. + + Retrieves a deployment resource by its resource name. + + Arguments: + NAME: The name of the resource to retrieve. Format: + deployments/{deployment_id}/resources/{resource_key}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getResourceReq.Name = args[0] + + response, err := w.Bundle.GetResource(ctx, getResourceReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getResourceOverrides { + fn(cmd, &getResourceReq) + } + + return cmd +} + +// start get-version command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getVersionOverrides []func( + *cobra.Command, + *bundle.GetVersionRequest, +) + +func newGetVersion() *cobra.Command { + cmd := &cobra.Command{} + + var getVersionReq bundle.GetVersionRequest + + cmd.Use = "get-version NAME" + cmd.Short = `Get a version.` + cmd.Long = `Get a version. + + Retrieves a version by its resource name. + + Arguments: + NAME: The name of the version to retrieve. Format: + deployments/{deployment_id}/versions/{version_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getVersionReq.Name = args[0] + + response, err := w.Bundle.GetVersion(ctx, getVersionReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getVersionOverrides { + fn(cmd, &getVersionReq) + } + + return cmd +} + +// start heartbeat command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var heartbeatOverrides []func( + *cobra.Command, + *bundle.HeartbeatRequest, +) + +func newHeartbeat() *cobra.Command { + cmd := &cobra.Command{} + + var heartbeatReq bundle.HeartbeatRequest + + cmd.Use = "heartbeat NAME" + cmd.Short = `Send a version heartbeat.` + cmd.Long = `Send a version heartbeat. + + Sends a heartbeat to renew the lock held by a version. + + The server validates that the version is the active (non-terminal) version on + the parent deployment and resets the lock expiry. If the lock has already + expired or the version is no longer active, the server returns ABORTED. + + Arguments: + NAME: The version whose lock to renew. Format: + deployments/{deployment_id}/versions/{version_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + heartbeatReq.Name = args[0] + + response, err := w.Bundle.Heartbeat(ctx, heartbeatReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range heartbeatOverrides { + fn(cmd, &heartbeatReq) + } + + return cmd +} + +// start list-deployments command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listDeploymentsOverrides []func( + *cobra.Command, + *bundle.ListDeploymentsRequest, +) + +func newListDeployments() *cobra.Command { + cmd := &cobra.Command{} + + var listDeploymentsReq bundle.ListDeploymentsRequest + // 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 listDeploymentsLimit int + + cmd.Flags().IntVar(&listDeploymentsReq.PageSize, "page-size", listDeploymentsReq.PageSize, `The maximum number of deployments to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listDeploymentsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listDeploymentsReq.PageToken, "page-token", listDeploymentsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-deployments" + cmd.Short = `List deployments.` + cmd.Long = `List deployments. + + Lists deployments in the workspace.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + response := w.Bundle.ListDeployments(ctx, listDeploymentsReq) + if listDeploymentsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listDeploymentsLimit) + } + if listDeploymentsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listDeploymentsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listDeploymentsOverrides { + fn(cmd, &listDeploymentsReq) + } + + return cmd +} + +// start list-operations command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listOperationsOverrides []func( + *cobra.Command, + *bundle.ListOperationsRequest, +) + +func newListOperations() *cobra.Command { + cmd := &cobra.Command{} + + var listOperationsReq bundle.ListOperationsRequest + // 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 listOperationsLimit int + + cmd.Flags().IntVar(&listOperationsReq.PageSize, "page-size", listOperationsReq.PageSize, `The maximum number of operations to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listOperationsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listOperationsReq.PageToken, "page-token", listOperationsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-operations PARENT" + cmd.Short = `List operations.` + cmd.Long = `List operations. + + Lists resource operations under a version. + + Arguments: + PARENT: The parent version. Format: + deployments/{deployment_id}/versions/{version_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listOperationsReq.Parent = args[0] + + response := w.Bundle.ListOperations(ctx, listOperationsReq) + if listOperationsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listOperationsLimit) + } + if listOperationsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listOperationsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listOperationsOverrides { + fn(cmd, &listOperationsReq) + } + + return cmd +} + +// start list-resources command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listResourcesOverrides []func( + *cobra.Command, + *bundle.ListResourcesRequest, +) + +func newListResources() *cobra.Command { + cmd := &cobra.Command{} + + var listResourcesReq bundle.ListResourcesRequest + // 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 listResourcesLimit int + + cmd.Flags().IntVar(&listResourcesReq.PageSize, "page-size", listResourcesReq.PageSize, `The maximum number of resources to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listResourcesLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listResourcesReq.PageToken, "page-token", listResourcesReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-resources PARENT" + cmd.Short = `List resources.` + cmd.Long = `List resources. + + Lists resources under a deployment. + + Arguments: + PARENT: The parent deployment. Format: deployments/{deployment_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listResourcesReq.Parent = args[0] + + response := w.Bundle.ListResources(ctx, listResourcesReq) + if listResourcesLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listResourcesLimit) + } + if listResourcesLimit > 0 { + ctx = cmdio.WithLimit(ctx, listResourcesLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listResourcesOverrides { + fn(cmd, &listResourcesReq) + } + + return cmd +} + +// start list-versions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listVersionsOverrides []func( + *cobra.Command, + *bundle.ListVersionsRequest, +) + +func newListVersions() *cobra.Command { + cmd := &cobra.Command{} + + var listVersionsReq bundle.ListVersionsRequest + // 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 listVersionsLimit int + + cmd.Flags().IntVar(&listVersionsReq.PageSize, "page-size", listVersionsReq.PageSize, `The maximum number of versions to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listVersionsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listVersionsReq.PageToken, "page-token", listVersionsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-versions PARENT" + cmd.Short = `List versions.` + cmd.Long = `List versions. + + Lists versions under a deployment, ordered by version_id descending (most + recent first). + + Arguments: + PARENT: The parent deployment. Format: deployments/{deployment_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listVersionsReq.Parent = args[0] + + response := w.Bundle.ListVersions(ctx, listVersionsReq) + if listVersionsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listVersionsLimit) + } + if listVersionsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listVersionsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listVersionsOverrides { + fn(cmd, &listVersionsReq) + } + + return cmd +} + +// end service Bundle diff --git a/cmd/workspace/catalogs/catalogs.go b/cmd/workspace/catalogs/catalogs.go index f8eccfa6114..568501b4ea4 100755 --- a/cmd/workspace/catalogs/catalogs.go +++ b/cmd/workspace/catalogs/catalogs.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -85,6 +89,8 @@ func newCreate() *cobra.Command { NAME: Name of catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -166,6 +172,8 @@ func newDelete() *cobra.Command { NAME: The name of the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -226,6 +234,8 @@ func newGet() *cobra.Command { NAME: The name of the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -308,6 +318,8 @@ func newList() *cobra.Command { indication that the end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -380,6 +392,8 @@ func newUpdate() *cobra.Command { NAME: The name of the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/clean-room-asset-revisions/clean-room-asset-revisions.go b/cmd/workspace/clean-room-asset-revisions/clean-room-asset-revisions.go index 9e773e0617e..e8188f3939c 100755 --- a/cmd/workspace/clean-room-asset-revisions/clean-room-asset-revisions.go +++ b/cmd/workspace/clean-room-asset-revisions/clean-room-asset-revisions.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "clean-room-asset-revisions", - Short: `Clean Room Asset Revisions denote new versions of uploaded assets (e.g.`, - Long: `Clean Room Asset Revisions denote new versions of uploaded assets (e.g. + Short: `*Beta* Clean Room Asset Revisions denote new versions of uploaded assets (e.g.`, + Long: `This command is in Beta and may change without notice. + +Clean Room Asset Revisions denote new versions of uploaded assets (e.g. notebooks) in the clean room.`, GroupID: "cleanrooms", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newList()) @@ -53,8 +59,10 @@ func newGet() *cobra.Command { var getReq cleanrooms.GetCleanRoomAssetRevisionRequest cmd.Use = "get CLEAN_ROOM_NAME ASSET_TYPE NAME ETAG" - cmd.Short = `Get an asset revision.` - cmd.Long = `Get an asset revision. + cmd.Short = `*Beta* Get an asset revision.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an asset revision. Get a specific revision of an asset @@ -67,6 +75,8 @@ func newGet() *cobra.Command { returned.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -135,8 +145,10 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list CLEAN_ROOM_NAME ASSET_TYPE NAME" - cmd.Short = `List asset revisions.` - cmd.Long = `List asset revisions. + cmd.Short = `*Beta* List asset revisions.` + cmd.Long = `This command is in Beta and may change without notice. + +List asset revisions. List revisions for an asset @@ -147,6 +159,8 @@ func newList() *cobra.Command { NAME: Name of the asset.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) diff --git a/cmd/workspace/clean-room-assets/clean-room-assets.go b/cmd/workspace/clean-room-assets/clean-room-assets.go index 4b371d1d22f..dffc53ec176 100755 --- a/cmd/workspace/clean-room-assets/clean-room-assets.go +++ b/cmd/workspace/clean-room-assets/clean-room-assets.go @@ -27,6 +27,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newCreateCleanRoomAssetReview()) @@ -96,6 +100,8 @@ func newCreate() *cobra.Command { Supported values: [FOREIGN_TABLE, NOTEBOOK_FILE, TABLE, VIEW, VOLUME]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -178,8 +184,10 @@ func newCreateCleanRoomAssetReview() *cobra.Command { // TODO: complex arg: notebook_review cmd.Use = "create-clean-room-asset-review CLEAN_ROOM_NAME ASSET_TYPE NAME" - cmd.Short = `Create a review (e.g. approval) for an asset.` - cmd.Long = `Create a review (e.g. approval) for an asset. + cmd.Short = `*Beta* Create a review (e.g. approval) for an asset.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a review (e.g. approval) for an asset. Submit an asset review @@ -190,6 +198,8 @@ func newCreateCleanRoomAssetReview() *cobra.Command { NAME: Name of the asset` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -269,6 +279,8 @@ func newDelete() *cobra.Command { CleanRoomAsset.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -335,6 +347,8 @@ func newGet() *cobra.Command { CleanRoomAsset.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -407,6 +421,8 @@ func newList() *cobra.Command { CLEAN_ROOM_NAME: Name of the clean room.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -492,6 +508,8 @@ func newUpdate() *cobra.Command { name is the jar analysis name.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) diff --git a/cmd/workspace/clean-room-auto-approval-rules/clean-room-auto-approval-rules.go b/cmd/workspace/clean-room-auto-approval-rules/clean-room-auto-approval-rules.go index 7cefc4a57e8..1515694ca64 100755 --- a/cmd/workspace/clean-room-auto-approval-rules/clean-room-auto-approval-rules.go +++ b/cmd/workspace/clean-room-auto-approval-rules/clean-room-auto-approval-rules.go @@ -20,14 +20,20 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "clean-room-auto-approval-rules", - Short: `Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g.`, - Long: `Clean room auto-approval rules automatically create an approval on your behalf + Short: `*Beta* Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g.`, + Long: `This command is in Beta and may change without notice. + +Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g. notebook) meeting specific criteria is shared in a clean room.`, GroupID: "cleanrooms", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -61,8 +67,10 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create CLEAN_ROOM_NAME" - cmd.Short = `Create an auto-approval rule.` - cmd.Long = `Create an auto-approval rule. + cmd.Short = `*Beta* Create an auto-approval rule.` + cmd.Long = `This command is in Beta and may change without notice. + +Create an auto-approval rule. Create an auto-approval rule @@ -70,6 +78,8 @@ func newCreate() *cobra.Command { CLEAN_ROOM_NAME: The name of the clean room this auto-approval rule belongs to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -132,12 +142,16 @@ func newDelete() *cobra.Command { var deleteReq cleanrooms.DeleteCleanRoomAutoApprovalRuleRequest cmd.Use = "delete CLEAN_ROOM_NAME RULE_ID" - cmd.Short = `Delete an auto-approval rule.` - cmd.Long = `Delete an auto-approval rule. + cmd.Short = `*Beta* Delete an auto-approval rule.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete an auto-approval rule. Delete a auto-approval rule by rule ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -186,12 +200,16 @@ func newGet() *cobra.Command { var getReq cleanrooms.GetCleanRoomAutoApprovalRuleRequest cmd.Use = "get CLEAN_ROOM_NAME RULE_ID" - cmd.Short = `Get an auto-approval rule.` - cmd.Long = `Get an auto-approval rule. + cmd.Short = `*Beta* Get an auto-approval rule.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an auto-approval rule. Get a auto-approval rule by rule ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -254,12 +272,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list CLEAN_ROOM_NAME" - cmd.Short = `List auto-approval rules.` - cmd.Long = `List auto-approval rules. + cmd.Short = `*Beta* List auto-approval rules.` + cmd.Long = `This command is in Beta and may change without notice. + +List auto-approval rules. List all auto-approval rules for the caller` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -320,8 +342,10 @@ func newUpdate() *cobra.Command { cmd.Flags().StringVar(&updateReq.AutoApprovalRule.RunnerCollaboratorAlias, "runner-collaborator-alias", updateReq.AutoApprovalRule.RunnerCollaboratorAlias, `Collaborator alias of the runner covered by the rule.`) cmd.Use = "update CLEAN_ROOM_NAME RULE_ID" - cmd.Short = `Update an auto-approval rule.` - cmd.Long = `Update an auto-approval rule. + cmd.Short = `*Beta* Update an auto-approval rule.` + cmd.Long = `This command is in Beta and may change without notice. + +Update an auto-approval rule. Update a auto-approval rule by rule ID @@ -330,6 +354,8 @@ func newUpdate() *cobra.Command { RULE_ID: A generated UUID identifying the rule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/clean-room-task-runs/clean-room-task-runs.go b/cmd/workspace/clean-room-task-runs/clean-room-task-runs.go index 6469e9bc882..044912d41d1 100755 --- a/cmd/workspace/clean-room-task-runs/clean-room-task-runs.go +++ b/cmd/workspace/clean-room-task-runs/clean-room-task-runs.go @@ -25,6 +25,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newList()) @@ -74,6 +78,8 @@ func newList() *cobra.Command { CLEAN_ROOM_NAME: Name of the clean room.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/clean-rooms/clean-rooms.go b/cmd/workspace/clean-rooms/clean-rooms.go index 2a323554425..6ce4f30def1 100755 --- a/cmd/workspace/clean-rooms/clean-rooms.go +++ b/cmd/workspace/clean-rooms/clean-rooms.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newCreateOutputCatalog()) @@ -90,6 +94,8 @@ func newCreate() *cobra.Command { privilege on the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -176,6 +182,8 @@ func newCreateOutputCatalog() *cobra.Command { CLEAN_ROOM_NAME: Name of the clean room.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -248,6 +256,8 @@ func newDelete() *cobra.Command { NAME: Name of the clean room.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -301,6 +311,8 @@ func newGet() *cobra.Command { Get the details of a clean room given its name.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -369,6 +381,8 @@ func newList() *cobra.Command { has access to are returned.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -435,6 +449,8 @@ func newUpdate() *cobra.Command { NAME: Name of the clean room.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/cluster-policies/cluster-policies.go b/cmd/workspace/cluster-policies/cluster-policies.go index 4aa257d5692..6cd52d27112 100755 --- a/cmd/workspace/cluster-policies/cluster-policies.go +++ b/cmd/workspace/cluster-policies/cluster-policies.go @@ -48,6 +48,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -99,6 +103,8 @@ func newCreate() *cobra.Command { Creates a new policy with prescribed settings.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -171,6 +177,8 @@ func newDelete() *cobra.Command { POLICY_ID: The ID of the policy to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -275,6 +283,8 @@ func newEdit() *cobra.Command { POLICY_ID: The ID of the policy to update.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -368,6 +378,8 @@ func newGet() *cobra.Command { POLICY_ID: Canonical unique identifier for the Cluster Policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -437,6 +449,8 @@ func newGetPermissionLevels() *cobra.Command { CLUSTER_POLICY_ID: The cluster policy for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -507,6 +521,8 @@ func newGetPermissions() *cobra.Command { CLUSTER_POLICY_ID: The cluster policy for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -585,6 +601,8 @@ func newList() *cobra.Command { Returns a list of policies accessible by the requesting user.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -650,6 +668,8 @@ func newSetPermissions() *cobra.Command { CLUSTER_POLICY_ID: The cluster policy for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -737,6 +757,8 @@ func newUpdatePermissions() *cobra.Command { CLUSTER_POLICY_ID: The cluster policy for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/clusters/clusters.go b/cmd/workspace/clusters/clusters.go index ca8ed39838c..467246ca9a5 100755 --- a/cmd/workspace/clusters/clusters.go +++ b/cmd/workspace/clusters/clusters.go @@ -52,6 +52,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newChangeOwner()) cmd.AddCommand(newCreate()) @@ -113,6 +117,8 @@ func newChangeOwner() *cobra.Command { OWNER_USERNAME: New owner of the cluster_id after this RPC.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -266,6 +272,8 @@ func newCreate() *cobra.Command { :method:clusters/sparkVersions API call.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -367,6 +375,8 @@ func newDelete() *cobra.Command { CLUSTER_ID: The cluster to be terminated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -539,6 +549,8 @@ func newEdit() *cobra.Command { :method:clusters/sparkVersions API call.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -654,6 +666,8 @@ func newEvents() *cobra.Command { CLUSTER_ID: The ID of the cluster to retrieve events about.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -752,6 +766,8 @@ func newGet() *cobra.Command { CLUSTER_ID: The cluster about which to retrieve information.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -821,6 +837,8 @@ func newGetPermissionLevels() *cobra.Command { CLUSTER_ID: The cluster for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -891,6 +909,8 @@ func newGetPermissions() *cobra.Command { CLUSTER_ID: The cluster for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -974,6 +994,8 @@ func newList() *cobra.Command { are not included.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1027,6 +1049,8 @@ func newListNodeTypes() *cobra.Command { launch a cluster.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1071,6 +1095,8 @@ func newListZones() *cobra.Command { example, us-west-2a). These zones can be used to launch a cluster.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1128,6 +1154,8 @@ func newPermanentDelete() *cobra.Command { CLUSTER_ID: The cluster to be deleted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1222,6 +1250,8 @@ func newPin() *cobra.Command { effect. This API can only be called by workspace admins.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1327,6 +1357,8 @@ func newResize() *cobra.Command { CLUSTER_ID: The cluster to be resized.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1444,6 +1476,8 @@ func newRestart() *cobra.Command { CLUSTER_ID: The cluster to be started.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1556,6 +1590,8 @@ func newSetPermissions() *cobra.Command { CLUSTER_ID: The cluster for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1632,6 +1668,8 @@ func newSparkVersions() *cobra.Command { launch a cluster.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1696,6 +1734,8 @@ func newStart() *cobra.Command { CLUSTER_ID: The cluster to be started.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1802,6 +1842,8 @@ func newUnpin() *cobra.Command { This API can only be called by workspace admins.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1928,6 +1970,8 @@ func newUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2026,6 +2070,8 @@ func newUpdatePermissions() *cobra.Command { CLUSTER_ID: The cluster for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/cmd.go b/cmd/workspace/cmd.go index f8d5e672629..d944c6c90a2 100755 --- a/cmd/workspace/cmd.go +++ b/cmd/workspace/cmd.go @@ -11,6 +11,7 @@ import ( apps "github.com/databricks/cli/cmd/workspace/apps" apps_settings "github.com/databricks/cli/cmd/workspace/apps-settings" artifact_allowlists "github.com/databricks/cli/cmd/workspace/artifact-allowlists" + bundle "github.com/databricks/cli/cmd/workspace/bundle" catalogs "github.com/databricks/cli/cmd/workspace/catalogs" clean_room_asset_revisions "github.com/databricks/cli/cmd/workspace/clean-room-asset-revisions" clean_room_assets "github.com/databricks/cli/cmd/workspace/clean-room-assets" @@ -96,18 +97,21 @@ import ( rfa "github.com/databricks/cli/cmd/workspace/rfa" schemas "github.com/databricks/cli/cmd/workspace/schemas" secrets "github.com/databricks/cli/cmd/workspace/secrets" + secrets_uc "github.com/databricks/cli/cmd/workspace/secrets-uc" service_principal_secrets_proxy "github.com/databricks/cli/cmd/workspace/service-principal-secrets-proxy" service_principals_v2 "github.com/databricks/cli/cmd/workspace/service-principals-v2" serving_endpoints "github.com/databricks/cli/cmd/workspace/serving-endpoints" settings "github.com/databricks/cli/cmd/workspace/settings" shares "github.com/databricks/cli/cmd/workspace/shares" storage_credentials "github.com/databricks/cli/cmd/workspace/storage-credentials" + supervisor_agents "github.com/databricks/cli/cmd/workspace/supervisor-agents" system_schemas "github.com/databricks/cli/cmd/workspace/system-schemas" table_constraints "github.com/databricks/cli/cmd/workspace/table-constraints" tables "github.com/databricks/cli/cmd/workspace/tables" tag_policies "github.com/databricks/cli/cmd/workspace/tag-policies" temporary_path_credentials "github.com/databricks/cli/cmd/workspace/temporary-path-credentials" temporary_table_credentials "github.com/databricks/cli/cmd/workspace/temporary-table-credentials" + temporary_volume_credentials "github.com/databricks/cli/cmd/workspace/temporary-volume-credentials" token_management "github.com/databricks/cli/cmd/workspace/token-management" tokens "github.com/databricks/cli/cmd/workspace/tokens" users_v2 "github.com/databricks/cli/cmd/workspace/users-v2" @@ -139,6 +143,7 @@ func All() []*cobra.Command { out = append(out, apps.New()) out = append(out, apps_settings.New()) out = append(out, artifact_allowlists.New()) + out = append(out, bundle.New()) out = append(out, catalogs.New()) out = append(out, clean_room_asset_revisions.New()) out = append(out, clean_room_assets.New()) @@ -224,18 +229,21 @@ func All() []*cobra.Command { out = append(out, rfa.New()) out = append(out, schemas.New()) out = append(out, secrets.New()) + out = append(out, secrets_uc.New()) out = append(out, service_principal_secrets_proxy.New()) out = append(out, service_principals_v2.New()) out = append(out, serving_endpoints.New()) out = append(out, settings.New()) out = append(out, shares.New()) out = append(out, storage_credentials.New()) + out = append(out, supervisor_agents.New()) out = append(out, system_schemas.New()) out = append(out, table_constraints.New()) out = append(out, tables.New()) out = append(out, tag_policies.New()) out = append(out, temporary_path_credentials.New()) out = append(out, temporary_table_credentials.New()) + out = append(out, temporary_volume_credentials.New()) out = append(out, token_management.New()) out = append(out, tokens.New()) out = append(out, users_v2.New()) diff --git a/cmd/workspace/compliance-security-profile/compliance-security-profile.go b/cmd/workspace/compliance-security-profile/compliance-security-profile.go index 570c2e1897a..f709c78a1c9 100755 --- a/cmd/workspace/compliance-security-profile/compliance-security-profile.go +++ b/cmd/workspace/compliance-security-profile/compliance-security-profile.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "compliance-security-profile", - Short: `Controls whether to enable the compliance security profile for the current workspace.`, - Long: `Controls whether to enable the compliance security profile for the current + Short: `*Public Preview* Controls whether to enable the compliance security profile for the current workspace.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether to enable the compliance security profile for the current workspace. Enabling it on a workspace is permanent. By default, it is turned off. @@ -29,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -58,12 +64,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the compliance security profile setting.` - cmd.Long = `Get the compliance security profile setting. + cmd.Short = `*Public Preview* Get the compliance security profile setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the compliance security profile setting. Gets the compliance security profile setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -113,8 +123,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the compliance security profile setting.` - cmd.Long = `Update the compliance security profile setting. + cmd.Short = `*Public Preview* Update the compliance security profile setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the compliance security profile setting. Updates the compliance security profile setting for the workspace. A fresh etag needs to be provided in PATCH requests (as part of the setting field). @@ -123,6 +135,8 @@ func newUpdate() *cobra.Command { the request must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/connections/connections.go b/cmd/workspace/connections/connections.go index 5507ed839af..7753bba923f 100755 --- a/cmd/workspace/connections/connections.go +++ b/cmd/workspace/connections/connections.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -83,6 +87,8 @@ func newCreate() *cobra.Command { external server.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -148,6 +154,8 @@ func newDelete() *cobra.Command { NAME: The name of the connection to be deleted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -216,6 +224,8 @@ func newGet() *cobra.Command { NAME: Name of the connection.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -303,6 +313,8 @@ func newList() *cobra.Command { indication that the end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -367,6 +379,8 @@ func newUpdate() *cobra.Command { NAME: Name of the connection.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/consumer-fulfillments/consumer-fulfillments.go b/cmd/workspace/consumer-fulfillments/consumer-fulfillments.go index 6bf3a6169a5..73c78334ca7 100755 --- a/cmd/workspace/consumer-fulfillments/consumer-fulfillments.go +++ b/cmd/workspace/consumer-fulfillments/consumer-fulfillments.go @@ -18,13 +18,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "consumer-fulfillments", - Short: `Fulfillments are entities that allow consumers to preview installations.`, - Long: `Fulfillments are entities that allow consumers to preview installations.`, + Use: "consumer-fulfillments", + Short: `*Public Preview* Fulfillments are entities that allow consumers to preview installations.`, + Long: `This command is in Public Preview and may change without notice. + +Fulfillments are entities that allow consumers to preview installations.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newList()) @@ -65,12 +71,16 @@ func newGet() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "get LISTING_ID" - cmd.Short = `Get listing content metadata.` - cmd.Long = `Get listing content metadata. + cmd.Short = `*Public Preview* Get listing content metadata.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get listing content metadata. Get a high level preview of the metadata of listing installable content.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -135,8 +145,10 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list LISTING_ID" - cmd.Short = `List all listing fulfillments.` - cmd.Long = `List all listing fulfillments. + cmd.Short = `*Public Preview* List all listing fulfillments.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List all listing fulfillments. Get all listings fulfillments associated with a listing. A _fulfillment_ is a potential installation. Standard installations contain metadata about the @@ -145,6 +157,8 @@ func newList() *cobra.Command { repo, as well as the Delta Sharing recipient type.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/consumer-installations/consumer-installations.go b/cmd/workspace/consumer-installations/consumer-installations.go index 352b8380764..b22738dd062 100755 --- a/cmd/workspace/consumer-installations/consumer-installations.go +++ b/cmd/workspace/consumer-installations/consumer-installations.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "consumer-installations", - Short: `Installations are entities that allow consumers to interact with Databricks Marketplace listings.`, - Long: `Installations are entities that allow consumers to interact with Databricks + Short: `*Public Preview* Installations are entities that allow consumers to interact with Databricks Marketplace listings.`, + Long: `This command is in Public Preview and may change without notice. + +Installations are entities that allow consumers to interact with Databricks Marketplace listings.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -66,12 +72,16 @@ func newCreate() *cobra.Command { cmd.Flags().StringVar(&createReq.ShareName, "share-name", createReq.ShareName, ``) cmd.Use = "create LISTING_ID" - cmd.Short = `Install from a listing.` - cmd.Long = `Install from a listing. + cmd.Short = `*Public Preview* Install from a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Install from a listing. Install payload associated with a Databricks Marketplace listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -132,12 +142,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteInstallationRequest cmd.Use = "delete LISTING_ID INSTALLATION_ID" - cmd.Short = `Uninstall from a listing.` - cmd.Long = `Uninstall from a listing. + cmd.Short = `*Public Preview* Uninstall from a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Uninstall from a listing. Uninstall an installation associated with a Databricks Marketplace listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -199,12 +213,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List all installations.` - cmd.Long = `List all installations. + cmd.Short = `*Public Preview* List all installations.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List all installations. List all installations across all listings.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -267,12 +285,16 @@ func newListListingInstallations() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-listing-installations LISTING_ID" - cmd.Short = `List installations for a listing.` - cmd.Long = `List installations for a listing. + cmd.Short = `*Public Preview* List installations for a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List installations for a listing. List all installations for a particular listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -329,8 +351,10 @@ func newUpdate() *cobra.Command { cmd.Flags().BoolVar(&updateReq.RotateToken, "rotate-token", updateReq.RotateToken, ``) cmd.Use = "update LISTING_ID INSTALLATION_ID" - cmd.Short = `Update an installation.` - cmd.Long = `Update an installation. + cmd.Short = `*Public Preview* Update an installation.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an installation. This is a update API that will update the part of the fields defined in the installation table as well as interact with external services according to the @@ -339,6 +363,8 @@ func newUpdate() *cobra.Command { rotateToken flag is true and the tokenInfo field is empty` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/consumer-listings/consumer-listings.go b/cmd/workspace/consumer-listings/consumer-listings.go index a3bfe6f9c30..f8113e3532a 100755 --- a/cmd/workspace/consumer-listings/consumer-listings.go +++ b/cmd/workspace/consumer-listings/consumer-listings.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "consumer-listings", - Short: `Listings are the core entities in the Marketplace.`, - Long: `Listings are the core entities in the Marketplace. They represent the products + Short: `*Public Preview* Listings are the core entities in the Marketplace.`, + Long: `This command is in Public Preview and may change without notice. + +Listings are the core entities in the Marketplace. They represent the products that are available for consumption.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newBatchGet()) cmd.AddCommand(newGet()) @@ -57,13 +63,17 @@ func newBatchGet() *cobra.Command { // TODO: array: ids cmd.Use = "batch-get" - cmd.Short = `Get one batch of listings. One may specify up to 50 IDs per request.` - cmd.Long = `Get one batch of listings. One may specify up to 50 IDs per request. + cmd.Short = `*Public Preview* Get one batch of listings. One may specify up to 50 IDs per request.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get one batch of listings. One may specify up to 50 IDs per request. Batch get a published listing in the Databricks Marketplace that the consumer has access to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -110,13 +120,17 @@ func newGet() *cobra.Command { var getReq marketplace.GetListingRequest cmd.Use = "get ID" - cmd.Short = `Get listing.` - cmd.Long = `Get listing. + cmd.Short = `*Public Preview* Get listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get listing. Get a published listing in the Databricks Marketplace that the consumer has access to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -187,7 +201,7 @@ func newList() *cobra.Command { cmd.Flags().BoolVar(&listReq.IsStaffPick, "is-staff-pick", listReq.IsStaffPick, `Filters each listing based on whether it is a staff pick.`) cmd.Flags().IntVar(&listReq.PageSize, "page-size", listReq.PageSize, ``) // TODO: array: provider_ids - // TODO: array: tags + // TODO: complex arg: tags // Limit flag for total result capping. cmd.Flags().IntVar(&listLimit, "limit", 0, `Maximum number of results to return.`) @@ -197,13 +211,17 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List listings.` - cmd.Long = `List listings. + cmd.Short = `*Public Preview* List listings.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List listings. List all published listings in the Databricks Marketplace that the consumer has access to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -271,8 +289,10 @@ func newSearch() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "search QUERY" - cmd.Short = `Search listings.` - cmd.Long = `Search listings. + cmd.Short = `*Public Preview* Search listings.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Search listings. Search published listings in the Databricks Marketplace that the consumer has access to. This query supports a variety of different search parameters and @@ -282,6 +302,8 @@ func newSearch() *cobra.Command { QUERY: Fuzzy matches query` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/consumer-personalization-requests/consumer-personalization-requests.go b/cmd/workspace/consumer-personalization-requests/consumer-personalization-requests.go index 74f6f7e5a95..c1bbdc047d0 100755 --- a/cmd/workspace/consumer-personalization-requests/consumer-personalization-requests.go +++ b/cmd/workspace/consumer-personalization-requests/consumer-personalization-requests.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "consumer-personalization-requests", - Short: `Personalization Requests allow customers to interact with the individualized Marketplace listing flow.`, - Long: `Personalization Requests allow customers to interact with the individualized + Short: `*Public Preview* Personalization Requests allow customers to interact with the individualized Marketplace listing flow.`, + Long: `This command is in Public Preview and may change without notice. + +Personalization Requests allow customers to interact with the individualized Marketplace listing flow.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newGet()) @@ -65,12 +71,16 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createReq.RecipientType, "recipient-type", `Supported values: [DELTA_SHARING_RECIPIENT_TYPE_DATABRICKS, DELTA_SHARING_RECIPIENT_TYPE_OPEN]`) cmd.Use = "create LISTING_ID" - cmd.Short = `Create a personalization request.` - cmd.Long = `Create a personalization request. + cmd.Short = `*Public Preview* Create a personalization request.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a personalization request. Create a personalization request for a listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -133,13 +143,17 @@ func newGet() *cobra.Command { var getReq marketplace.GetPersonalizationRequestRequest cmd.Use = "get LISTING_ID" - cmd.Short = `Get the personalization request for a listing.` - cmd.Long = `Get the personalization request for a listing. + cmd.Short = `*Public Preview* Get the personalization request for a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the personalization request for a listing. Get the personalization request for a listing. Each consumer can make at *most* one personalization request for a listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -201,12 +215,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List all personalization requests.` - cmd.Long = `List all personalization requests. + cmd.Short = `*Public Preview* List all personalization requests.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List all personalization requests. List personalization requests for a consumer across all listings.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/consumer-providers/consumer-providers.go b/cmd/workspace/consumer-providers/consumer-providers.go index 9c579c5d610..4dc372d24d4 100755 --- a/cmd/workspace/consumer-providers/consumer-providers.go +++ b/cmd/workspace/consumer-providers/consumer-providers.go @@ -18,13 +18,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "consumer-providers", - Short: `Providers are the entities that publish listings to the Marketplace.`, - Long: `Providers are the entities that publish listings to the Marketplace.`, + Use: "consumer-providers", + Short: `*Public Preview* Providers are the entities that publish listings to the Marketplace.`, + Long: `This command is in Public Preview and may change without notice. + +Providers are the entities that publish listings to the Marketplace.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newBatchGet()) cmd.AddCommand(newGet()) @@ -55,13 +61,17 @@ func newBatchGet() *cobra.Command { // TODO: array: ids cmd.Use = "batch-get" - cmd.Short = `Get one batch of providers. One may specify up to 50 IDs per request.` - cmd.Long = `Get one batch of providers. One may specify up to 50 IDs per request. + cmd.Short = `*Public Preview* Get one batch of providers. One may specify up to 50 IDs per request.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get one batch of providers. One may specify up to 50 IDs per request. Batch get a provider in the Databricks Marketplace with at least one visible listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -108,13 +118,17 @@ func newGet() *cobra.Command { var getReq marketplace.GetProviderRequest cmd.Use = "get ID" - cmd.Short = `Get a provider.` - cmd.Long = `Get a provider. + cmd.Short = `*Public Preview* Get a provider.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a provider. Get a provider in the Databricks Marketplace with at least one visible listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -189,13 +203,17 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List providers.` - cmd.Long = `List providers. + cmd.Short = `*Public Preview* List providers.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List providers. List all providers in the Databricks Marketplace with at least one visible listing.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/credentials-manager/credentials-manager.go b/cmd/workspace/credentials-manager/credentials-manager.go index 4e0d2a10d84..67386dcecc1 100755 --- a/cmd/workspace/credentials-manager/credentials-manager.go +++ b/cmd/workspace/credentials-manager/credentials-manager.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newExchangeToken()) @@ -63,9 +67,22 @@ func newExchangeToken() *cobra.Command { cmd.Long = `Exchange token. Exchange tokens with an Identity Provider to get a new access token. It allows - specifying scopes to determine token permissions.` + specifying scopes to determine token permissions. + + POST /exchange-tokens/token is the documented public form, expressed via + google.api.http below. GET /exchange-tokens/$exchange is a legacy alias used + by the Spark driver's OAuth refresh path (DBHttpClient#get sends a body via + HttpGetWithEntity) and stays on the legacy option (rpc).endpoints + annotation: its path contains a literal $, which google.api.http's LITERAL + grammar does not allow, and HttpPathParser does not percent-decode template + segments (so encoding as %24exchange would not match the literal $exchange + path the Spark driver sends). Per-endpoint visibility: PUBLIC_UNDOCUMENTED + preserves the DECO-7732 intent of suppressing the GET alias from the public + API spec.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/credentials/credentials.go b/cmd/workspace/credentials/credentials.go index cd743f0edf8..e8ba0edc4dc 100755 --- a/cmd/workspace/credentials/credentials.go +++ b/cmd/workspace/credentials/credentials.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateCredential()) cmd.AddCommand(newDeleteCredential()) @@ -92,6 +96,8 @@ func newCreateCredential() *cobra.Command { credentials within the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -173,6 +179,8 @@ func newDeleteCredential() *cobra.Command { NAME_ARG: Name of the credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -237,6 +245,8 @@ func newGenerateTemporaryServiceCredential() *cobra.Command { CREDENTIAL_NAME: The name of the service credential used to generate a temporary credential` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -317,6 +327,8 @@ func newGetCredential() *cobra.Command { NAME_ARG: Name of the credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -396,6 +408,8 @@ func newListCredentials() *cobra.Command { end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -473,6 +487,8 @@ func newUpdateCredential() *cobra.Command { NAME_ARG: Name of the credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -565,6 +581,8 @@ func newValidateCredential() *cobra.Command { **CREATE_EXTERNAL_LOCATION** when purpose is **STORAGE**).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/current-user/current-user.go b/cmd/workspace/current-user/current-user.go index 7a11844f6c5..de32f481229 100755 --- a/cmd/workspace/current-user/current-user.go +++ b/cmd/workspace/current-user/current-user.go @@ -6,6 +6,7 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/spf13/cobra" ) @@ -16,13 +17,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "current-user", - Short: `This API allows retrieving information about currently authenticated user or service principal.`, - Long: `This API allows retrieving information about currently authenticated user or + Short: `*Public Preview* This API allows retrieving information about currently authenticated user or service principal.`, + Long: `This command is in Public Preview and may change without notice. + +This API allows retrieving information about currently authenticated user or service principal.`, GroupID: "iam", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newMe()) @@ -40,24 +47,40 @@ func New() *cobra.Command { // Functions can be added from the `init()` function in manually curated files in this directory. var meOverrides []func( *cobra.Command, + *iam.MeRequest, ) func newMe() *cobra.Command { cmd := &cobra.Command{} + var meReq iam.MeRequest + + cmd.Flags().StringVar(&meReq.Attributes, "attributes", meReq.Attributes, `Comma-separated list of attributes to return in response.`) + cmd.Flags().StringVar(&meReq.ExcludedAttributes, "excluded-attributes", meReq.ExcludedAttributes, `Comma-separated list of attributes to exclude in response.`) + cmd.Use = "me" - cmd.Short = `Get current user info.` - cmd.Long = `Get current user info. + cmd.Short = `*Public Preview* Get current user info.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get current user info. Get details about the current method caller's identity.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { ctx := cmd.Context() w := cmdctx.WorkspaceClient(ctx) - response, err := w.CurrentUser.Me(ctx) + + response, err := w.CurrentUser.Me(ctx, meReq) if err != nil { return err } @@ -71,7 +94,7 @@ func newMe() *cobra.Command { // Apply optional overrides to this command. for _, fn := range meOverrides { - fn(cmd) + fn(cmd, &meReq) } return cmd diff --git a/cmd/workspace/dashboard-email-subscriptions/dashboard-email-subscriptions.go b/cmd/workspace/dashboard-email-subscriptions/dashboard-email-subscriptions.go index 702bf376d54..5da81d45bb1 100755 --- a/cmd/workspace/dashboard-email-subscriptions/dashboard-email-subscriptions.go +++ b/cmd/workspace/dashboard-email-subscriptions/dashboard-email-subscriptions.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "dashboard-email-subscriptions", - Short: `Controls whether schedules or workload tasks for refreshing AI/BI Dashboards in the workspace can send subscription emails containing PDFs and/or images of the dashboard.`, - Long: `Controls whether schedules or workload tasks for refreshing AI/BI Dashboards + Short: `*Public Preview* Controls whether schedules or workload tasks for refreshing AI/BI Dashboards in the workspace can send subscription emails containing PDFs and/or images of the dashboard.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether schedules or workload tasks for refreshing AI/BI Dashboards in the workspace can send subscription emails containing PDFs and/or images of the dashboard. By default, this setting is enabled (set to true)`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -57,12 +63,16 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete the Dashboard Email Subscriptions setting.` - cmd.Long = `Delete the Dashboard Email Subscriptions setting. + cmd.Short = `*Public Preview* Delete the Dashboard Email Subscriptions setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete the Dashboard Email Subscriptions setting. Reverts the Dashboard Email Subscriptions setting to its default value.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -111,12 +121,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the Dashboard Email Subscriptions setting.` - cmd.Long = `Get the Dashboard Email Subscriptions setting. + cmd.Short = `*Public Preview* Get the Dashboard Email Subscriptions setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the Dashboard Email Subscriptions setting. Gets the Dashboard Email Subscriptions setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -166,12 +180,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the Dashboard Email Subscriptions setting.` - cmd.Long = `Update the Dashboard Email Subscriptions setting. + cmd.Short = `*Public Preview* Update the Dashboard Email Subscriptions setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the Dashboard Email Subscriptions setting. Updates the Dashboard Email Subscriptions setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/dashboard-widgets/dashboard-widgets.go b/cmd/workspace/dashboard-widgets/dashboard-widgets.go index 89d584ecac8..891896c02b2 100755 --- a/cmd/workspace/dashboard-widgets/dashboard-widgets.go +++ b/cmd/workspace/dashboard-widgets/dashboard-widgets.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -71,6 +75,8 @@ func newCreate() *cobra.Command { Adds a widget to a dashboard` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -136,6 +142,8 @@ func newDelete() *cobra.Command { ID: Widget ID returned by :method:dashboardwidgets/create` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -198,6 +206,8 @@ func newUpdate() *cobra.Command { ID: Widget ID returned by :method:dashboardwidgets/create` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/dashboards/dashboards.go b/cmd/workspace/dashboards/dashboards.go index fedffb5a397..5eb7ed2d3a1 100755 --- a/cmd/workspace/dashboards/dashboards.go +++ b/cmd/workspace/dashboards/dashboards.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -78,6 +82,8 @@ func newDelete() *cobra.Command { [Learn more]: https://docs.databricks.com/en/dashboards/` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -137,6 +143,8 @@ func newGet() *cobra.Command { [Learn more]: https://docs.databricks.com/en/dashboards/` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -215,6 +223,8 @@ func newList() *cobra.Command { [Learn more]: https://docs.databricks.com/en/dashboards/` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -275,6 +285,8 @@ func newRestore() *cobra.Command { [Learn more]: https://docs.databricks.com/en/dashboards/` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -343,6 +355,8 @@ func newUpdate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/dashboards/` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/data-classification/data-classification.go b/cmd/workspace/data-classification/data-classification.go index e3a87558072..e7809590300 100755 --- a/cmd/workspace/data-classification/data-classification.go +++ b/cmd/workspace/data-classification/data-classification.go @@ -21,8 +21,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "data-classification", - Short: `Manage data classification for Unity Catalog catalogs.`, - Long: `Manage data classification for Unity Catalog catalogs. Data classification + Short: `*Beta* Manage data classification for Unity Catalog catalogs.`, + Long: `This command is in Beta and may change without notice. + +Manage data classification for Unity Catalog catalogs. Data classification automatically identifies and tags sensitive data (PII) in Unity Catalog tables. Each catalog can have at most one configuration resource that controls scanning behavior and auto-tagging rules.`, @@ -30,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newCreateCatalogConfig()) cmd.AddCommand(newDeleteCatalogConfig()) @@ -67,8 +73,10 @@ func newCreateCatalogConfig() *cobra.Command { cmd.Flags().StringVar(&createCatalogConfigReq.CatalogConfig.Name, "name", createCatalogConfigReq.CatalogConfig.Name, `Resource name in the format: catalogs/{catalog_name}/config.`) cmd.Use = "create-catalog-config PARENT" - cmd.Short = `Create config for a catalog.` - cmd.Long = `Create config for a catalog. + cmd.Short = `*Beta* Create config for a catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Create config for a catalog. Create Data Classification configuration for a catalog. @@ -79,6 +87,8 @@ func newCreateCatalogConfig() *cobra.Command { PARENT: Parent resource in the format: catalogs/{catalog_name}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -139,8 +149,10 @@ func newDeleteCatalogConfig() *cobra.Command { var deleteCatalogConfigReq dataclassification.DeleteCatalogConfigRequest cmd.Use = "delete-catalog-config NAME" - cmd.Short = `Delete config for a catalog.` - cmd.Long = `Delete config for a catalog. + cmd.Short = `*Beta* Delete config for a catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete config for a catalog. Delete Data Classification configuration for a catalog. @@ -148,6 +160,8 @@ func newDeleteCatalogConfig() *cobra.Command { NAME: Resource name in the format: catalogs/{catalog_name}/config` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -195,8 +209,10 @@ func newGetCatalogConfig() *cobra.Command { var getCatalogConfigReq dataclassification.GetCatalogConfigRequest cmd.Use = "get-catalog-config NAME" - cmd.Short = `Get config for a catalog.` - cmd.Long = `Get config for a catalog. + cmd.Short = `*Beta* Get config for a catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Get config for a catalog. Get the Data Classification configuration for a catalog. @@ -204,6 +220,8 @@ func newGetCatalogConfig() *cobra.Command { NAME: Resource name in the format: catalogs/{catalog_name}/config` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -260,8 +278,10 @@ func newUpdateCatalogConfig() *cobra.Command { cmd.Flags().StringVar(&updateCatalogConfigReq.CatalogConfig.Name, "name", updateCatalogConfigReq.CatalogConfig.Name, `Resource name in the format: catalogs/{catalog_name}/config.`) cmd.Use = "update-catalog-config NAME UPDATE_MASK" - cmd.Short = `Update config for a catalog.` - cmd.Long = `Update config for a catalog. + cmd.Short = `*Beta* Update config for a catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Update config for a catalog. Update the Data Classification configuration for a catalog. - The config must already exist for the catalog. - Updates fields specified in the update_mask. @@ -272,6 +292,8 @@ func newUpdateCatalogConfig() *cobra.Command { UPDATE_MASK: Field mask specifying which fields to update.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/data-quality/data-quality.go b/cmd/workspace/data-quality/data-quality.go index 905a0076d51..47234a41df2 100755 --- a/cmd/workspace/data-quality/data-quality.go +++ b/cmd/workspace/data-quality/data-quality.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "data-quality", - Short: `Manage the data quality of Unity Catalog objects (currently support schema and table).`, - Long: `Manage the data quality of Unity Catalog objects (currently support schema + Short: `*Public Preview* Manage the data quality of Unity Catalog objects (currently support schema and table).`, + Long: `This command is in Public Preview and may change without notice. + +Manage the data quality of Unity Catalog objects (currently support schema and table)`, GroupID: "dataquality", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCancelRefresh()) cmd.AddCommand(newCreateMonitor()) @@ -63,8 +69,10 @@ func newCancelRefresh() *cobra.Command { var cancelRefreshReq dataquality.CancelRefreshRequest cmd.Use = "cancel-refresh OBJECT_TYPE OBJECT_ID REFRESH_ID" - cmd.Short = `Cancel a refresh.` - cmd.Long = `Cancel a refresh. + cmd.Short = `*Public Preview* Cancel a refresh.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Cancel a refresh. Cancels a data quality monitor refresh. Currently only supported for the table object_type. The call must be made in the same workspace as where @@ -97,6 +105,8 @@ func newCancelRefresh() *cobra.Command { REFRESH_ID: Unique id of the refresh operation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -157,8 +167,10 @@ func newCreateMonitor() *cobra.Command { // TODO: complex arg: data_profiling_config cmd.Use = "create-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Create a monitor.` - cmd.Long = `Create a monitor. + cmd.Short = `*Public Preview* Create a monitor.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a monitor. Create a data quality monitor on a Unity Catalog object. The caller must provide either anomaly_detection_config for a schema monitor or @@ -199,6 +211,8 @@ func newCreateMonitor() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -275,8 +289,10 @@ func newCreateRefresh() *cobra.Command { cmd.Flags().Var(&createRefreshJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create-refresh OBJECT_TYPE OBJECT_ID" - cmd.Short = `Create a refresh.` - cmd.Long = `Create a refresh. + cmd.Short = `*Public Preview* Create a refresh.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a refresh. Creates a refresh. Currently only supported for the table object_type. The call must be made in the same workspace as where the monitor was created. @@ -307,6 +323,8 @@ func newCreateRefresh() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -368,8 +386,10 @@ func newDeleteMonitor() *cobra.Command { var deleteMonitorReq dataquality.DeleteMonitorRequest cmd.Use = "delete-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Delete a monitor.` - cmd.Long = `Delete a monitor. + cmd.Short = `*Public Preview* Delete a monitor.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a monitor. Delete a data quality monitor on Unity Catalog object. @@ -407,6 +427,8 @@ func newDeleteMonitor() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -455,8 +477,10 @@ func newDeleteRefresh() *cobra.Command { var deleteRefreshReq dataquality.DeleteRefreshRequest cmd.Use = "delete-refresh OBJECT_TYPE OBJECT_ID REFRESH_ID" - cmd.Short = `Delete a refresh.` - cmd.Long = `Delete a refresh. + cmd.Short = `*Public Preview* Delete a refresh.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a refresh. (Unimplemented) Delete a refresh @@ -480,6 +504,8 @@ func newDeleteRefresh() *cobra.Command { REFRESH_ID: Unique id of the refresh operation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -532,8 +558,10 @@ func newGetMonitor() *cobra.Command { var getMonitorReq dataquality.GetMonitorRequest cmd.Use = "get-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Read a monitor.` - cmd.Long = `Read a monitor. + cmd.Short = `*Public Preview* Read a monitor.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Read a monitor. Read a data quality monitor on a Unity Catalog object. @@ -573,6 +601,8 @@ func newGetMonitor() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -622,8 +652,10 @@ func newGetRefresh() *cobra.Command { var getRefreshReq dataquality.GetRefreshRequest cmd.Use = "get-refresh OBJECT_TYPE OBJECT_ID REFRESH_ID" - cmd.Short = `Get a refresh.` - cmd.Long = `Get a refresh. + cmd.Short = `*Public Preview* Get a refresh.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a refresh. Get data quality monitor refresh. The call must be made in the same workspace as where the monitor was created. @@ -660,6 +692,8 @@ func newGetRefresh() *cobra.Command { REFRESH_ID: Unique id of the refresh operation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -726,12 +760,16 @@ func newListMonitor() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-monitor" - cmd.Short = `List monitors.` - cmd.Long = `List monitors. + cmd.Short = `*Public Preview* List monitors.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List monitors. (Unimplemented) List data quality monitors.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -794,8 +832,10 @@ func newListRefresh() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-refresh OBJECT_TYPE OBJECT_ID" - cmd.Short = `List refreshes.` - cmd.Long = `List refreshes. + cmd.Short = `*Public Preview* List refreshes.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List refreshes. List data quality monitor refreshes. The call must be made in the same workspace as where the monitor was created. @@ -831,6 +871,8 @@ func newListRefresh() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -890,8 +932,10 @@ func newUpdateMonitor() *cobra.Command { // TODO: complex arg: data_profiling_config cmd.Use = "update-monitor OBJECT_TYPE OBJECT_ID UPDATE_MASK OBJECT_TYPE OBJECT_ID" - cmd.Short = `Update a monitor.` - cmd.Long = `Update a monitor. + cmd.Short = `*Public Preview* Update a monitor.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a monitor. Update a data quality monitor on Unity Catalog object. @@ -945,6 +989,8 @@ func newUpdateMonitor() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1024,8 +1070,10 @@ func newUpdateRefresh() *cobra.Command { cmd.Flags().Var(&updateRefreshJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update-refresh OBJECT_TYPE OBJECT_ID REFRESH_ID UPDATE_MASK OBJECT_TYPE OBJECT_ID" - cmd.Short = `Update a refresh.` - cmd.Long = `Update a refresh. + cmd.Short = `*Public Preview* Update a refresh.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a refresh. (Unimplemented) Update a refresh @@ -1066,6 +1114,8 @@ func newUpdateRefresh() *cobra.Command { [table_id]: https://docs.databricks.com/api/workspace/tables/get#table_id` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/data-sources/data-sources.go b/cmd/workspace/data-sources/data-sources.go index 637522c1126..33bee7f17bf 100755 --- a/cmd/workspace/data-sources/data-sources.go +++ b/cmd/workspace/data-sources/data-sources.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newList()) @@ -72,6 +76,8 @@ func newList() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/database/database.go b/cmd/workspace/database/database.go index b2476255bb0..92aa1408bfd 100755 --- a/cmd/workspace/database/database.go +++ b/cmd/workspace/database/database.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "database", - Short: `Database Instances provide access to a database via REST API or direct SQL.`, - Long: `Database Instances provide access to a database via REST API or direct SQL.`, + Use: "database", + Short: `*Public Preview* Database Instances provide access to a database via REST API or direct SQL.`, + Long: `This command is in Public Preview and may change without notice. + +Database Instances provide access to a database via REST API or direct SQL.`, GroupID: "database", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreateDatabaseCatalog()) cmd.AddCommand(newCreateDatabaseInstance()) @@ -82,8 +88,10 @@ func newCreateDatabaseCatalog() *cobra.Command { cmd.Flags().BoolVar(&createDatabaseCatalogReq.Catalog.CreateDatabaseIfNotExists, "create-database-if-not-exists", createDatabaseCatalogReq.Catalog.CreateDatabaseIfNotExists, ``) cmd.Use = "create-database-catalog NAME DATABASE_INSTANCE_NAME DATABASE_NAME" - cmd.Short = `Create a Database Catalog.` - cmd.Long = `Create a Database Catalog. + cmd.Short = `*Public Preview* Create a Database Catalog.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a Database Catalog. Arguments: NAME: The name of the catalog in UC. @@ -91,6 +99,8 @@ func newCreateDatabaseCatalog() *cobra.Command { DATABASE_NAME: The name of the database (in a instance) associated with the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -188,13 +198,17 @@ func newCreateDatabaseInstance() *cobra.Command { cmd.Flags().StringVar(&createDatabaseInstanceReq.DatabaseInstance.UsagePolicyId, "usage-policy-id", createDatabaseInstanceReq.DatabaseInstance.UsagePolicyId, `The desired usage policy to associate with the instance.`) cmd.Use = "create-database-instance NAME" - cmd.Short = `Create a Database Instance.` - cmd.Long = `Create a Database Instance. + cmd.Short = `*Public Preview* Create a Database Instance.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a Database Instance. Arguments: NAME: The name of the instance. This is the unique identifier for the instance.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -299,6 +313,8 @@ func newCreateDatabaseInstanceRole() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -376,8 +392,10 @@ func newCreateDatabaseTable() *cobra.Command { cmd.Flags().StringVar(&createDatabaseTableReq.Table.LogicalDatabaseName, "logical-database-name", createDatabaseTableReq.Table.LogicalDatabaseName, `Target Postgres database object (logical database) name for this table.`) cmd.Use = "create-database-table NAME" - cmd.Short = `Create a Database Table.` - cmd.Long = `Create a Database Table. + cmd.Short = `*Public Preview* Create a Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a Database Table. Create a Database Table. Useful for registering pre-existing PG tables in UC. See CreateSyncedDatabaseTable for creating synced tables in PG from a source @@ -387,6 +405,8 @@ func newCreateDatabaseTable() *cobra.Command { NAME: Full three-part (catalog, schema, table) name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -465,13 +485,17 @@ func newCreateSyncedDatabaseTable() *cobra.Command { // TODO: complex arg: spec cmd.Use = "create-synced-database-table NAME" - cmd.Short = `Create a Synced Database Table.` - cmd.Long = `Create a Synced Database Table. + cmd.Short = `*Public Preview* Create a Synced Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a Synced Database Table. Arguments: NAME: Full three-part (catalog, schema, table) name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -541,10 +565,14 @@ func newDeleteDatabaseCatalog() *cobra.Command { var deleteDatabaseCatalogReq database.DeleteDatabaseCatalogRequest cmd.Use = "delete-database-catalog NAME" - cmd.Short = `Delete a Database Catalog.` - cmd.Long = `Delete a Database Catalog.` + cmd.Short = `*Public Preview* Delete a Database Catalog.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a Database Catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -595,13 +623,17 @@ func newDeleteDatabaseInstance() *cobra.Command { cmd.Flags().BoolVar(&deleteDatabaseInstanceReq.Purge, "purge", deleteDatabaseInstanceReq.Purge, `Deprecated.`) cmd.Use = "delete-database-instance NAME" - cmd.Short = `Delete a Database Instance.` - cmd.Long = `Delete a Database Instance. + cmd.Short = `*Public Preview* Delete a Database Instance.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a Database Instance. Arguments: NAME: Name of the instance to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -661,6 +693,8 @@ func newDeleteDatabaseInstanceRole() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -709,10 +743,14 @@ func newDeleteDatabaseTable() *cobra.Command { var deleteDatabaseTableReq database.DeleteDatabaseTableRequest cmd.Use = "delete-database-table NAME" - cmd.Short = `Delete a Database Table.` - cmd.Long = `Delete a Database Table.` + cmd.Short = `*Public Preview* Delete a Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a Database Table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -762,10 +800,14 @@ func newDeleteSyncedDatabaseTable() *cobra.Command { cmd.Flags().BoolVar(&deleteSyncedDatabaseTableReq.PurgeData, "purge-data", deleteSyncedDatabaseTableReq.PurgeData, `Optional.`) cmd.Use = "delete-synced-database-table NAME" - cmd.Short = `Delete a Synced Database Table.` - cmd.Long = `Delete a Synced Database Table.` + cmd.Short = `*Public Preview* Delete a Synced Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a Synced Database Table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -815,10 +857,14 @@ func newFindDatabaseInstanceByUid() *cobra.Command { cmd.Flags().StringVar(&findDatabaseInstanceByUidReq.Uid, "uid", findDatabaseInstanceByUidReq.Uid, `UID of the cluster to get.`) cmd.Use = "find-database-instance-by-uid" - cmd.Short = `Find a Database Instance by uid.` - cmd.Long = `Find a Database Instance by uid.` + cmd.Short = `*Public Preview* Find a Database Instance by uid.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Find a Database Instance by uid.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -872,10 +918,14 @@ func newGenerateDatabaseCredential() *cobra.Command { cmd.Flags().StringVar(&generateDatabaseCredentialReq.RequestId, "request-id", generateDatabaseCredentialReq.RequestId, ``) cmd.Use = "generate-database-credential" - cmd.Short = `Generates a credential that can be used to access database instances.` - cmd.Long = `Generates a credential that can be used to access database instances.` + cmd.Short = `*Public Preview* Generates a credential that can be used to access database instances.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Generates a credential that can be used to access database instances.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -935,10 +985,14 @@ func newGetDatabaseCatalog() *cobra.Command { var getDatabaseCatalogReq database.GetDatabaseCatalogRequest cmd.Use = "get-database-catalog NAME" - cmd.Short = `Get a Database Catalog.` - cmd.Long = `Get a Database Catalog.` + cmd.Short = `*Public Preview* Get a Database Catalog.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a Database Catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -987,13 +1041,17 @@ func newGetDatabaseInstance() *cobra.Command { var getDatabaseInstanceReq database.GetDatabaseInstanceRequest cmd.Use = "get-database-instance NAME" - cmd.Short = `Get a Database Instance.` - cmd.Long = `Get a Database Instance. + cmd.Short = `*Public Preview* Get a Database Instance.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a Database Instance. Arguments: NAME: Name of the cluster to get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1051,6 +1109,8 @@ func newGetDatabaseInstanceRole() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1100,10 +1160,14 @@ func newGetDatabaseTable() *cobra.Command { var getDatabaseTableReq database.GetDatabaseTableRequest cmd.Use = "get-database-table NAME" - cmd.Short = `Get a Database Table.` - cmd.Long = `Get a Database Table.` + cmd.Short = `*Public Preview* Get a Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a Database Table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1152,10 +1216,14 @@ func newGetSyncedDatabaseTable() *cobra.Command { var getSyncedDatabaseTableReq database.GetSyncedDatabaseTableRequest cmd.Use = "get-synced-database-table NAME" - cmd.Short = `Get a Synced Database Table.` - cmd.Long = `Get a Synced Database Table.` + cmd.Short = `*Public Preview* Get a Synced Database Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a Synced Database Table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1229,6 +1297,8 @@ func newListDatabaseCatalogs() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1306,6 +1376,8 @@ func newListDatabaseInstanceRoles() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1370,10 +1442,14 @@ func newListDatabaseInstances() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-database-instances" - cmd.Short = `List Database Instances.` - cmd.Long = `List Database Instances.` + cmd.Short = `*Public Preview* List Database Instances.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List Database Instances.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1448,6 +1524,8 @@ func newListSyncedDatabaseTables() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1520,6 +1598,8 @@ func newUpdateDatabaseCatalog() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1610,8 +1690,10 @@ func newUpdateDatabaseInstance() *cobra.Command { cmd.Flags().StringVar(&updateDatabaseInstanceReq.DatabaseInstance.UsagePolicyId, "usage-policy-id", updateDatabaseInstanceReq.DatabaseInstance.UsagePolicyId, `The desired usage policy to associate with the instance.`) cmd.Use = "update-database-instance NAME UPDATE_MASK" - cmd.Short = `Update a Database Instance.` - cmd.Long = `Update a Database Instance. + cmd.Short = `*Public Preview* Update a Database Instance.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a Database Instance. Arguments: NAME: The name of the instance. This is the unique identifier for the instance. @@ -1620,6 +1702,8 @@ func newUpdateDatabaseInstance() *cobra.Command { update_mask with an empty custom_tags map.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1703,6 +1787,8 @@ func newUpdateSyncedDatabaseTable() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/default-namespace/default-namespace.go b/cmd/workspace/default-namespace/default-namespace.go index 5ec635652f2..963d944d1f7 100755 --- a/cmd/workspace/default-namespace/default-namespace.go +++ b/cmd/workspace/default-namespace/default-namespace.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "default-namespace", - Short: `The default namespace setting API allows users to configure the default namespace for a Databricks workspace.`, - Long: `The default namespace setting API allows users to configure the default + Short: `*Public Preview* The default namespace setting API allows users to configure the default namespace for a Databricks workspace.`, + Long: `This command is in Public Preview and may change without notice. + +The default namespace setting API allows users to configure the default namespace for a Databricks workspace. Through this API, users can retrieve, set, or modify the default namespace @@ -36,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -66,8 +72,10 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete the default namespace setting.` - cmd.Long = `Delete the default namespace setting. + cmd.Short = `*Public Preview* Delete the default namespace setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete the default namespace setting. Deletes the default namespace setting for the workspace. A fresh etag needs to be provided in DELETE requests (as a query parameter). The etag can be @@ -76,6 +84,8 @@ func newDelete() *cobra.Command { request must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -124,12 +134,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the default namespace setting.` - cmd.Long = `Get the default namespace setting. + cmd.Short = `*Public Preview* Get the default namespace setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the default namespace setting. Gets the default namespace setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -179,8 +193,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the default namespace setting.` - cmd.Long = `Update the default namespace setting. + cmd.Short = `*Public Preview* Update the default namespace setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the default namespace setting. Updates the default namespace setting for the workspace. A fresh etag needs to be provided in PATCH requests (as part of the setting field). The etag can @@ -191,6 +207,8 @@ func newUpdate() *cobra.Command { must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/default-warehouse-id/default-warehouse-id.go b/cmd/workspace/default-warehouse-id/default-warehouse-id.go index 1b9a61e41ff..b22d987e747 100755 --- a/cmd/workspace/default-warehouse-id/default-warehouse-id.go +++ b/cmd/workspace/default-warehouse-id/default-warehouse-id.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -65,6 +69,8 @@ func newDelete() *cobra.Command { Reverts the Default Warehouse Id setting to its default value.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -119,6 +125,8 @@ func newGet() *cobra.Command { Gets the Default Warehouse Id setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -174,6 +182,8 @@ func newUpdate() *cobra.Command { Updates the Default Warehouse Id setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/disable-legacy-access/disable-legacy-access.go b/cmd/workspace/disable-legacy-access/disable-legacy-access.go index 317546ea068..64bf8fd34c8 100755 --- a/cmd/workspace/disable-legacy-access/disable-legacy-access.go +++ b/cmd/workspace/disable-legacy-access/disable-legacy-access.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -66,6 +70,8 @@ func newDelete() *cobra.Command { Deletes legacy access disablement status.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -120,6 +126,8 @@ func newGet() *cobra.Command { Retrieves legacy access disablement Status.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -175,6 +183,8 @@ func newUpdate() *cobra.Command { Updates legacy access disablement status.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/disable-legacy-dbfs/disable-legacy-dbfs.go b/cmd/workspace/disable-legacy-dbfs/disable-legacy-dbfs.go index 5630444ee17..a57d63a451d 100755 --- a/cmd/workspace/disable-legacy-dbfs/disable-legacy-dbfs.go +++ b/cmd/workspace/disable-legacy-dbfs/disable-legacy-dbfs.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -70,6 +74,8 @@ func newDelete() *cobra.Command { default.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -124,6 +130,8 @@ func newGet() *cobra.Command { Gets the disable legacy DBFS setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -179,6 +187,8 @@ func newUpdate() *cobra.Command { Updates the disable legacy DBFS setting for the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/enable-export-notebook/enable-export-notebook.go b/cmd/workspace/enable-export-notebook/enable-export-notebook.go index c9670c88034..870cc7c0393 100755 --- a/cmd/workspace/enable-export-notebook/enable-export-notebook.go +++ b/cmd/workspace/enable-export-notebook/enable-export-notebook.go @@ -20,12 +20,18 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "enable-export-notebook", - Short: `Controls whether users can export notebooks and files from the Workspace UI.`, - Long: `Controls whether users can export notebooks and files from the Workspace UI. + Short: `*Public Preview* Controls whether users can export notebooks and files from the Workspace UI.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether users can export notebooks and files from the Workspace UI. By default, this setting is enabled.`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetEnableExportNotebook()) cmd.AddCommand(newPatchEnableExportNotebook()) @@ -50,12 +56,16 @@ func newGetEnableExportNotebook() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "get-enable-export-notebook" - cmd.Short = `Get the Notebook and File exporting setting.` - cmd.Long = `Get the Notebook and File exporting setting. + cmd.Short = `*Public Preview* Get the Notebook and File exporting setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the Notebook and File exporting setting. Gets the Notebook and File exporting setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -99,14 +109,18 @@ func newPatchEnableExportNotebook() *cobra.Command { cmd.Flags().Var(&patchEnableExportNotebookJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "patch-enable-export-notebook" - cmd.Short = `Update the Notebook and File exporting setting.` - cmd.Long = `Update the Notebook and File exporting setting. + cmd.Short = `*Public Preview* Update the Notebook and File exporting setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the Notebook and File exporting setting. Updates the Notebook and File exporting setting. The model follows eventual consistency, which means the get after the update operation might receive stale values for some time.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/enable-notebook-table-clipboard/enable-notebook-table-clipboard.go b/cmd/workspace/enable-notebook-table-clipboard/enable-notebook-table-clipboard.go index 0ec3f7135fd..16e8d259ee8 100755 --- a/cmd/workspace/enable-notebook-table-clipboard/enable-notebook-table-clipboard.go +++ b/cmd/workspace/enable-notebook-table-clipboard/enable-notebook-table-clipboard.go @@ -20,12 +20,18 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "enable-notebook-table-clipboard", - Short: `Controls whether users can copy tabular data to the clipboard via the UI.`, - Long: `Controls whether users can copy tabular data to the clipboard via the UI. By + Short: `*Public Preview* Controls whether users can copy tabular data to the clipboard via the UI.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether users can copy tabular data to the clipboard via the UI. By default, this setting is enabled.`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetEnableNotebookTableClipboard()) cmd.AddCommand(newPatchEnableNotebookTableClipboard()) @@ -50,12 +56,16 @@ func newGetEnableNotebookTableClipboard() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "get-enable-notebook-table-clipboard" - cmd.Short = `Get the Results Table Clipboard features setting.` - cmd.Long = `Get the Results Table Clipboard features setting. + cmd.Short = `*Public Preview* Get the Results Table Clipboard features setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the Results Table Clipboard features setting. Gets the Results Table Clipboard features setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -99,14 +109,18 @@ func newPatchEnableNotebookTableClipboard() *cobra.Command { cmd.Flags().Var(&patchEnableNotebookTableClipboardJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "patch-enable-notebook-table-clipboard" - cmd.Short = `Update the Results Table Clipboard features setting.` - cmd.Long = `Update the Results Table Clipboard features setting. + cmd.Short = `*Public Preview* Update the Results Table Clipboard features setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the Results Table Clipboard features setting. Updates the Results Table Clipboard features setting. The model follows eventual consistency, which means the get after the update operation might receive stale values for some time.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/enable-results-downloading/enable-results-downloading.go b/cmd/workspace/enable-results-downloading/enable-results-downloading.go index efec5fecf82..8fe558e306f 100755 --- a/cmd/workspace/enable-results-downloading/enable-results-downloading.go +++ b/cmd/workspace/enable-results-downloading/enable-results-downloading.go @@ -20,12 +20,18 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "enable-results-downloading", - Short: `Controls whether users can download notebook results.`, - Long: `Controls whether users can download notebook results. By default, this setting + Short: `*Public Preview* Controls whether users can download notebook results.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether users can download notebook results. By default, this setting is enabled.`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetEnableResultsDownloading()) cmd.AddCommand(newPatchEnableResultsDownloading()) @@ -50,12 +56,16 @@ func newGetEnableResultsDownloading() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "get-enable-results-downloading" - cmd.Short = `Get the Notebook results download setting.` - cmd.Long = `Get the Notebook results download setting. + cmd.Short = `*Public Preview* Get the Notebook results download setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the Notebook results download setting. Gets the Notebook results download setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -99,14 +109,18 @@ func newPatchEnableResultsDownloading() *cobra.Command { cmd.Flags().Var(&patchEnableResultsDownloadingJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "patch-enable-results-downloading" - cmd.Short = `Update the Notebook results download setting.` - cmd.Long = `Update the Notebook results download setting. + cmd.Short = `*Public Preview* Update the Notebook results download setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the Notebook results download setting. Updates the Notebook results download setting. The model follows eventual consistency, which means the get after the update operation might receive stale values for some time.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/enhanced-security-monitoring/enhanced-security-monitoring.go b/cmd/workspace/enhanced-security-monitoring/enhanced-security-monitoring.go index 7582c7f363e..7beea5f4431 100755 --- a/cmd/workspace/enhanced-security-monitoring/enhanced-security-monitoring.go +++ b/cmd/workspace/enhanced-security-monitoring/enhanced-security-monitoring.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "enhanced-security-monitoring", - Short: `Controls whether enhanced security monitoring is enabled for the current workspace.`, - Long: `Controls whether enhanced security monitoring is enabled for the current + Short: `*Public Preview* Controls whether enhanced security monitoring is enabled for the current workspace.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether enhanced security monitoring is enabled for the current workspace. If the compliance security profile is enabled, this is automatically enabled. By default, it is disabled. However, if the compliance security profile is enabled, this is automatically enabled. @@ -31,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newUpdate()) @@ -60,12 +66,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the enhanced security monitoring setting.` - cmd.Long = `Get the enhanced security monitoring setting. + cmd.Short = `*Public Preview* Get the enhanced security monitoring setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the enhanced security monitoring setting. Gets the enhanced security monitoring setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -115,8 +125,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the enhanced security monitoring setting.` - cmd.Long = `Update the enhanced security monitoring setting. + cmd.Short = `*Public Preview* Update the enhanced security monitoring setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the enhanced security monitoring setting. Updates the enhanced security monitoring setting for the workspace. A fresh etag needs to be provided in PATCH requests (as part of the setting field). @@ -125,6 +137,8 @@ func newUpdate() *cobra.Command { the request must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/entity-tag-assignments/entity-tag-assignments.go b/cmd/workspace/entity-tag-assignments/entity-tag-assignments.go index b29ac06da3b..d3e98174a93 100755 --- a/cmd/workspace/entity-tag-assignments/entity-tag-assignments.go +++ b/cmd/workspace/entity-tag-assignments/entity-tag-assignments.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -87,6 +91,8 @@ func newCreate() *cobra.Command { ENTITY_TYPE: The type of the entity to which the tag is assigned.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -183,6 +189,8 @@ func newDelete() *cobra.Command { TAG_KEY: Required. The key of the tag to delete` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -243,6 +251,8 @@ func newGet() *cobra.Command { TAG_KEY: Required. The key of the tag` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -321,6 +331,8 @@ func newList() *cobra.Command { ENTITY_NAME: The fully qualified name of the entity to which the tag is assigned` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -411,6 +423,8 @@ func newUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) diff --git a/cmd/workspace/environments/environments.go b/cmd/workspace/environments/environments.go index 385467dffd4..97fc52874a7 100755 --- a/cmd/workspace/environments/environments.go +++ b/cmd/workspace/environments/environments.go @@ -35,6 +35,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateWorkspaceBaseEnvironment()) cmd.AddCommand(newDeleteWorkspaceBaseEnvironment()) @@ -102,6 +106,8 @@ func newCreateWorkspaceBaseEnvironment() *cobra.Command { DISPLAY_NAME: Human-readable display name for the workspace base environment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -213,6 +219,8 @@ func newDeleteWorkspaceBaseEnvironment() *cobra.Command { Format: workspace-base-environments/{workspace_base_environment}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -272,6 +280,8 @@ func newGetDefaultWorkspaceBaseEnvironment() *cobra.Command { default-workspace-base-environment` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -330,6 +340,8 @@ func newGetOperation() *cobra.Command { NAME: The name of the operation resource.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -388,6 +400,8 @@ func newGetWorkspaceBaseEnvironment() *cobra.Command { Format: workspace-base-environments/{workspace_base_environment}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -452,9 +466,23 @@ func newListWorkspaceBaseEnvironments() *cobra.Command { cmd.Short = `List workspace base environments.` cmd.Long = `List workspace base environments. - Lists all WorkspaceBaseEnvironments in the workspace.` + Lists all WorkspaceBaseEnvironments in the workspace. + + Databricks provides the following base environments: + + - workspace-base-environments/databricks_ai_...: includes popular AI and + deep learning packages for serverless GPU compute. + + - workspace-base-environments/databricks_ml_...: includes popular ML + packages for serverless compute. + + Databricks-provided base environments are versioned. For example, + workspace-base-environments/databricks_ml_v5 corresponds to the ML + environment built on environment version 5.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -529,6 +557,8 @@ func newRefreshWorkspaceBaseEnvironment() *cobra.Command { Format: workspace-base-environments/{workspace_base_environment}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -635,6 +665,8 @@ func newUpdateDefaultWorkspaceBaseEnvironment() *cobra.Command { explicitly — the wildcard '*' cannot be used to unset fields.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -732,6 +764,8 @@ func newUpdateWorkspaceBaseEnvironment() *cobra.Command { DISPLAY_NAME: Human-readable display name for the workspace base environment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/experiments/experiments.go b/cmd/workspace/experiments/experiments.go index 5f24b7e95bf..6875a03b588 100755 --- a/cmd/workspace/experiments/experiments.go +++ b/cmd/workspace/experiments/experiments.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateExperiment()) cmd.AddCommand(newCreateLoggedModel()) @@ -113,11 +117,15 @@ func newCreateExperiment() *cobra.Command { exists. Throws RESOURCE_ALREADY_EXISTS if an experiment with the given name exists. + Note: In some contexts, this error may be remapped to ALREADY_EXISTS. To be + safe, clients should check for both error codes. Arguments: NAME: Experiment name.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -203,6 +211,8 @@ func newCreateLoggedModel() *cobra.Command { EXPERIMENT_ID: The ID of the experiment that owns the model.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -290,6 +300,8 @@ func newCreateRun() *cobra.Command { execution.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -363,6 +375,8 @@ func newDeleteExperiment() *cobra.Command { EXPERIMENT_ID: ID of the associated experiment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -438,6 +452,8 @@ func newDeleteLoggedModel() *cobra.Command { MODEL_ID: The ID of the logged model to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -493,6 +509,8 @@ func newDeleteLoggedModelTag() *cobra.Command { TAG_KEY: The tag key.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -553,6 +571,8 @@ func newDeleteRun() *cobra.Command { RUN_ID: ID of the run to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -640,6 +660,8 @@ func newDeleteRuns() *cobra.Command { deleted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -730,6 +752,8 @@ func newDeleteTag() *cobra.Command { KEY: Name of the tag. Maximum size is 255 bytes. Must be provided.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -815,6 +839,8 @@ func newFinalizeLoggedModel() *cobra.Command { Supported values: [LOGGED_MODEL_PENDING, LOGGED_MODEL_READY, LOGGED_MODEL_UPLOAD_FAILED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -906,6 +932,8 @@ func newGetByName() *cobra.Command { EXPERIMENT_NAME: Name of the associated experiment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -963,6 +991,8 @@ func newGetExperiment() *cobra.Command { EXPERIMENT_ID: ID of the associated experiment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1035,6 +1065,8 @@ func newGetHistory() *cobra.Command { METRIC_KEY: Name of the metric.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1093,6 +1125,8 @@ func newGetLoggedModel() *cobra.Command { MODEL_ID: The ID of the logged model to retrieve.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1150,6 +1184,8 @@ func newGetPermissionLevels() *cobra.Command { EXPERIMENT_ID: The experiment for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1208,6 +1244,8 @@ func newGetPermissions() *cobra.Command { EXPERIMENT_ID: The experiment for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1272,6 +1310,8 @@ func newGetRun() *cobra.Command { RUN_ID: ID of the run to fetch. Must be provided.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1346,6 +1386,8 @@ func newListArtifacts() *cobra.Command { API](/api/workspace/files/listdirectorycontents).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1415,6 +1457,8 @@ func newListExperiments() *cobra.Command { Gets a list of all experiments.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1520,6 +1564,8 @@ func newLogBatch() *cobra.Command { * Parameter and tag values can be up to 250 characters in length` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1593,6 +1639,8 @@ func newLogInputs() *cobra.Command { RUN_ID: ID of the run to log under` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1678,6 +1726,8 @@ func newLogLoggedModelParams() *cobra.Command { MODEL_ID: The ID of the logged model to log params for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1760,6 +1810,8 @@ func newLogMetric() *cobra.Command { TIMESTAMP: Unix timestamp in milliseconds at the time metric was logged.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1858,6 +1910,8 @@ func newLogModel() *cobra.Command { Log a model to an MLflow Run.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1930,6 +1984,8 @@ func newLogOutputs() *cobra.Command { RUN_ID: The ID of the Run from which to log outputs.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2017,6 +2073,8 @@ func newLogParam() *cobra.Command { VALUE: String value of the param being logged. Maximum size is 500 bytes.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2105,6 +2163,8 @@ func newRestoreExperiment() *cobra.Command { EXPERIMENT_ID: ID of the associated experiment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2189,6 +2249,8 @@ func newRestoreRun() *cobra.Command { RUN_ID: ID of the run to restore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2276,6 +2338,8 @@ func newRestoreRuns() *cobra.Command { restored.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2377,6 +2441,8 @@ func newSearchExperiments() *cobra.Command { Searches for experiments that satisfy specified search criteria.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -2455,6 +2521,8 @@ func newSearchLoggedModels() *cobra.Command { Search for Logged Models that satisfy specified search criteria.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -2542,6 +2610,8 @@ func newSearchRuns() *cobra.Command { Search expressions can use mlflowMetric and mlflowParam keys.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -2619,6 +2689,8 @@ func newSetExperimentTag() *cobra.Command { supported.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2705,6 +2777,8 @@ func newSetLoggedModelTags() *cobra.Command { MODEL_ID: The ID of the logged model to set the tags on.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2780,6 +2854,8 @@ func newSetPermissions() *cobra.Command { EXPERIMENT_ID: The experiment for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2858,6 +2934,8 @@ func newSetTag() *cobra.Command { supported.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2943,6 +3021,8 @@ func newUpdateExperiment() *cobra.Command { EXPERIMENT_ID: ID of the associated experiment.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -3026,6 +3106,8 @@ func newUpdatePermissions() *cobra.Command { EXPERIMENT_ID: The experiment for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -3101,6 +3183,8 @@ func newUpdateRun() *cobra.Command { Updates run metadata.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/external-lineage/external-lineage.go b/cmd/workspace/external-lineage/external-lineage.go index 9c871b5defc..c724701a0d9 100755 --- a/cmd/workspace/external-lineage/external-lineage.go +++ b/cmd/workspace/external-lineage/external-lineage.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "external-lineage", - Short: `External Lineage APIs enable defining and managing lineage relationships between Databricks objects and external systems.`, - Long: `External Lineage APIs enable defining and managing lineage relationships + Short: `*Public Preview* External Lineage APIs enable defining and managing lineage relationships between Databricks objects and external systems.`, + Long: `This command is in Public Preview and may change without notice. + +External Lineage APIs enable defining and managing lineage relationships between Databricks objects and external systems. These APIs allow users to capture data flows connecting Databricks tables, models, and file paths with external metadata objects. @@ -32,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreateExternalLineageRelationship()) cmd.AddCommand(newDeleteExternalLineageRelationship()) @@ -68,8 +74,10 @@ func newCreateExternalLineageRelationship() *cobra.Command { // TODO: map via StringToStringVar: properties cmd.Use = "create-external-lineage-relationship SOURCE TARGET" - cmd.Short = `Create an external lineage relationship.` - cmd.Long = `Create an external lineage relationship. + cmd.Short = `*Public Preview* Create an external lineage relationship.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an external lineage relationship. Creates an external lineage relationship between a Databricks or external metadata object and another external metadata object. @@ -79,6 +87,8 @@ func newCreateExternalLineageRelationship() *cobra.Command { TARGET: Target object of the external lineage relationship.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -162,13 +172,17 @@ func newDeleteExternalLineageRelationship() *cobra.Command { cmd.Flags().Var(&deleteExternalLineageRelationshipJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "delete-external-lineage-relationship" - cmd.Short = `Delete an external lineage relationship.` - cmd.Long = `Delete an external lineage relationship. + cmd.Short = `*Public Preview* Delete an external lineage relationship.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an external lineage relationship. Deletes an external lineage relationship between a Databricks or external metadata object and another external metadata object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -240,13 +254,17 @@ func newListExternalLineageRelationships() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-external-lineage-relationships" - cmd.Short = `List external lineage relationships.` - cmd.Long = `List external lineage relationships. + cmd.Short = `*Public Preview* List external lineage relationships.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List external lineage relationships. Lists external lineage relationships of a Databricks object or external metadata given a supplied direction.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -313,8 +331,10 @@ func newUpdateExternalLineageRelationship() *cobra.Command { // TODO: map via StringToStringVar: properties cmd.Use = "update-external-lineage-relationship UPDATE_MASK SOURCE TARGET" - cmd.Short = `Update an external lineage relationship.` - cmd.Long = `Update an external lineage relationship. + cmd.Short = `*Public Preview* Update an external lineage relationship.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an external lineage relationship. Updates an external lineage relationship between a Databricks or external metadata object and another external metadata object. @@ -335,6 +355,8 @@ func newUpdateExternalLineageRelationship() *cobra.Command { TARGET: Target object of the external lineage relationship.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/external-locations/external-locations.go b/cmd/workspace/external-locations/external-locations.go index cd9c1b2ab0f..8eee537d08e 100755 --- a/cmd/workspace/external-locations/external-locations.go +++ b/cmd/workspace/external-locations/external-locations.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -93,6 +97,8 @@ func newCreate() *cobra.Command { CREDENTIAL_NAME: Name of the storage credential used with this location.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -180,6 +186,8 @@ func newDelete() *cobra.Command { NAME: Name of the external location.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -240,6 +248,8 @@ func newGet() *cobra.Command { NAME: Name of the external location.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -321,6 +331,8 @@ func newList() *cobra.Command { indication that the end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -399,6 +411,8 @@ func newUpdate() *cobra.Command { NAME: Name of the external location.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/external-metadata/external-metadata.go b/cmd/workspace/external-metadata/external-metadata.go index 79a9c335599..d51817902d1 100755 --- a/cmd/workspace/external-metadata/external-metadata.go +++ b/cmd/workspace/external-metadata/external-metadata.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "external-metadata", - Short: `External Metadata objects enable customers to register and manage metadata about external systems within Unity Catalog.`, - Long: `External Metadata objects enable customers to register and manage metadata + Short: `*Public Preview* External Metadata objects enable customers to register and manage metadata about external systems within Unity Catalog.`, + Long: `This command is in Public Preview and may change without notice. + +External Metadata objects enable customers to register and manage metadata about external systems within Unity Catalog. These APIs provide a standardized way to create, update, retrieve, list, and @@ -32,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreateExternalMetadata()) cmd.AddCommand(newDeleteExternalMetadata()) @@ -72,8 +78,10 @@ func newCreateExternalMetadata() *cobra.Command { cmd.Flags().StringVar(&createExternalMetadataReq.ExternalMetadata.Url, "url", createExternalMetadataReq.ExternalMetadata.Url, `URL associated with the external metadata object.`) cmd.Use = "create-external-metadata NAME SYSTEM_TYPE ENTITY_TYPE" - cmd.Short = `Create an external metadata object.` - cmd.Long = `Create an external metadata object. + cmd.Short = `*Public Preview* Create an external metadata object.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an external metadata object. Creates a new external metadata object in the parent metastore if the caller is a metastore admin or has the **CREATE_EXTERNAL_METADATA** privilege. Grants @@ -110,6 +118,8 @@ func newCreateExternalMetadata() *cobra.Command { ENTITY_TYPE: Type of entity within the external system.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -189,14 +199,18 @@ func newDeleteExternalMetadata() *cobra.Command { var deleteExternalMetadataReq catalog.DeleteExternalMetadataRequest cmd.Use = "delete-external-metadata NAME" - cmd.Short = `Delete an external metadata object.` - cmd.Long = `Delete an external metadata object. + cmd.Short = `*Public Preview* Delete an external metadata object.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an external metadata object. Deletes the external metadata object that matches the supplied name. The caller must be a metastore admin, the owner of the external metadata object, or a user that has the **MANAGE** privilege.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -244,14 +258,18 @@ func newGetExternalMetadata() *cobra.Command { var getExternalMetadataReq catalog.GetExternalMetadataRequest cmd.Use = "get-external-metadata NAME" - cmd.Short = `Get an external metadata object.` - cmd.Long = `Get an external metadata object. + cmd.Short = `*Public Preview* Get an external metadata object.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an external metadata object. Gets the specified external metadata object in a metastore. The caller must be a metastore admin, the owner of the external metadata object, or a user that has the **BROWSE** privilege.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -313,8 +331,10 @@ func newListExternalMetadata() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-external-metadata" - cmd.Short = `List external metadata objects.` - cmd.Long = `List external metadata objects. + cmd.Short = `*Public Preview* List external metadata objects.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List external metadata objects. Gets an array of external metadata objects in the metastore. If the caller is the metastore admin, all external metadata objects will be retrieved. @@ -323,6 +343,8 @@ func newListExternalMetadata() *cobra.Command { elements in the array.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -382,8 +404,10 @@ func newUpdateExternalMetadata() *cobra.Command { cmd.Flags().StringVar(&updateExternalMetadataReq.ExternalMetadata.Url, "url", updateExternalMetadataReq.ExternalMetadata.Url, `URL associated with the external metadata object.`) cmd.Use = "update-external-metadata NAME UPDATE_MASK SYSTEM_TYPE ENTITY_TYPE" - cmd.Short = `Update an external metadata object.` - cmd.Long = `Update an external metadata object. + cmd.Short = `*Public Preview* Update an external metadata object.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an external metadata object. Updates the external metadata object that matches the supplied name. The caller can only update either the owner or other metadata fields in one @@ -433,6 +457,8 @@ func newUpdateExternalMetadata() *cobra.Command { ENTITY_TYPE: Type of entity within the external system.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/feature-engineering/feature-engineering.go b/cmd/workspace/feature-engineering/feature-engineering.go index d89f65f7d4e..a9d665fc17b 100755 --- a/cmd/workspace/feature-engineering/feature-engineering.go +++ b/cmd/workspace/feature-engineering/feature-engineering.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreateFeature()) cmd.AddCommand(newCreateKafkaConfig()) @@ -89,11 +93,15 @@ func newCreateFeature() *cobra.Command { Create a Feature. Arguments: - FULL_NAME: The full three-part name (catalog, schema, name) of the feature. + FULL_NAME: The full three-part name (catalog, schema, name) of the feature. This is + the feature's resource identifier; the catalog_name, schema_name, and name + fields below are OUTPUT_ONLY decomposed views of this value. SOURCE: The data source of the feature. FUNCTION: The function by which the feature is computed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -202,6 +210,8 @@ func newCreateKafkaConfig() *cobra.Command { AUTH_CONFIG: Authentication configuration for connection to topics.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -305,6 +315,8 @@ func newCreateMaterializedFeature() *cobra.Command { FEATURE_NAME: The full name of the feature in Unity Catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -383,6 +395,8 @@ func newDeleteFeature() *cobra.Command { FULL_NAME: Name of the feature to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -441,6 +455,8 @@ func newDeleteKafkaConfig() *cobra.Command { NAME: Name of the Kafka config to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -495,6 +511,8 @@ func newDeleteMaterializedFeature() *cobra.Command { MATERIALIZED_FEATURE_ID: The ID of the materialized feature to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -551,6 +569,8 @@ func newGetFeature() *cobra.Command { FULL_NAME: Name of the feature to get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -610,6 +630,8 @@ func newGetKafkaConfig() *cobra.Command { NAME: Name of the Kafka config to get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -665,6 +687,8 @@ func newGetMaterializedFeature() *cobra.Command { MATERIALIZED_FEATURE_ID: The ID of the materialized feature.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -725,16 +749,22 @@ func newListFeatures() *cobra.Command { cmd.Flags().StringVar(&listFeaturesReq.PageToken, "page-token", listFeaturesReq.PageToken, `Pagination token.`) cmd.Flags().Lookup("page-token").Hidden = true - cmd.Use = "list-features" + cmd.Use = "list-features CATALOG_NAME SCHEMA_NAME" cmd.Short = `List features.` cmd.Long = `List features. - List Features.` + List Features. + + Arguments: + CATALOG_NAME: Name of parent catalog for features of interest. + SCHEMA_NAME: Name of parent schema relative to its parent catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { - check := root.ExactArgs(0) + check := root.ExactArgs(2) return check(cmd, args) } @@ -743,6 +773,9 @@ func newListFeatures() *cobra.Command { ctx := cmd.Context() w := cmdctx.WorkspaceClient(ctx) + listFeaturesReq.CatalogName = args[0] + listFeaturesReq.SchemaName = args[1] + response := w.FeatureEngineering.ListFeatures(ctx, listFeaturesReq) if listFeaturesLimit < 0 { return fmt.Errorf("--limit must be a non-negative integer, got %d", listFeaturesLimit) @@ -802,6 +835,8 @@ func newListKafkaConfigs() *cobra.Command { config can delete it.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -869,6 +904,8 @@ func newListMaterializedFeatures() *cobra.Command { cmd.Long = `List materialized features.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -936,12 +973,16 @@ func newUpdateFeature() *cobra.Command { Update a Feature. Arguments: - FULL_NAME: The full three-part name (catalog, schema, name) of the feature. + FULL_NAME: The full three-part name (catalog, schema, name) of the feature. This is + the feature's resource identifier; the catalog_name, schema_name, and name + fields below are OUTPUT_ONLY decomposed views of this value. UPDATE_MASK: The list of fields to update. SOURCE: The data source of the feature. FUNCTION: The function by which the feature is computed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1050,6 +1091,8 @@ func newUpdateKafkaConfig() *cobra.Command { AUTH_CONFIG: Authentication configuration for connection to topics.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1160,6 +1203,8 @@ func newUpdateMaterializedFeature() *cobra.Command { FEATURE_NAME: The full name of the feature in Unity Catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/feature-store/feature-store.go b/cmd/workspace/feature-store/feature-store.go index 4b1a7a0150c..dbd4e581a59 100755 --- a/cmd/workspace/feature-store/feature-store.go +++ b/cmd/workspace/feature-store/feature-store.go @@ -35,6 +35,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreateOnlineStore()) cmd.AddCommand(newDeleteOnlineStore()) @@ -84,6 +88,8 @@ func newCreateOnlineStore() *cobra.Command { "CU_8".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -163,6 +169,8 @@ func newDeleteOnlineStore() *cobra.Command { NAME: Name of the online store to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -219,6 +227,8 @@ func newDeleteOnlineTable() *cobra.Command { ONLINE_TABLE_NAME: The full three-part (catalog, schema, table) name of the online table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -273,6 +283,8 @@ func newGetOnlineStore() *cobra.Command { NAME: Name of the online store to get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -338,6 +350,8 @@ func newListOnlineStores() *cobra.Command { cmd.Long = `List Online Feature Stores.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -397,6 +411,8 @@ func newPublishTable() *cobra.Command { SOURCE_TABLE_NAME: The full three-part (catalog, schema, table) name of the source table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -477,6 +493,8 @@ func newUpdateOnlineStore() *cobra.Command { "CU_8".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/forecasting/forecasting.go b/cmd/workspace/forecasting/forecasting.go index 7802776a93d..c284a717bf6 100755 --- a/cmd/workspace/forecasting/forecasting.go +++ b/cmd/workspace/forecasting/forecasting.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreateExperiment()) cmd.AddCommand(newGetExperiment()) @@ -103,6 +107,8 @@ func newCreateExperiment() *cobra.Command { the model should forecast.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -209,6 +215,8 @@ func newGetExperiment() *cobra.Command { EXPERIMENT_ID: The unique ID of a forecasting experiment` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/functions/functions.go b/cmd/workspace/functions/functions.go index 7c02b25fb85..fc70bd717ca 100755 --- a/cmd/workspace/functions/functions.go +++ b/cmd/workspace/functions/functions.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -76,6 +80,8 @@ func newCreate() *cobra.Command { and **CREATE_FUNCTION** on the function's parent schema` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -149,6 +155,8 @@ func newDelete() *cobra.Command { __catalog_name__.__schema_name__.__function__name__) .` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -226,6 +234,8 @@ func newGet() *cobra.Command { __catalog_name__.__schema_name__.__function__name__).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -325,6 +335,8 @@ func newList() *cobra.Command { SCHEMA_NAME: Parent schema of functions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -399,6 +411,8 @@ func newUpdate() *cobra.Command { __catalog_name__.__schema_name__.__function__name__).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/genie/genie.go b/cmd/workspace/genie/genie.go index 08879af3ab5..0376b717912 100755 --- a/cmd/workspace/genie/genie.go +++ b/cmd/workspace/genie/genie.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateMessage()) cmd.AddCommand(newCreateMessageComment()) @@ -106,6 +110,8 @@ func newCreateMessage() *cobra.Command { CONTENT: User message content.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -192,8 +198,10 @@ func newCreateMessageComment() *cobra.Command { cmd.Flags().Var(&createMessageCommentJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create-message-comment SPACE_ID CONVERSATION_ID MESSAGE_ID CONTENT" - cmd.Short = `Create message comment.` - cmd.Long = `Create message comment. + cmd.Short = `*Public Preview* Create message comment.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create message comment. Create a comment on a conversation message. @@ -204,6 +212,8 @@ func newCreateMessageComment() *cobra.Command { CONTENT: Comment text content.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -297,6 +307,8 @@ func newCreateSpace() *cobra.Command { components.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -379,6 +391,8 @@ func newDeleteConversation() *cobra.Command { CONVERSATION_ID: The ID of the conversation to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -438,6 +452,8 @@ func newDeleteConversationMessage() *cobra.Command { MESSAGE_ID: The ID associated with the message to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -500,6 +516,8 @@ func newExecuteMessageAttachmentQuery() *cobra.Command { ATTACHMENT_ID: Attachment ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -566,6 +584,8 @@ func newExecuteMessageQuery() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -650,6 +670,8 @@ func newGenerateDownloadFullQueryResult() *cobra.Command { ATTACHMENT_ID: Attachment ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -706,8 +728,10 @@ func newGenieCreateEvalRun() *cobra.Command { // TODO: array: benchmark_question_ids cmd.Use = "genie-create-eval-run SPACE_ID" - cmd.Short = `Create eval run for benchmarks.` - cmd.Long = `Create eval run for benchmarks. + cmd.Short = `*Beta* Create eval run for benchmarks.` + cmd.Long = `This command is in Beta and may change without notice. + +Create eval run for benchmarks. Create and run evaluations for multiple benchmark questions in a Genie space. @@ -716,6 +740,8 @@ func newGenieCreateEvalRun() *cobra.Command { executed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -776,8 +802,10 @@ func newGenieGetEvalResultDetails() *cobra.Command { var genieGetEvalResultDetailsReq dashboards.GenieGetEvalResultDetailsRequest cmd.Use = "genie-get-eval-result-details SPACE_ID EVAL_RUN_ID RESULT_ID" - cmd.Short = `Get benchmark evaluation result details.` - cmd.Long = `Get benchmark evaluation result details. + cmd.Short = `*Beta* Get benchmark evaluation result details.` + cmd.Long = `This command is in Beta and may change without notice. + +Get benchmark evaluation result details. Get details for evaluation results. @@ -788,6 +816,8 @@ func newGenieGetEvalResultDetails() *cobra.Command { RESULT_ID: The unique identifier for the evaluation result.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -838,8 +868,10 @@ func newGenieGetEvalRun() *cobra.Command { var genieGetEvalRunReq dashboards.GenieGetEvalRunRequest cmd.Use = "genie-get-eval-run SPACE_ID EVAL_RUN_ID" - cmd.Short = `Get benchmark evaluation run.` - cmd.Long = `Get benchmark evaluation run. + cmd.Short = `*Beta* Get benchmark evaluation run.` + cmd.Long = `This command is in Beta and may change without notice. + +Get benchmark evaluation run. Get evaluation run details. @@ -849,6 +881,8 @@ func newGenieGetEvalRun() *cobra.Command { EVAL_RUN_ID: ` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -901,8 +935,10 @@ func newGenieListEvalResults() *cobra.Command { cmd.Flags().StringVar(&genieListEvalResultsReq.PageToken, "page-token", genieListEvalResultsReq.PageToken, `Opaque token to retrieve the next page of results.`) cmd.Use = "genie-list-eval-results SPACE_ID EVAL_RUN_ID" - cmd.Short = `List benchmark evaluation results.` - cmd.Long = `List benchmark evaluation results. + cmd.Short = `*Beta* List benchmark evaluation results.` + cmd.Long = `This command is in Beta and may change without notice. + +List benchmark evaluation results. List evaluation results for a specific evaluation run. @@ -912,6 +948,8 @@ func newGenieListEvalResults() *cobra.Command { EVAL_RUN_ID: The unique identifier for the evaluation run.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -964,8 +1002,10 @@ func newGenieListEvalRuns() *cobra.Command { cmd.Flags().StringVar(&genieListEvalRunsReq.PageToken, "page-token", genieListEvalRunsReq.PageToken, `Token to get the next page of results.`) cmd.Use = "genie-list-eval-runs SPACE_ID" - cmd.Short = `List all evaluation runs in the space.` - cmd.Long = `List all evaluation runs in the space. + cmd.Short = `*Beta* List all evaluation runs in the space.` + cmd.Long = `This command is in Beta and may change without notice. + +List all evaluation runs in the space. Lists all evaluation runs in a space. @@ -974,6 +1014,8 @@ func newGenieListEvalRuns() *cobra.Command { located.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1060,6 +1102,8 @@ func newGetDownloadFullQueryResult() *cobra.Command { DOWNLOAD_ID_SIGNATURE: JWT signature for the download_id to ensure secure access to query results` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(6) @@ -1126,6 +1170,8 @@ func newGetMessage() *cobra.Command { conversation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -1190,6 +1236,8 @@ func newGetMessageAttachmentQueryResult() *cobra.Command { ATTACHMENT_ID: Attachment ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -1256,6 +1304,8 @@ func newGetMessageQueryResult() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -1322,6 +1372,8 @@ func newGetMessageQueryResultByAttachment() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -1384,6 +1436,8 @@ func newGetSpace() *cobra.Command { SPACE_ID: The ID associated with the Genie space` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1435,8 +1489,10 @@ func newListConversationComments() *cobra.Command { cmd.Flags().StringVar(&listConversationCommentsReq.PageToken, "page-token", listConversationCommentsReq.PageToken, `Pagination token for getting the next page of results.`) cmd.Use = "list-conversation-comments SPACE_ID CONVERSATION_ID" - cmd.Short = `List conversation comments.` - cmd.Long = `List conversation comments. + cmd.Short = `*Public Preview* List conversation comments.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List conversation comments. List all comments across all messages in a conversation. @@ -1445,6 +1501,8 @@ func newListConversationComments() *cobra.Command { CONVERSATION_ID: The ID associated with the conversation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1507,6 +1565,8 @@ func newListConversationMessages() *cobra.Command { CONVERSATION_ID: The ID of the conversation to list messages from` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1569,6 +1629,8 @@ func newListConversations() *cobra.Command { SPACE_ID: The ID of the Genie space to retrieve conversations from.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1620,8 +1682,10 @@ func newListMessageComments() *cobra.Command { cmd.Flags().StringVar(&listMessageCommentsReq.PageToken, "page-token", listMessageCommentsReq.PageToken, `Pagination token for getting the next page of results.`) cmd.Use = "list-message-comments SPACE_ID CONVERSATION_ID MESSAGE_ID" - cmd.Short = `List message comments.` - cmd.Long = `List message comments. + cmd.Short = `*Public Preview* List message comments.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List message comments. List comments on a specific conversation message. @@ -1631,6 +1695,8 @@ func newListMessageComments() *cobra.Command { MESSAGE_ID: The ID associated with the message.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -1690,6 +1756,8 @@ func newListSpaces() *cobra.Command { Get list of Genie Spaces.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1754,6 +1822,8 @@ func newSendMessageFeedback() *cobra.Command { Supported values: [NEGATIVE, NONE, POSITIVE]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1849,6 +1919,8 @@ func newStartConversation() *cobra.Command { CONTENT: The text of the message that starts the conversation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1940,6 +2012,8 @@ func newTrashSpace() *cobra.Command { SPACE_ID: The ID associated with the Genie space to be sent to the trash.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1990,6 +2064,7 @@ func newUpdateSpace() *cobra.Command { cmd.Flags().Var(&updateSpaceJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Flags().StringVar(&updateSpaceReq.Description, "description", updateSpaceReq.Description, `Optional description.`) + cmd.Flags().StringVar(&updateSpaceReq.Etag, "etag", updateSpaceReq.Etag, `ETag returned by a previous GET or UPDATE.`) cmd.Flags().StringVar(&updateSpaceReq.SerializedSpace, "serialized-space", updateSpaceReq.SerializedSpace, `The contents of the Genie Space in serialized string form (full replacement).`) cmd.Flags().StringVar(&updateSpaceReq.Title, "title", updateSpaceReq.Title, `Optional title override.`) cmd.Flags().StringVar(&updateSpaceReq.WarehouseId, "warehouse-id", updateSpaceReq.WarehouseId, `Optional warehouse override.`) @@ -2004,6 +2079,8 @@ func newUpdateSpace() *cobra.Command { SPACE_ID: Genie space ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/git-credentials/git-credentials.go b/cmd/workspace/git-credentials/git-credentials.go index b139272f0f9..9b5618b501d 100755 --- a/cmd/workspace/git-credentials/git-credentials.go +++ b/cmd/workspace/git-credentials/git-credentials.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -79,11 +83,15 @@ func newCreate() *cobra.Command { Arguments: GIT_PROVIDER: Git provider. This field is case-insensitive. The available Git providers - are gitHub, bitbucketCloud, gitLab, azureDevOpsServices, - gitHubEnterprise, bitbucketServer, gitLabEnterpriseEdition and - awsCodeCommit.` + are gitHub, bitbucketCloud, gitLab, azureDevOpsServices (Azure + DevOps Services, including Microsoft Entra ID authentication), + gitHubEnterprise, bitbucketServer (Bitbucket Data Center), + gitLabEnterpriseEdition (GitLab Self-Managed), and awsCodeCommit + (deprecated by AWS, not accepting new customers).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -164,6 +172,8 @@ func newDelete() *cobra.Command { CREDENTIAL_ID: The ID for the corresponding credential to access.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -237,6 +247,8 @@ func newGet() *cobra.Command { CREDENTIAL_ID: The ID for the corresponding credential to access.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -317,6 +329,8 @@ func newList() *cobra.Command { Lists the calling user's Git credentials.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -384,11 +398,15 @@ func newUpdate() *cobra.Command { Arguments: CREDENTIAL_ID: The ID for the corresponding credential to access. GIT_PROVIDER: Git provider. This field is case-insensitive. The available Git providers - are gitHub, bitbucketCloud, gitLab, azureDevOpsServices, - gitHubEnterprise, bitbucketServer, gitLabEnterpriseEdition and - awsCodeCommit.` + are gitHub, bitbucketCloud, gitLab, azureDevOpsServices (Azure + DevOps Services, including Microsoft Entra ID authentication), + gitHubEnterprise, bitbucketServer (Bitbucket Data Center), + gitLabEnterpriseEdition (GitLab Self-Managed), and awsCodeCommit + (deprecated by AWS, not accepting new customers).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/global-init-scripts/global-init-scripts.go b/cmd/workspace/global-init-scripts/global-init-scripts.go index c5a10c1e479..3097c739cf1 100755 --- a/cmd/workspace/global-init-scripts/global-init-scripts.go +++ b/cmd/workspace/global-init-scripts/global-init-scripts.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -80,6 +84,8 @@ func newCreate() *cobra.Command { SCRIPT: The Base64-encoded content of the script.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -161,6 +167,8 @@ func newDelete() *cobra.Command { SCRIPT_ID: The ID of the global init script.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -229,6 +237,8 @@ func newGet() *cobra.Command { SCRIPT_ID: The ID of the global init script.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -304,6 +314,8 @@ func newList() *cobra.Command { script](:method:globalinitscripts/get) operation.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -365,6 +377,8 @@ func newUpdate() *cobra.Command { SCRIPT: The Base64-encoded content of the script.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/grants/grants.go b/cmd/workspace/grants/grants.go index a92c3edfbd3..b7d40343867 100755 --- a/cmd/workspace/grants/grants.go +++ b/cmd/workspace/grants/grants.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newGetEffective()) @@ -84,6 +88,8 @@ func newGet() *cobra.Command { FULL_NAME: Full name of securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -156,6 +162,8 @@ func newGetEffective() *cobra.Command { FULL_NAME: Full name of securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -220,6 +228,8 @@ func newUpdate() *cobra.Command { FULL_NAME: Full name of securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/groups-v2/groups-v2.go b/cmd/workspace/groups-v2/groups-v2.go index bc51d1a4927..64b5439cb7a 100755 --- a/cmd/workspace/groups-v2/groups-v2.go +++ b/cmd/workspace/groups-v2/groups-v2.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "groups-v2", - Short: `Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects.`, - Long: `Groups simplify identity management, making it easier to assign access to + Short: `*Public Preview* Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects.`, + Long: `This command is in Public Preview and may change without notice. + +Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects. It is best practice to assign access to workspaces and access-control policies @@ -32,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -76,13 +82,17 @@ func newCreate() *cobra.Command { // TODO: array: schemas cmd.Use = "create" - cmd.Short = `Create a new group.` - cmd.Long = `Create a new group. + cmd.Short = `*Public Preview* Create a new group.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a new group. Creates a group in the Databricks workspace with a unique name, using the supplied group details.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -142,8 +152,10 @@ func newDelete() *cobra.Command { var deleteReq iam.DeleteGroupRequest cmd.Use = "delete ID" - cmd.Short = `Delete a group.` - cmd.Long = `Delete a group. + cmd.Short = `*Public Preview* Delete a group.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a group. Deletes a group from the Databricks workspace. @@ -151,6 +163,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a group in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -198,8 +212,10 @@ func newGet() *cobra.Command { var getReq iam.GetGroupRequest cmd.Use = "get ID" - cmd.Short = `Get group details.` - cmd.Long = `Get group details. + cmd.Short = `*Public Preview* Get group details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get group details. Gets the information for a specific group in the Databricks workspace. @@ -207,6 +223,8 @@ func newGet() *cobra.Command { ID: Unique ID for a group in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -274,12 +292,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" - cmd.Short = `List group details.` - cmd.Long = `List group details. + cmd.Short = `*Public Preview* List group details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List group details. Gets all details of the groups associated with the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -335,8 +357,10 @@ func newPatch() *cobra.Command { // TODO: array: schemas cmd.Use = "patch ID" - cmd.Short = `Update group details.` - cmd.Long = `Update group details. + cmd.Short = `*Public Preview* Update group details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update group details. Partially updates the details of a group. @@ -344,6 +368,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -415,8 +441,10 @@ func newUpdate() *cobra.Command { // TODO: array: schemas cmd.Use = "update ID" - cmd.Short = `Replace a group.` - cmd.Long = `Replace a group. + cmd.Short = `*Public Preview* Replace a group.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Replace a group. Updates the details of a group by replacing the entire group entity. @@ -424,6 +452,8 @@ func newUpdate() *cobra.Command { ID: Databricks group ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/groups.go b/cmd/workspace/groups.go index 8dd096ee630..985b4815695 100644 --- a/cmd/workspace/groups.go +++ b/cmd/workspace/groups.go @@ -108,5 +108,9 @@ func Groups() []cobra.Group { ID: "environments", Title: "Environments", }, + { + ID: "bundle", + Title: "Bundle", + }, } } diff --git a/cmd/workspace/instance-pools/instance-pools.go b/cmd/workspace/instance-pools/instance-pools.go index e1f69f62a88..9af0ac7714a 100755 --- a/cmd/workspace/instance-pools/instance-pools.go +++ b/cmd/workspace/instance-pools/instance-pools.go @@ -43,6 +43,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -110,6 +114,8 @@ func newCreate() *cobra.Command { :method:clusters/listNodeTypes API call.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -195,6 +201,8 @@ func newDelete() *cobra.Command { INSTANCE_POOL_ID: The instance pool to be terminated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -304,6 +312,8 @@ func newEdit() *cobra.Command { :method:clusters/listNodeTypes API call.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -387,6 +397,8 @@ func newGet() *cobra.Command { INSTANCE_POOL_ID: The canonical unique identifier for the instance pool.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -456,6 +468,8 @@ func newGetPermissionLevels() *cobra.Command { INSTANCE_POOL_ID: The instance pool for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -526,6 +540,8 @@ func newGetPermissions() *cobra.Command { INSTANCE_POOL_ID: The instance pool for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -598,6 +614,8 @@ func newList() *cobra.Command { Gets a list of instance pools with their statistics.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -657,6 +675,8 @@ func newSetPermissions() *cobra.Command { INSTANCE_POOL_ID: The instance pool for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -744,6 +764,8 @@ func newUpdatePermissions() *cobra.Command { INSTANCE_POOL_ID: The instance pool for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/instance-profiles/instance-profiles.go b/cmd/workspace/instance-profiles/instance-profiles.go index 190ea50fb12..d16a69d0c9d 100755 --- a/cmd/workspace/instance-profiles/instance-profiles.go +++ b/cmd/workspace/instance-profiles/instance-profiles.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newAdd()) cmd.AddCommand(newEdit()) @@ -80,6 +84,8 @@ func newAdd() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -178,6 +184,8 @@ func newEdit() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -260,6 +268,8 @@ func newList() *cobra.Command { This API is available to all users.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -318,6 +328,8 @@ func newRemove() *cobra.Command { INSTANCE_PROFILE_ARN: The ARN of the instance profile to remove. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/ip-access-lists/ip-access-lists.go b/cmd/workspace/ip-access-lists/ip-access-lists.go index 2dfec2fb473..0fd26430b67 100755 --- a/cmd/workspace/ip-access-lists/ip-access-lists.go +++ b/cmd/workspace/ip-access-lists/ip-access-lists.go @@ -46,6 +46,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -108,6 +112,8 @@ func newCreate() *cobra.Command { Supported values: [ALLOW, BLOCK]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -193,6 +199,8 @@ func newDelete() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -261,6 +269,8 @@ func newGet() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -333,6 +343,8 @@ func newList() *cobra.Command { Gets all IP access lists for the specified workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -405,6 +417,8 @@ func newReplace() *cobra.Command { ENABLED: Specifies whether this IP access list is enabled.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -520,6 +534,8 @@ func newUpdate() *cobra.Command { IP_ACCESS_LIST_ID: The ID for the corresponding IP access list` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/jobs/jobs.go b/cmd/workspace/jobs/jobs.go index 20b1ca221cf..be26fc586d0 100755 --- a/cmd/workspace/jobs/jobs.go +++ b/cmd/workspace/jobs/jobs.go @@ -44,6 +44,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCancelAllRuns()) cmd.AddCommand(newCancelRun()) @@ -102,6 +106,8 @@ func newCancelAllRuns() *cobra.Command { doesn't prevent new runs from being started.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -179,6 +185,8 @@ func newCancelRun() *cobra.Command { RUN_ID: This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -292,6 +300,8 @@ func newCreate() *cobra.Command { cmd.Long = `Create a new job.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -360,6 +370,8 @@ func newDelete() *cobra.Command { JOB_ID: The canonical identifier of the job to delete. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -459,6 +471,8 @@ func newDeleteRun() *cobra.Command { RUN_ID: ID of the run to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -557,6 +571,8 @@ func newExportRun() *cobra.Command { RUN_ID: The canonical identifier for the run. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -619,6 +635,7 @@ func newGet() *cobra.Command { var getReq jobs.GetJobRequest + cmd.Flags().BoolVar(&getReq.IncludeTriggerState, "include-trigger-state", getReq.IncludeTriggerState, `Flag that indicates that trigger state should be included in the response.`) cmd.Flags().StringVar(&getReq.PageToken, "page-token", getReq.PageToken, `Use next_page_token returned from the previous GetJob response to request the next page of the job's array properties.`) cmd.Use = "get JOB_ID" @@ -641,6 +658,8 @@ func newGet() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -713,6 +732,8 @@ func newGetPermissionLevels() *cobra.Command { JOB_ID: The job for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -783,6 +804,8 @@ func newGetPermissions() *cobra.Command { JOB_ID: The job for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -866,6 +889,8 @@ func newGetRun() *cobra.Command { This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -947,6 +972,8 @@ func newGetRunOutput() *cobra.Command { RUN_ID: The canonical identifier for the run.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1033,6 +1060,8 @@ func newList() *cobra.Command { Retrieves a list of jobs.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1110,6 +1139,8 @@ func newListRuns() *cobra.Command { List runs in descending order by start time.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1194,6 +1225,8 @@ func newRepairRun() *cobra.Command { RUN_ID: The job run ID of the run to repair. The run must not be in progress.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1310,6 +1343,8 @@ func newReset() *cobra.Command { endpoint](:method:jobs/update) to update job settings partially.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1397,6 +1432,8 @@ func newRunNow() *cobra.Command { JOB_ID: The ID of the job to be executed` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1519,6 +1556,8 @@ func newSetPermissions() *cobra.Command { JOB_ID: The job for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1632,6 +1671,8 @@ func newSubmit() *cobra.Command { POST /jobs/run-now endpoints to create and run a saved job.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1725,6 +1766,8 @@ func newUpdate() *cobra.Command { JOB_ID: The canonical identifier of the job to update. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1827,6 +1870,8 @@ func newUpdatePermissions() *cobra.Command { JOB_ID: The job for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/knowledge-assistants/knowledge-assistants.go b/cmd/workspace/knowledge-assistants/knowledge-assistants.go index f72403a61ab..299571c4ae5 100755 --- a/cmd/workspace/knowledge-assistants/knowledge-assistants.go +++ b/cmd/workspace/knowledge-assistants/knowledge-assistants.go @@ -21,25 +21,40 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "knowledge-assistants", - Short: `Manage Knowledge Assistants and related resources.`, - Long: `Manage Knowledge Assistants and related resources.`, + Use: "knowledge-assistants", + Short: `*Beta* Manage Knowledge Assistants and related resources.`, + Long: `This command is in Beta and may change without notice. + +Manage Knowledge Assistants and related resources.`, GroupID: "agentbricks", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods + cmd.AddCommand(newCreateExample()) cmd.AddCommand(newCreateKnowledgeAssistant()) cmd.AddCommand(newCreateKnowledgeSource()) + cmd.AddCommand(newDeleteExample()) cmd.AddCommand(newDeleteKnowledgeAssistant()) cmd.AddCommand(newDeleteKnowledgeSource()) + cmd.AddCommand(newGetExample()) cmd.AddCommand(newGetKnowledgeAssistant()) cmd.AddCommand(newGetKnowledgeSource()) + cmd.AddCommand(newGetPermissionLevels()) + cmd.AddCommand(newGetPermissions()) + cmd.AddCommand(newListExamples()) cmd.AddCommand(newListKnowledgeAssistants()) cmd.AddCommand(newListKnowledgeSources()) + cmd.AddCommand(newSetPermissions()) cmd.AddCommand(newSyncKnowledgeSources()) + cmd.AddCommand(newUpdateExample()) cmd.AddCommand(newUpdateKnowledgeAssistant()) cmd.AddCommand(newUpdateKnowledgeSource()) + cmd.AddCommand(newUpdatePermissions()) // Apply optional overrides to this command. for _, fn := range cmdOverrides { @@ -49,6 +64,98 @@ func New() *cobra.Command { return cmd } +// start create-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createExampleOverrides []func( + *cobra.Command, + *knowledgeassistants.CreateExampleRequest, +) + +func newCreateExample() *cobra.Command { + cmd := &cobra.Command{} + + var createExampleReq knowledgeassistants.CreateExampleRequest + createExampleReq.Example = knowledgeassistants.Example{} + var createExampleJson flags.JsonFlag + + cmd.Flags().Var(&createExampleJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: guidelines + cmd.Flags().StringVar(&createExampleReq.Example.Name, "name", createExampleReq.Example.Name, `Full resource name: knowledge-assistants/{knowledge_assistant_id}/examples/{example_id}.`) + + cmd.Use = "create-example PARENT QUESTION" + cmd.Short = `*Beta* Create an example for a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Create an example for a Knowledge Assistant. + + Creates an example for a Knowledge Assistant. + + Arguments: + PARENT: Parent resource where this example will be created. Format: + knowledge-assistants/{knowledge_assistant_id} + QUESTION: The example question.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(1)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT as positional arguments. Provide 'question' in your JSON input") + } + return nil + } + check := root.ExactArgs(2) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createExampleJson.Unmarshal(&createExampleReq.Example) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createExampleReq.Parent = args[0] + if !cmd.Flags().Changed("json") { + createExampleReq.Example.Question = args[1] + } + + response, err := w.KnowledgeAssistants.CreateExample(ctx, createExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createExampleOverrides { + fn(cmd, &createExampleReq) + } + + return cmd +} + // start create-knowledge-assistant command // Slice with functions to override default command behavior. @@ -71,8 +178,10 @@ func newCreateKnowledgeAssistant() *cobra.Command { cmd.Flags().StringVar(&createKnowledgeAssistantReq.KnowledgeAssistant.Name, "name", createKnowledgeAssistantReq.KnowledgeAssistant.Name, `The resource name of the Knowledge Assistant.`) cmd.Use = "create-knowledge-assistant DISPLAY_NAME DESCRIPTION" - cmd.Short = `Create a Knowledge Assistant.` - cmd.Long = `Create a Knowledge Assistant. + cmd.Short = `*Beta* Create a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Knowledge Assistant. Creates a Knowledge Assistant. @@ -85,6 +194,8 @@ func newCreateKnowledgeAssistant() *cobra.Command { optional unless included in update_mask.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -166,8 +277,10 @@ func newCreateKnowledgeSource() *cobra.Command { cmd.Flags().StringVar(&createKnowledgeSourceReq.KnowledgeSource.Name, "name", createKnowledgeSourceReq.KnowledgeSource.Name, `Full resource name: knowledge-assistants/{knowledge_assistant_id}/knowledge-sources/{knowledge_source_id}.`) cmd.Use = "create-knowledge-source PARENT DISPLAY_NAME DESCRIPTION SOURCE_TYPE" - cmd.Short = `Create a Knowledge Source.` - cmd.Long = `Create a Knowledge Source. + cmd.Short = `*Beta* Create a Knowledge Source.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Knowledge Source. Creates a Knowledge Source under a Knowledge Assistant. @@ -185,6 +298,8 @@ func newCreateKnowledgeSource() *cobra.Command { is ignored.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -246,6 +361,67 @@ func newCreateKnowledgeSource() *cobra.Command { return cmd } +// start delete-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteExampleOverrides []func( + *cobra.Command, + *knowledgeassistants.DeleteExampleRequest, +) + +func newDeleteExample() *cobra.Command { + cmd := &cobra.Command{} + + var deleteExampleReq knowledgeassistants.DeleteExampleRequest + + cmd.Use = "delete-example NAME" + cmd.Short = `*Beta* Delete an example from a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete an example from a Knowledge Assistant. + + Deletes an example from a Knowledge Assistant. + + Arguments: + NAME: The resource name of the example to delete. Format: + knowledge-assistants/{knowledge_assistant_id}/examples/{example_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteExampleReq.Name = args[0] + + err = w.KnowledgeAssistants.DeleteExample(ctx, deleteExampleReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteExampleOverrides { + fn(cmd, &deleteExampleReq) + } + + return cmd +} + // start delete-knowledge-assistant command // Slice with functions to override default command behavior. @@ -261,8 +437,10 @@ func newDeleteKnowledgeAssistant() *cobra.Command { var deleteKnowledgeAssistantReq knowledgeassistants.DeleteKnowledgeAssistantRequest cmd.Use = "delete-knowledge-assistant NAME" - cmd.Short = `Delete a Knowledge Assistant.` - cmd.Long = `Delete a Knowledge Assistant. + cmd.Short = `*Beta* Delete a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Knowledge Assistant. Deletes a Knowledge Assistant. @@ -271,6 +449,8 @@ func newDeleteKnowledgeAssistant() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -318,8 +498,10 @@ func newDeleteKnowledgeSource() *cobra.Command { var deleteKnowledgeSourceReq knowledgeassistants.DeleteKnowledgeSourceRequest cmd.Use = "delete-knowledge-source NAME" - cmd.Short = `Delete a Knowledge Source.` - cmd.Long = `Delete a Knowledge Source. + cmd.Short = `*Beta* Delete a Knowledge Source.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Knowledge Source. Deletes a Knowledge Source. @@ -328,6 +510,8 @@ func newDeleteKnowledgeSource() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}/knowledge-sources/{knowledge_source_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -360,6 +544,68 @@ func newDeleteKnowledgeSource() *cobra.Command { return cmd } +// start get-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getExampleOverrides []func( + *cobra.Command, + *knowledgeassistants.GetExampleRequest, +) + +func newGetExample() *cobra.Command { + cmd := &cobra.Command{} + + var getExampleReq knowledgeassistants.GetExampleRequest + + cmd.Use = "get-example NAME" + cmd.Short = `*Beta* Get an example from a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an example from a Knowledge Assistant. + + Gets an example from a Knowledge Assistant. + + Arguments: + NAME: The resource name of the example. Format: + knowledge-assistants/{knowledge_assistant_id}/examples/{example_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getExampleReq.Name = args[0] + + response, err := w.KnowledgeAssistants.GetExample(ctx, getExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getExampleOverrides { + fn(cmd, &getExampleReq) + } + + return cmd +} + // start get-knowledge-assistant command // Slice with functions to override default command behavior. @@ -375,8 +621,10 @@ func newGetKnowledgeAssistant() *cobra.Command { var getKnowledgeAssistantReq knowledgeassistants.GetKnowledgeAssistantRequest cmd.Use = "get-knowledge-assistant NAME" - cmd.Short = `Get a Knowledge Assistant.` - cmd.Long = `Get a Knowledge Assistant. + cmd.Short = `*Beta* Get a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Knowledge Assistant. Gets a Knowledge Assistant. @@ -385,6 +633,8 @@ func newGetKnowledgeAssistant() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -433,8 +683,10 @@ func newGetKnowledgeSource() *cobra.Command { var getKnowledgeSourceReq knowledgeassistants.GetKnowledgeSourceRequest cmd.Use = "get-knowledge-source NAME" - cmd.Short = `Get a Knowledge Source.` - cmd.Long = `Get a Knowledge Source. + cmd.Short = `*Beta* Get a Knowledge Source.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Knowledge Source. Gets a Knowledge Source. @@ -443,6 +695,8 @@ func newGetKnowledgeSource() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}/knowledge-sources/{knowledge_source_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -476,6 +730,207 @@ func newGetKnowledgeSource() *cobra.Command { return cmd } +// start get-permission-levels command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionLevelsOverrides []func( + *cobra.Command, + *knowledgeassistants.GetKnowledgeAssistantPermissionLevelsRequest, +) + +func newGetPermissionLevels() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionLevelsReq knowledgeassistants.GetKnowledgeAssistantPermissionLevelsRequest + + cmd.Use = "get-permission-levels KNOWLEDGE_ASSISTANT_ID" + cmd.Short = `*Beta* Get knowledge assistant permission levels.` + cmd.Long = `This command is in Beta and may change without notice. + +Get knowledge assistant permission levels. + + Gets the permission levels that a user can have on an object. + + Arguments: + KNOWLEDGE_ASSISTANT_ID: The knowledge assistant for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionLevelsReq.KnowledgeAssistantId = args[0] + + response, err := w.KnowledgeAssistants.GetPermissionLevels(ctx, getPermissionLevelsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionLevelsOverrides { + fn(cmd, &getPermissionLevelsReq) + } + + return cmd +} + +// start get-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionsOverrides []func( + *cobra.Command, + *knowledgeassistants.GetKnowledgeAssistantPermissionsRequest, +) + +func newGetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionsReq knowledgeassistants.GetKnowledgeAssistantPermissionsRequest + + cmd.Use = "get-permissions KNOWLEDGE_ASSISTANT_ID" + cmd.Short = `*Beta* Get knowledge assistant permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Get knowledge assistant permissions. + + Gets the permissions of a knowledge assistant. Knowledge assistants can + inherit permissions from their root object. + + Arguments: + KNOWLEDGE_ASSISTANT_ID: The knowledge assistant for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionsReq.KnowledgeAssistantId = args[0] + + response, err := w.KnowledgeAssistants.GetPermissions(ctx, getPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionsOverrides { + fn(cmd, &getPermissionsReq) + } + + return cmd +} + +// start list-examples command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listExamplesOverrides []func( + *cobra.Command, + *knowledgeassistants.ListExamplesRequest, +) + +func newListExamples() *cobra.Command { + cmd := &cobra.Command{} + + var listExamplesReq knowledgeassistants.ListExamplesRequest + // 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 listExamplesLimit int + + cmd.Flags().IntVar(&listExamplesReq.PageSize, "page-size", listExamplesReq.PageSize, `The maximum number of examples to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listExamplesLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listExamplesReq.PageToken, "page-token", listExamplesReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-examples PARENT" + cmd.Short = `*Beta* List examples for a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +List examples for a Knowledge Assistant. + + Lists examples under a Knowledge Assistant. + + Arguments: + PARENT: Parent resource to list from. Format: + knowledge-assistants/{knowledge_assistant_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listExamplesReq.Parent = args[0] + + response := w.KnowledgeAssistants.ListExamples(ctx, listExamplesReq) + if listExamplesLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listExamplesLimit) + } + if listExamplesLimit > 0 { + ctx = cmdio.WithLimit(ctx, listExamplesLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listExamplesOverrides { + fn(cmd, &listExamplesReq) + } + + return cmd +} + // start list-knowledge-assistants command // Slice with functions to override default command behavior. @@ -504,12 +959,16 @@ func newListKnowledgeAssistants() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-knowledge-assistants" - cmd.Short = `List Knowledge Assistants.` - cmd.Long = `List Knowledge Assistants. + cmd.Short = `*Beta* List Knowledge Assistants.` + cmd.Long = `This command is in Beta and may change without notice. + +List Knowledge Assistants. List Knowledge Assistants` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -572,8 +1031,10 @@ func newListKnowledgeSources() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-knowledge-sources PARENT" - cmd.Short = `List Knowledge Sources.` - cmd.Long = `List Knowledge Sources. + cmd.Short = `*Beta* List Knowledge Sources.` + cmd.Long = `This command is in Beta and may change without notice. + +List Knowledge Sources. Lists Knowledge Sources under a Knowledge Assistant. @@ -582,6 +1043,8 @@ func newListKnowledgeSources() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -618,6 +1081,86 @@ func newListKnowledgeSources() *cobra.Command { return cmd } +// start set-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var setPermissionsOverrides []func( + *cobra.Command, + *knowledgeassistants.KnowledgeAssistantPermissionsRequest, +) + +func newSetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var setPermissionsReq knowledgeassistants.KnowledgeAssistantPermissionsRequest + var setPermissionsJson flags.JsonFlag + + cmd.Flags().Var(&setPermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "set-permissions KNOWLEDGE_ASSISTANT_ID" + cmd.Short = `*Beta* Set knowledge assistant permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Set knowledge assistant permissions. + + Sets permissions on an object, replacing existing permissions if they exist. + Deletes all direct permissions if none are specified. Objects can inherit + permissions from their root object. + + Arguments: + KNOWLEDGE_ASSISTANT_ID: The knowledge assistant for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := setPermissionsJson.Unmarshal(&setPermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + setPermissionsReq.KnowledgeAssistantId = args[0] + + response, err := w.KnowledgeAssistants.SetPermissions(ctx, setPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range setPermissionsOverrides { + fn(cmd, &setPermissionsReq) + } + + return cmd +} + // start sync-knowledge-sources command // Slice with functions to override default command behavior. @@ -633,8 +1176,10 @@ func newSyncKnowledgeSources() *cobra.Command { var syncKnowledgeSourcesReq knowledgeassistants.SyncKnowledgeSourcesRequest cmd.Use = "sync-knowledge-sources NAME" - cmd.Short = `Syncs all Knowledge Sources for a Knowledge Assistant.` - cmd.Long = `Syncs all Knowledge Sources for a Knowledge Assistant. + cmd.Short = `*Beta* Syncs all Knowledge Sources for a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Syncs all Knowledge Sources for a Knowledge Assistant. Sync all non-index Knowledge Sources for a Knowledge Assistant (index sources do not require sync) @@ -644,6 +1189,8 @@ func newSyncKnowledgeSources() *cobra.Command { knowledge-assistants/{knowledge_assistant_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -676,6 +1223,104 @@ func newSyncKnowledgeSources() *cobra.Command { return cmd } +// start update-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateExampleOverrides []func( + *cobra.Command, + *knowledgeassistants.UpdateExampleRequest, +) + +func newUpdateExample() *cobra.Command { + cmd := &cobra.Command{} + + var updateExampleReq knowledgeassistants.UpdateExampleRequest + updateExampleReq.Example = knowledgeassistants.Example{} + var updateExampleJson flags.JsonFlag + + cmd.Flags().Var(&updateExampleJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: guidelines + cmd.Flags().StringVar(&updateExampleReq.Example.Name, "name", updateExampleReq.Example.Name, `Full resource name: knowledge-assistants/{knowledge_assistant_id}/examples/{example_id}.`) + + cmd.Use = "update-example NAME UPDATE_MASK QUESTION" + cmd.Short = `*Beta* Update an example in a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Update an example in a Knowledge Assistant. + + Updates an example in a Knowledge Assistant. + + Arguments: + NAME: The resource name of the example to update. Format: + knowledge-assistants/{knowledge_assistant_id}/examples/{example_id} + UPDATE_MASK: Comma-delimited list of fields to update on the example. Allowed values: + question, guidelines. Examples: - question - question,guidelines + QUESTION: The example question.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME, UPDATE_MASK as positional arguments. Provide 'question' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateExampleJson.Unmarshal(&updateExampleReq.Example) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateExampleReq.Name = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateExampleReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + updateExampleReq.Example.Question = args[2] + } + + response, err := w.KnowledgeAssistants.UpdateExample(ctx, updateExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateExampleOverrides { + fn(cmd, &updateExampleReq) + } + + return cmd +} + // start update-knowledge-assistant command // Slice with functions to override default command behavior. @@ -698,8 +1343,10 @@ func newUpdateKnowledgeAssistant() *cobra.Command { cmd.Flags().StringVar(&updateKnowledgeAssistantReq.KnowledgeAssistant.Name, "name", updateKnowledgeAssistantReq.KnowledgeAssistant.Name, `The resource name of the Knowledge Assistant.`) cmd.Use = "update-knowledge-assistant NAME UPDATE_MASK DISPLAY_NAME DESCRIPTION" - cmd.Short = `Update a Knowledge Assistant.` - cmd.Long = `Update a Knowledge Assistant. + cmd.Short = `*Beta* Update a Knowledge Assistant.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Knowledge Assistant. Updates a Knowledge Assistant. @@ -717,6 +1364,8 @@ func newUpdateKnowledgeAssistant() *cobra.Command { optional unless included in update_mask.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -803,8 +1452,10 @@ func newUpdateKnowledgeSource() *cobra.Command { cmd.Flags().StringVar(&updateKnowledgeSourceReq.KnowledgeSource.Name, "name", updateKnowledgeSourceReq.KnowledgeSource.Name, `Full resource name: knowledge-assistants/{knowledge_assistant_id}/knowledge-sources/{knowledge_source_id}.`) cmd.Use = "update-knowledge-source NAME UPDATE_MASK DISPLAY_NAME DESCRIPTION SOURCE_TYPE" - cmd.Short = `Update a Knowledge Source.` - cmd.Long = `Update a Knowledge Source. + cmd.Short = `*Beta* Update a Knowledge Source.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Knowledge Source. Updates a Knowledge Source. @@ -825,6 +1476,8 @@ func newUpdateKnowledgeSource() *cobra.Command { is ignored.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -890,4 +1543,83 @@ func newUpdateKnowledgeSource() *cobra.Command { return cmd } +// start update-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updatePermissionsOverrides []func( + *cobra.Command, + *knowledgeassistants.KnowledgeAssistantPermissionsRequest, +) + +func newUpdatePermissions() *cobra.Command { + cmd := &cobra.Command{} + + var updatePermissionsReq knowledgeassistants.KnowledgeAssistantPermissionsRequest + var updatePermissionsJson flags.JsonFlag + + cmd.Flags().Var(&updatePermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "update-permissions KNOWLEDGE_ASSISTANT_ID" + cmd.Short = `*Beta* Update knowledge assistant permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Update knowledge assistant permissions. + + Updates the permissions on a knowledge assistant. Knowledge assistants can + inherit permissions from their root object. + + Arguments: + KNOWLEDGE_ASSISTANT_ID: The knowledge assistant for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updatePermissionsJson.Unmarshal(&updatePermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updatePermissionsReq.KnowledgeAssistantId = args[0] + + response, err := w.KnowledgeAssistants.UpdatePermissions(ctx, updatePermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updatePermissionsOverrides { + fn(cmd, &updatePermissionsReq) + } + + return cmd +} + // end service KnowledgeAssistants diff --git a/cmd/workspace/lakeview-embedded/lakeview-embedded.go b/cmd/workspace/lakeview-embedded/lakeview-embedded.go index 8034e8086a8..8c0d82339d7 100755 --- a/cmd/workspace/lakeview-embedded/lakeview-embedded.go +++ b/cmd/workspace/lakeview-embedded/lakeview-embedded.go @@ -23,6 +23,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGetPublishedDashboardTokenInfo()) @@ -52,8 +56,10 @@ func newGetPublishedDashboardTokenInfo() *cobra.Command { cmd.Flags().StringVar(&getPublishedDashboardTokenInfoReq.ExternalViewerId, "external-viewer-id", getPublishedDashboardTokenInfoReq.ExternalViewerId, `Provided external viewer id to be included in the custom claim.`) cmd.Use = "get-published-dashboard-token-info DASHBOARD_ID" - cmd.Short = `Read information of a published dashboard to mint an OAuth token.` - cmd.Long = `Read information of a published dashboard to mint an OAuth token. + cmd.Short = `*Public Preview* Read information of a published dashboard to mint an OAuth token.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Read information of a published dashboard to mint an OAuth token. Get a required authorization details and scopes of a published dashboard to mint an OAuth token. @@ -62,6 +68,8 @@ func newGetPublishedDashboardTokenInfo() *cobra.Command { DASHBOARD_ID: UUID identifying the published dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/lakeview/lakeview.go b/cmd/workspace/lakeview/lakeview.go index f9ea06c4690..c3cf12a9747 100755 --- a/cmd/workspace/lakeview/lakeview.go +++ b/cmd/workspace/lakeview/lakeview.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newCreateSchedule()) @@ -87,6 +91,8 @@ func newCreate() *cobra.Command { Create a draft dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -163,6 +169,8 @@ func newCreateSchedule() *cobra.Command { this schedule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -253,6 +261,8 @@ func newCreateSubscription() *cobra.Command { to the schedule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -338,6 +348,8 @@ func newDeleteSchedule() *cobra.Command { SCHEDULE_ID: UUID identifying the schedule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -397,6 +409,8 @@ func newDeleteSubscription() *cobra.Command { SUBSCRIPTION_ID: UUID identifying the subscription.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -455,6 +469,8 @@ func newGet() *cobra.Command { DASHBOARD_ID: UUID identifying the dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -512,6 +528,8 @@ func newGetPublished() *cobra.Command { DASHBOARD_ID: UUID identifying the published dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -568,6 +586,8 @@ func newGetSchedule() *cobra.Command { SCHEDULE_ID: UUID identifying the schedule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -626,6 +646,8 @@ func newGetSubscription() *cobra.Command { SUBSCRIPTION_ID: UUID identifying the subscription.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -695,6 +717,8 @@ func newList() *cobra.Command { cmd.Long = `List dashboards.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -764,6 +788,8 @@ func newListSchedules() *cobra.Command { DASHBOARD_ID: UUID identifying the dashboard to which the schedules belongs.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -836,6 +862,8 @@ func newListSubscriptions() *cobra.Command { SCHEDULE_ID: UUID identifying the schedule which the subscriptions belongs.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -904,6 +932,8 @@ func newMigrate() *cobra.Command { SOURCE_DASHBOARD_ID: UUID of the dashboard to be migrated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -988,6 +1018,8 @@ func newPublish() *cobra.Command { DASHBOARD_ID: UUID identifying the dashboard to be published.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1057,6 +1089,8 @@ func newTrash() *cobra.Command { DASHBOARD_ID: UUID identifying the dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1113,6 +1147,8 @@ func newUnpublish() *cobra.Command { DASHBOARD_ID: UUID identifying the published dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1179,6 +1215,8 @@ func newUpdate() *cobra.Command { DASHBOARD_ID: UUID identifying the dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1257,6 +1295,8 @@ func newUpdateSchedule() *cobra.Command { this schedule.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/libraries/libraries.go b/cmd/workspace/libraries/libraries.go index 5f890d86819..32511997395 100755 --- a/cmd/workspace/libraries/libraries.go +++ b/cmd/workspace/libraries/libraries.go @@ -41,6 +41,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newAllClusterStatuses()) cmd.AddCommand(newClusterStatus()) @@ -83,6 +87,8 @@ func newAllClusterStatuses() *cobra.Command { libraries installed on this cluster via the API or the libraries UI.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -150,6 +156,8 @@ func newClusterStatus() *cobra.Command { CLUSTER_ID: Unique identifier of the cluster whose status should be retrieved.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -211,6 +219,8 @@ func newInstall() *cobra.Command { happens in the background after the completion of this request.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -277,6 +287,8 @@ func newUninstall() *cobra.Command { currently installed is ignored.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/llm-proxy-partner-powered-workspace/llm-proxy-partner-powered-workspace.go b/cmd/workspace/llm-proxy-partner-powered-workspace/llm-proxy-partner-powered-workspace.go index 90c003e216d..0733e480083 100755 --- a/cmd/workspace/llm-proxy-partner-powered-workspace/llm-proxy-partner-powered-workspace.go +++ b/cmd/workspace/llm-proxy-partner-powered-workspace/llm-proxy-partner-powered-workspace.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -66,6 +70,8 @@ func newDelete() *cobra.Command { default value.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -120,6 +126,8 @@ func newGet() *cobra.Command { Gets the enable partner powered AI features workspace setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -175,6 +183,8 @@ func newUpdate() *cobra.Command { Updates the enable partner powered AI features workspace setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/materialized-features/materialized-features.go b/cmd/workspace/materialized-features/materialized-features.go index a9a7f55b142..1beef5f871b 100755 --- a/cmd/workspace/materialized-features/materialized-features.go +++ b/cmd/workspace/materialized-features/materialized-features.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreateFeatureTag()) cmd.AddCommand(newDeleteFeatureTag()) @@ -73,6 +77,8 @@ func newCreateFeatureTag() *cobra.Command { Creates a FeatureTag.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -155,6 +161,8 @@ func newDeleteFeatureTag() *cobra.Command { KEY: The key of the tag to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -212,6 +220,8 @@ func newGetFeatureLineage() *cobra.Command { FEATURE_NAME: The name of the feature.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -267,6 +277,8 @@ func newGetFeatureTag() *cobra.Command { Gets a FeatureTag.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -336,6 +348,8 @@ func newListFeatureTags() *cobra.Command { Lists FeatureTags.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -401,6 +415,8 @@ func newUpdateFeatureTag() *cobra.Command { Updates a FeatureTag.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) diff --git a/cmd/workspace/metastores/metastores.go b/cmd/workspace/metastores/metastores.go index 95decff8409..46fdedb8eca 100755 --- a/cmd/workspace/metastores/metastores.go +++ b/cmd/workspace/metastores/metastores.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newAssign()) cmd.AddCommand(newCreate()) @@ -91,6 +95,8 @@ func newAssign() *cobra.Command { catalog for a Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -187,6 +193,8 @@ func newCreate() *cobra.Command { NAME: The user-specified name of the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -259,6 +267,8 @@ func newCurrent() *cobra.Command { Gets the metastore assignment for the workspace being accessed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -310,6 +320,8 @@ func newDelete() *cobra.Command { ID: Unique ID of the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -367,6 +379,8 @@ func newGet() *cobra.Command { ID: Unique ID of the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -444,6 +458,8 @@ func newList() *cobra.Command { indication that the end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -497,6 +513,8 @@ func newSummary() *cobra.Command { credential, the cloud vendor, the cloud region, and the global metastore ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -547,6 +565,8 @@ func newUnassign() *cobra.Command { METASTORE_ID: Query for the ID of the metastore to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -622,6 +642,8 @@ func newUpdate() *cobra.Command { ID: Unique ID of the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -700,6 +722,8 @@ func newUpdateAssignment() *cobra.Command { WORKSPACE_ID: A workspace ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/model-registry/model-registry.go b/cmd/workspace/model-registry/model-registry.go index f742fe8a666..4df77e40183 100755 --- a/cmd/workspace/model-registry/model-registry.go +++ b/cmd/workspace/model-registry/model-registry.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newApproveTransitionRequest()) cmd.AddCommand(newCreateComment()) @@ -120,6 +124,8 @@ func newApproveTransitionRequest() *cobra.Command { stage.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -218,6 +224,8 @@ func newCreateComment() *cobra.Command { COMMENT: User-provided comment on the action.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -310,6 +318,8 @@ func newCreateModel() *cobra.Command { NAME: Register models under this name` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -397,6 +407,8 @@ func newCreateModelVersion() *cobra.Command { SOURCE: URI indicating the location of the model artifacts.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -493,6 +505,8 @@ func newCreateTransitionRequest() *cobra.Command { * Archived: Archived stage.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -583,6 +597,8 @@ func newCreateWebhook() *cobra.Command { **NOTE:** This endpoint is in Public Preview. Creates a registry webhook.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -648,6 +664,8 @@ func newDeleteComment() *cobra.Command { ID: Unique identifier of an activity` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -704,6 +722,8 @@ func newDeleteModel() *cobra.Command { NAME: Registered model unique name identifier.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -762,6 +782,8 @@ func newDeleteModelTag() *cobra.Command { not supported. Maximum size is 250 bytes.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -820,6 +842,8 @@ func newDeleteModelVersion() *cobra.Command { VERSION: Model version number` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -880,6 +904,8 @@ func newDeleteModelVersionTag() *cobra.Command { not supported. Maximum size is 250 bytes.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -953,6 +979,8 @@ func newDeleteTransitionRequest() *cobra.Command { user will be deleted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) @@ -1013,6 +1041,8 @@ func newDeleteWebhook() *cobra.Command { ID: Webhook ID required to delete a registry webhook.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1083,6 +1113,8 @@ func newGetLatestVersions() *cobra.Command { NAME: Registered model unique name identifier.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1168,6 +1200,8 @@ func newGetModel() *cobra.Command { NAME: Registered model unique name identifier.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1224,6 +1258,8 @@ func newGetModelVersion() *cobra.Command { VERSION: Model version number` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1283,6 +1319,8 @@ func newGetModelVersionDownloadUri() *cobra.Command { VERSION: Model version number` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1341,6 +1379,8 @@ func newGetPermissionLevels() *cobra.Command { REGISTERED_MODEL_ID: The registered model for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1399,6 +1439,8 @@ func newGetPermissions() *cobra.Command { REGISTERED_MODEL_ID: The registered model for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1467,6 +1509,8 @@ func newListModels() *cobra.Command { __max_results__.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1535,6 +1579,8 @@ func newListTransitionRequests() *cobra.Command { VERSION: Version of the model.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -1608,6 +1654,8 @@ func newListWebhooks() *cobra.Command { **NOTE:** This endpoint is in Public Preview. Lists all registry webhooks.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1681,6 +1729,8 @@ func newRejectTransitionRequest() *cobra.Command { * Archived: Archived stage.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1770,6 +1820,8 @@ func newRenameModel() *cobra.Command { NAME: Registered model unique name identifier.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1860,6 +1912,8 @@ func newSearchModelVersions() *cobra.Command { Searches for specific model versions based on the supplied __filter__.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1930,6 +1984,8 @@ func newSearchModels() *cobra.Command { Search for registered models based on the specified __filter__.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1998,6 +2054,8 @@ func newSetModelTag() *cobra.Command { 5000 bytes in size.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2092,6 +2150,8 @@ func newSetModelVersionTag() *cobra.Command { 5000 bytes in size.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2185,6 +2245,8 @@ func newSetPermissions() *cobra.Command { REGISTERED_MODEL_ID: The registered model for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2272,6 +2334,8 @@ func newTestRegistryWebhook() *cobra.Command { ID: Webhook ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2371,6 +2435,8 @@ func newTransitionStage() *cobra.Command { stage.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2466,6 +2532,8 @@ func newUpdateComment() *cobra.Command { COMMENT: User-provided comment on the action.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2552,6 +2620,8 @@ func newUpdateModel() *cobra.Command { NAME: Registered model unique name identifier.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2636,6 +2706,8 @@ func newUpdateModelVersion() *cobra.Command { VERSION: Model version number` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -2723,6 +2795,8 @@ func newUpdatePermissions() *cobra.Command { REGISTERED_MODEL_ID: The registered model for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2801,6 +2875,8 @@ func newUpdateWebhook() *cobra.Command { ID: Webhook ID` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/model-versions/model-versions.go b/cmd/workspace/model-versions/model-versions.go index 2432693d89c..44e1bd995ed 100755 --- a/cmd/workspace/model-versions/model-versions.go +++ b/cmd/workspace/model-versions/model-versions.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -78,6 +82,8 @@ func newDelete() *cobra.Command { VERSION: The integer version number of the model version` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -147,6 +153,8 @@ func newGet() *cobra.Command { VERSION: The integer version number of the model version` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -216,6 +224,8 @@ func newGetByAlias() *cobra.Command { ALIAS: The name of the alias` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -306,6 +316,8 @@ func newList() *cobra.Command { model versions` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -395,6 +407,8 @@ func newUpdate() *cobra.Command { VERSION: The integer version number of the model version` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/notification-destinations/notification-destinations.go b/cmd/workspace/notification-destinations/notification-destinations.go index b028cf80d69..e463527466a 100755 --- a/cmd/workspace/notification-destinations/notification-destinations.go +++ b/cmd/workspace/notification-destinations/notification-destinations.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,6 +76,8 @@ func newCreate() *cobra.Command { Creates a notification destination. Requires workspace admin permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -137,6 +143,8 @@ func newDelete() *cobra.Command { Deletes a notification destination. Requires workspace admin permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -190,6 +198,8 @@ func newGet() *cobra.Command { Gets a notification destination.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -257,6 +267,8 @@ func newList() *cobra.Command { Lists notification destinations.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -322,6 +334,8 @@ func newUpdate() *cobra.Command { ID: UUID identifying notification destination.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/online-tables/online-tables.go b/cmd/workspace/online-tables/online-tables.go index fb43d85e8a9..d634450328e 100755 --- a/cmd/workspace/online-tables/online-tables.go +++ b/cmd/workspace/online-tables/online-tables.go @@ -21,13 +21,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "online-tables", - Short: `Online tables provide lower latency and higher QPS access to data from Delta tables.`, - Long: `Online tables provide lower latency and higher QPS access to data from Delta + Short: `*Public Preview* Online tables provide lower latency and higher QPS access to data from Delta tables.`, + Long: `This command is in Public Preview and may change without notice. + +Online tables provide lower latency and higher QPS access to data from Delta tables.`, GroupID: "catalog", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -70,12 +76,16 @@ func newCreate() *cobra.Command { // TODO: complex arg: status cmd.Use = "create" - cmd.Short = `Create an Online Table.` - cmd.Long = `Create an Online Table. + cmd.Short = `*Public Preview* Create an Online Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an Online Table. Create a new Online Table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -147,8 +157,10 @@ func newDelete() *cobra.Command { var deleteReq catalog.DeleteOnlineTableRequest cmd.Use = "delete NAME" - cmd.Short = `Delete an Online Table.` - cmd.Long = `Delete an Online Table. + cmd.Short = `*Public Preview* Delete an Online Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an Online Table. Delete an online table. Warning: This will delete all the data in the online table. If the source Delta table was deleted or modified since this Online @@ -158,6 +170,8 @@ func newDelete() *cobra.Command { NAME: Full three-part (catalog, schema, table) name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -205,8 +219,10 @@ func newGet() *cobra.Command { var getReq catalog.GetOnlineTableRequest cmd.Use = "get NAME" - cmd.Short = `Get an Online Table.` - cmd.Long = `Get an Online Table. + cmd.Short = `*Public Preview* Get an Online Table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an Online Table. Get information about an existing online table and its status. @@ -214,6 +230,8 @@ func newGet() *cobra.Command { NAME: Full three-part (catalog, schema, table) name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/permission-migration/permission-migration.go b/cmd/workspace/permission-migration/permission-migration.go index 0bcf145740e..c75ec9f01e1 100755 --- a/cmd/workspace/permission-migration/permission-migration.go +++ b/cmd/workspace/permission-migration/permission-migration.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newMigratePermissions()) @@ -71,6 +75,8 @@ func newMigratePermissions() *cobra.Command { TO_ACCOUNT_GROUP_NAME: The name of the account group that permissions will be migrated to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/permissions/permissions.go b/cmd/workspace/permissions/permissions.go index f12e2f9e41c..02bd53c875d 100755 --- a/cmd/workspace/permissions/permissions.go +++ b/cmd/workspace/permissions/permissions.go @@ -25,9 +25,9 @@ func New() *cobra.Command { **[Cluster permissions](:service:clusters)** — Manage which users can manage, restart, or attach to clusters. * **[Cluster policy permissions](:service:clusterpolicies)** — Manage which users can use - cluster policies. * **[Delta Live Tables pipeline + cluster policies. * **[Spark Declarative Pipelines permissions](:service:pipelines)** — Manage which users can view, manage, - run, cancel, or own a Delta Live Tables pipeline. * **[Job + run, cancel, or own a Spark Declarative Pipeline. * **[Job permissions](:service:jobs)** — Manage which users can view, manage, trigger, cancel, or own a job. * **[MLflow experiment permissions](:service:experiments)** — Manage which users can read, edit, or @@ -54,6 +54,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newGetPermissionLevels()) @@ -93,11 +97,14 @@ func newGet() *cobra.Command { REQUEST_OBJECT_TYPE: The type of the request object. Can be one of the following: alerts, alertsv2, authorization, clusters, cluster-policies, dashboards, database-projects, dbsql-dashboards, directories, experiments, files, - genie, instance-pools, jobs, notebooks, pipelines, queries, - registered-models, repos, serving-endpoints, or warehouses. + genie, instance-pools, jobs, knowledge-assistants, notebooks, pipelines, + queries, registered-models, repos, serving-endpoints, supervisor-agents, + vector-search-endpoints, or warehouses. REQUEST_OBJECT_ID: The id of the request object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -156,11 +163,14 @@ func newGetPermissionLevels() *cobra.Command { REQUEST_OBJECT_TYPE: The type of the request object. Can be one of the following: alerts, alertsv2, authorization, clusters, cluster-policies, dashboards, database-projects, dbsql-dashboards, directories, experiments, files, - genie, instance-pools, jobs, notebooks, pipelines, queries, - registered-models, repos, serving-endpoints, or warehouses. + genie, instance-pools, jobs, knowledge-assistants, notebooks, pipelines, + queries, registered-models, repos, serving-endpoints, supervisor-agents, + vector-search-endpoints, or warehouses. REQUEST_OBJECT_ID: ` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -226,11 +236,14 @@ func newSet() *cobra.Command { REQUEST_OBJECT_TYPE: The type of the request object. Can be one of the following: alerts, alertsv2, authorization, clusters, cluster-policies, dashboards, database-projects, dbsql-dashboards, directories, experiments, files, - genie, instance-pools, jobs, notebooks, pipelines, queries, - registered-models, repos, serving-endpoints, or warehouses. + genie, instance-pools, jobs, knowledge-assistants, notebooks, pipelines, + queries, registered-models, repos, serving-endpoints, supervisor-agents, + vector-search-endpoints, or warehouses. REQUEST_OBJECT_ID: The id of the request object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -307,11 +320,14 @@ func newUpdate() *cobra.Command { REQUEST_OBJECT_TYPE: The type of the request object. Can be one of the following: alerts, alertsv2, authorization, clusters, cluster-policies, dashboards, database-projects, dbsql-dashboards, directories, experiments, files, - genie, instance-pools, jobs, notebooks, pipelines, queries, - registered-models, repos, serving-endpoints, or warehouses. + genie, instance-pools, jobs, knowledge-assistants, notebooks, pipelines, + queries, registered-models, repos, serving-endpoints, supervisor-agents, + vector-search-endpoints, or warehouses. REQUEST_OBJECT_ID: The id of the request object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/pipelines/pipelines.go b/cmd/workspace/pipelines/pipelines.go index 2401b325371..5af6deed66b 100755 --- a/cmd/workspace/pipelines/pipelines.go +++ b/cmd/workspace/pipelines/pipelines.go @@ -41,6 +41,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newApplyEnvironment()) cmd.AddCommand(newClone()) @@ -82,13 +86,17 @@ func newApplyEnvironment() *cobra.Command { var applyEnvironmentReq pipelines.ApplyEnvironmentRequest cmd.Use = "apply-environment PIPELINE_ID" - cmd.Short = `Apply the latest environment to the pipeline.` - cmd.Long = `Apply the latest environment to the pipeline. + cmd.Short = `*Public Preview* Apply the latest environment to the pipeline.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Apply the latest environment to the pipeline. * Applies the current pipeline environment onto the pipeline compute. The environment applied can be used by subsequent dev-mode updates.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -163,6 +171,8 @@ func newClone() *cobra.Command { PIPELINE_ID: Source pipeline to clone from` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -235,6 +245,8 @@ func newCreate() *cobra.Command { If successful, this method returns the ID of the new pipeline.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -302,6 +314,8 @@ func newDelete() *cobra.Command { support for assistance to undo this action.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -365,6 +379,8 @@ func newGet() *cobra.Command { cmd.Long = `Get a pipeline.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -434,6 +450,8 @@ func newGetPermissionLevels() *cobra.Command { PIPELINE_ID: The pipeline for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -504,6 +522,8 @@ func newGetPermissions() *cobra.Command { PIPELINE_ID: The pipeline for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -574,6 +594,8 @@ func newGetUpdate() *cobra.Command { UPDATE_ID: The ID of the update.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -647,6 +669,8 @@ func newListPipelineEvents() *cobra.Command { PIPELINE_ID: The pipeline to return events for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -731,6 +755,8 @@ func newListPipelines() *cobra.Command { Lists pipelines defined in the Spark Declarative Pipelines system.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -793,6 +819,8 @@ func newListUpdates() *cobra.Command { PIPELINE_ID: The pipeline to return updates for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -869,6 +897,8 @@ func newSetPermissions() *cobra.Command { PIPELINE_ID: The pipeline for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -969,6 +999,8 @@ func newStartUpdate() *cobra.Command { the pipeline, the request will fail and the active update will remain running.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1054,6 +1086,8 @@ func newStop() *cobra.Command { update for the pipeline, this request is a no-op.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1130,7 +1164,7 @@ func newUpdate() *cobra.Command { cmd.Flags().BoolVar(&updateReq.AllowDuplicateNames, "allow-duplicate-names", updateReq.AllowDuplicateNames, `If false, deployment will fail if name has changed and conflicts the name of another pipeline.`) cmd.Flags().StringVar(&updateReq.BudgetPolicyId, "budget-policy-id", updateReq.BudgetPolicyId, `Budget policy of this pipeline.`) cmd.Flags().StringVar(&updateReq.Catalog, "catalog", updateReq.Catalog, `A catalog in Unity Catalog to publish data from this pipeline to.`) - cmd.Flags().StringVar(&updateReq.Channel, "channel", updateReq.Channel, `DLT Release Channel that specifies which version to use.`) + cmd.Flags().StringVar(&updateReq.Channel, "channel", updateReq.Channel, `SDP Release Channel that specifies which version to use.`) // TODO: array: clusters // TODO: map via StringToStringVar: configuration cmd.Flags().BoolVar(&updateReq.Continuous, "continuous", updateReq.Continuous, `Whether the pipeline is continuous or triggered.`) @@ -1169,6 +1203,8 @@ func newUpdate() *cobra.Command { PIPELINE_ID: Unique identifier for this pipeline.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1255,6 +1291,8 @@ func newUpdatePermissions() *cobra.Command { PIPELINE_ID: The pipeline for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/policies/policies.go b/cmd/workspace/policies/policies.go index 8bde169cdc0..c34fe2e7951 100755 --- a/cmd/workspace/policies/policies.go +++ b/cmd/workspace/policies/policies.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "policies", - Short: `Attribute-Based Access Control (ABAC) provides high leverage governance for enforcing compliance policies in Unity Catalog.`, - Long: `Attribute-Based Access Control (ABAC) provides high leverage governance for + Short: `*Public Preview* Attribute-Based Access Control (ABAC) provides high leverage governance for enforcing compliance policies in Unity Catalog.`, + Long: `This command is in Public Preview and may change without notice. + +Attribute-Based Access Control (ABAC) provides high leverage governance for enforcing compliance policies in Unity Catalog. With ABAC policies, access is controlled in a hierarchical and scalable manner, based on data attributes rather than specific resources, enabling more flexible and comprehensive @@ -33,6 +35,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreatePolicy()) cmd.AddCommand(newDeletePolicy()) @@ -95,8 +101,10 @@ func newCreatePolicy() *cobra.Command { cmd.Flags().StringVar(&createPolicyReq.PolicyInfo.WhenCondition, "when-condition", createPolicyReq.PolicyInfo.WhenCondition, `Optional condition when the policy should take effect.`) cmd.Use = "create-policy TO_PRINCIPALS FOR_SECURABLE_TYPE POLICY_TYPE" - cmd.Short = `Create an ABAC policy.` - cmd.Long = `Create an ABAC policy. + cmd.Short = `*Public Preview* Create an ABAC policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an ABAC policy. Creates a new policy on a securable. The new policy applies to the securable and all its descendants. @@ -129,6 +137,8 @@ func newCreatePolicy() *cobra.Command { Supported values: [POLICY_TYPE_COLUMN_MASK, POLICY_TYPE_ROW_FILTER]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -216,8 +226,10 @@ func newDeletePolicy() *cobra.Command { var deletePolicyReq catalog.DeletePolicyRequest cmd.Use = "delete-policy ON_SECURABLE_TYPE ON_SECURABLE_FULLNAME NAME" - cmd.Short = `Delete an ABAC policy.` - cmd.Long = `Delete an ABAC policy. + cmd.Short = `*Public Preview* Delete an ABAC policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an ABAC policy. Delete an ABAC policy defined on a securable. @@ -228,6 +240,8 @@ func newDeletePolicy() *cobra.Command { NAME: Required. The name of the policy to delete` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -278,8 +292,10 @@ func newGetPolicy() *cobra.Command { var getPolicyReq catalog.GetPolicyRequest cmd.Use = "get-policy ON_SECURABLE_TYPE ON_SECURABLE_FULLNAME NAME" - cmd.Short = `Get an ABAC policy.` - cmd.Long = `Get an ABAC policy. + cmd.Short = `*Public Preview* Get an ABAC policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an ABAC policy. Get the policy definition on a securable @@ -289,6 +305,8 @@ func newGetPolicy() *cobra.Command { NAME: Required. The name of the policy to retrieve.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -353,8 +371,10 @@ func newListPolicies() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-policies ON_SECURABLE_TYPE ON_SECURABLE_FULLNAME" - cmd.Short = `List ABAC policies.` - cmd.Long = `List ABAC policies. + cmd.Short = `*Public Preview* List ABAC policies.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List ABAC policies. List all policies defined on a securable. Optionally, the list can include inherited policies defined on the securable's parent schema or catalog. @@ -369,6 +389,8 @@ func newListPolicies() *cobra.Command { ON_SECURABLE_FULLNAME: Required. The fully qualified name of securable to list policies for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -454,8 +476,10 @@ func newUpdatePolicy() *cobra.Command { cmd.Flags().StringVar(&updatePolicyReq.PolicyInfo.WhenCondition, "when-condition", updatePolicyReq.PolicyInfo.WhenCondition, `Optional condition when the policy should take effect.`) cmd.Use = "update-policy ON_SECURABLE_TYPE ON_SECURABLE_FULLNAME NAME TO_PRINCIPALS FOR_SECURABLE_TYPE POLICY_TYPE" - cmd.Short = `Update an ABAC policy.` - cmd.Long = `Update an ABAC policy. + cmd.Short = `*Public Preview* Update an ABAC policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an ABAC policy. Update an ABAC policy on a securable. @@ -491,6 +515,8 @@ func newUpdatePolicy() *cobra.Command { Supported values: [POLICY_TYPE_COLUMN_MASK, POLICY_TYPE_ROW_FILTER]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/policy-compliance-for-clusters/policy-compliance-for-clusters.go b/cmd/workspace/policy-compliance-for-clusters/policy-compliance-for-clusters.go index a4ba0f4f9d7..ce93a72d82d 100755 --- a/cmd/workspace/policy-compliance-for-clusters/policy-compliance-for-clusters.go +++ b/cmd/workspace/policy-compliance-for-clusters/policy-compliance-for-clusters.go @@ -35,6 +35,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newEnforceCompliance()) cmd.AddCommand(newGetCompliance()) @@ -81,7 +85,7 @@ func newEnforceCompliance() *cobra.Command { TERMINATED. The next time the cluster is started, the new attributes will take effect. - Clusters created by the Databricks Jobs, DLT, or Models services cannot be + Clusters created by the Databricks Jobs, SDP, or Models services cannot be enforced by this API. Instead, use the "Enforce job policy compliance" API to enforce policy compliance on jobs. @@ -89,6 +93,8 @@ func newEnforceCompliance() *cobra.Command { CLUSTER_ID: The ID of the cluster you want to enforce policy compliance on.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -168,6 +174,8 @@ func newGetCompliance() *cobra.Command { CLUSTER_ID: The ID of the cluster to get the compliance status` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -240,6 +248,8 @@ func newListCompliance() *cobra.Command { POLICY_ID: Canonical unique identifier for the cluster policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/policy-compliance-for-jobs/policy-compliance-for-jobs.go b/cmd/workspace/policy-compliance-for-jobs/policy-compliance-for-jobs.go index 2a081689035..f184660ef63 100755 --- a/cmd/workspace/policy-compliance-for-jobs/policy-compliance-for-jobs.go +++ b/cmd/workspace/policy-compliance-for-jobs/policy-compliance-for-jobs.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newEnforceCompliance()) cmd.AddCommand(newGetCompliance()) @@ -83,6 +87,8 @@ func newEnforceCompliance() *cobra.Command { JOB_ID: The ID of the job you want to enforce policy compliance on.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -167,6 +173,8 @@ func newGetCompliance() *cobra.Command { JOB_ID: The ID of the job whose compliance status you are requesting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -243,6 +251,8 @@ func newListCompliance() *cobra.Command { POLICY_ID: Canonical unique identifier for the cluster policy.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/policy-families/policy-families.go b/cmd/workspace/policy-families/policy-families.go index 2926c04f260..d760aaf7db6 100755 --- a/cmd/workspace/policy-families/policy-families.go +++ b/cmd/workspace/policy-families/policy-families.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newList()) @@ -72,6 +76,8 @@ func newGet() *cobra.Command { POLICY_FAMILY_ID: The family ID about which to retrieve information.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -140,6 +146,8 @@ func newList() *cobra.Command { version. This API is paginated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/postgres/overrides.go b/cmd/workspace/postgres/overrides.go new file mode 100644 index 00000000000..0f5ca2030c7 --- /dev/null +++ b/cmd/workspace/postgres/overrides.go @@ -0,0 +1,78 @@ +package postgres + +import ( + "errors" + "fmt" + + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/service/postgres" + "github.com/spf13/cobra" +) + +// createRoleOverride appends an example body to the auto-generated help and +// rejects wrapped {"role": ...} bodies with a clear client-side error. +// The --json flag binds to the inner Role object (CreateRoleRequest.Role, +// JSON-tagged "role"), so users supply spec/name/etc. directly. Without an +// example, the auto-generated `// TODO: complex arg: spec` flags leave no +// hint about the body shape and the API's "Field 'role' is required" error +// is unhelpful when the request body is wrong. +func createRoleOverride(createRoleCmd *cobra.Command, _ *postgres.CreateRoleRequest) { + prevPreRunE := createRoleCmd.PreRunE + createRoleCmd.PreRunE = func(cmd *cobra.Command, args []string) error { + if err := rejectWrappedRoleJSON(cmd); err != nil { + return err + } + if prevPreRunE != nil { + return prevPreRunE(cmd, args) + } + return nil + } + + createRoleCmd.Long += ` + + Body shape (passed via --json): fields go directly on the Role object. + Do not wrap them in '{"role": ...}' — the CLI rejects wrapped bodies + client-side with a hint pointing to the right shape. + + Example — create a service-principal-backed role: + + databricks postgres create-role projects//branches/ \ + --role-id \ + --json '{"spec": {"identity_type": "SERVICE_PRINCIPAL", "postgres_role": "", "auth_method": "LAKEBASE_OAUTH_V1"}}' + + The example omits 'membership_roles' so the role starts with default + privileges only — grant database/schema/table access separately via + SQL, following least privilege. Set 'membership_roles' (e.g. + ["DATABRICKS_SUPERUSER"]) only when broad administrative access is + intentional. + + See databricks-sdk-go/service/postgres.RoleRoleSpec for the full set of + spec fields.` +} + +// rejectWrappedRoleJSON returns a clear error when --json is a top-level +// object containing a "role" key. Without this guard the generated unmarshal +// strips the unknown outer "role" field with a warning and ships an empty +// body, and the server rejects with a confusing "Field 'role' is required" +// message. +func rejectWrappedRoleJSON(cmd *cobra.Command) error { + // These checks are internal invariants — postgres create-role is a + // generated command and always has a *flags.JsonFlag for --json. A + // future codegen/refactor change could break that, and we want loud + // breakage rather than a silently-disabled guard. + flag := cmd.Flags().Lookup("json") + if flag == nil { + return errors.New("internal: postgres create-role expected a --json flag; this override is wired to the wrong command") + } + jf, ok := flag.Value.(*flags.JsonFlag) + if !ok { + return fmt.Errorf("internal: postgres create-role --json flag has unexpected type %T; expected *flags.JsonFlag", flag.Value) + } + return jf.RejectWrappedJSON("role", `databricks postgres create-role projects//branches/ \ + --role-id \ + --json '{"spec": {"identity_type": "SERVICE_PRINCIPAL", "postgres_role": "", "auth_method": "LAKEBASE_OAUTH_V1"}}'`) +} + +func init() { + createRoleOverrides = append(createRoleOverrides, createRoleOverride) +} diff --git a/cmd/workspace/postgres/overrides_test.go b/cmd/workspace/postgres/overrides_test.go new file mode 100644 index 00000000000..46b30426177 --- /dev/null +++ b/cmd/workspace/postgres/overrides_test.go @@ -0,0 +1,56 @@ +package postgres + +import ( + "testing" + + "github.com/databricks/cli/libs/flags" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func cmdWithJSON(t *testing.T, raw string) *cobra.Command { + t.Helper() + cmd := &cobra.Command{} + var jf flags.JsonFlag + cmd.Flags().Var(&jf, "json", "JSON body") + if raw != "" { + require.NoError(t, jf.Set(raw)) + } + return cmd +} + +func TestRejectWrappedRoleJSON(t *testing.T) { + t.Run("rejects wrapped {role: ...}", func(t *testing.T) { + cmd := cmdWithJSON(t, `{"role":{"spec":{"identity_type":"SERVICE_PRINCIPAL"}}}`) + err := rejectWrappedRoleJSON(cmd) + require.Error(t, err) + assert.Contains(t, err.Error(), "should NOT be wrapped") + assert.Contains(t, err.Error(), `databricks postgres create-role`) + }) + + t.Run("passes when body has spec at top level", func(t *testing.T) { + cmd := cmdWithJSON(t, `{"spec":{"identity_type":"SERVICE_PRINCIPAL"}}`) + assert.NoError(t, rejectWrappedRoleJSON(cmd)) + }) + + t.Run("passes when --json was not provided", func(t *testing.T) { + cmd := cmdWithJSON(t, "") + assert.NoError(t, rejectWrappedRoleJSON(cmd)) + }) + + t.Run("passes through non-object JSON to the generated diagnostics path", func(t *testing.T) { + cmd := cmdWithJSON(t, `"not-an-object"`) + assert.NoError(t, rejectWrappedRoleJSON(cmd)) + }) + + t.Run("fails loudly when --json flag is absent on the command", func(t *testing.T) { + // Internal invariant: postgres create-role is a generated command and + // always has a --json flag. If a future codegen change drops it, this + // override is wired to the wrong command and should fail loudly so the + // regression is caught rather than silently disabling the guard. + err := rejectWrappedRoleJSON(&cobra.Command{}) + require.Error(t, err) + assert.Contains(t, err.Error(), "internal:") + }) +} diff --git a/cmd/workspace/postgres/postgres.go b/cmd/workspace/postgres/postgres.go index e60842b1e76..df28feb58ce 100755 --- a/cmd/workspace/postgres/postgres.go +++ b/cmd/workspace/postgres/postgres.go @@ -24,8 +24,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "postgres", - Short: `Use the Postgres API to create and manage Lakebase Autoscaling Postgres infrastructure, including projects, branches, compute endpoints, and roles.`, - Long: `Use the Postgres API to create and manage Lakebase Autoscaling Postgres + Short: `*Beta* Use the Postgres API to create and manage Lakebase Autoscaling Postgres infrastructure, including projects, branches, compute endpoints, and roles.`, + Long: `This command is in Beta and may change without notice. + +Use the Postgres API to create and manage Lakebase Autoscaling Postgres infrastructure, including projects, branches, compute endpoints, and roles. This API manages database infrastructure only. To query or modify data, use @@ -41,6 +43,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newCreateBranch()) cmd.AddCommand(newCreateCatalog()) @@ -70,6 +76,8 @@ func New() *cobra.Command { cmd.AddCommand(newListEndpoints()) cmd.AddCommand(newListProjects()) cmd.AddCommand(newListRoles()) + cmd.AddCommand(newUndeleteBranch()) + cmd.AddCommand(newUndeleteProject()) cmd.AddCommand(newUpdateBranch()) cmd.AddCommand(newUpdateDatabase()) cmd.AddCommand(newUpdateEndpoint()) @@ -108,13 +116,16 @@ func newCreateBranch() *cobra.Command { cmd.Flags().Var(&createBranchJson, "json", `either inline JSON string or @path/to/file.json with request body`) + cmd.Flags().BoolVar(&createBranchReq.ReplaceExisting, "replace-existing", createBranchReq.ReplaceExisting, `If true, update the branch if it already exists instead of returning an error.`) cmd.Flags().StringVar(&createBranchReq.Branch.Name, "name", createBranchReq.Branch.Name, `Output only.`) // TODO: complex arg: spec // TODO: complex arg: status cmd.Use = "create-branch PARENT BRANCH_ID" - cmd.Short = `Create a Branch.` - cmd.Long = `Create a Branch. + cmd.Short = `*Beta* Create a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Branch. Creates a new database branch in the project. @@ -133,6 +144,8 @@ func newCreateBranch() *cobra.Command { projects/my-app/branches/development.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -238,8 +251,10 @@ func newCreateCatalog() *cobra.Command { // TODO: complex arg: status cmd.Use = "create-catalog CATALOG_ID" - cmd.Short = `Register a Database in UC.` - cmd.Long = `Register a Database in UC. + cmd.Short = `*Beta* Register a Database in UC.` + cmd.Long = `This command is in Beta and may change without notice. + +Register a Database in UC. Register a Postgres database in the Unity Catalog. @@ -253,6 +268,8 @@ func newCreateCatalog() *cobra.Command { example "my_catalog" becomes "catalogs/my_catalog".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -379,6 +396,8 @@ func newCreateDatabase() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -478,13 +497,16 @@ func newCreateEndpoint() *cobra.Command { cmd.Flags().Var(&createEndpointJson, "json", `either inline JSON string or @path/to/file.json with request body`) + cmd.Flags().BoolVar(&createEndpointReq.ReplaceExisting, "replace-existing", createEndpointReq.ReplaceExisting, `If true, update the endpoint if it already exists instead of returning an error.`) cmd.Flags().StringVar(&createEndpointReq.Endpoint.Name, "name", createEndpointReq.Endpoint.Name, `Output only.`) // TODO: complex arg: spec // TODO: complex arg: status cmd.Use = "create-endpoint PARENT ENDPOINT_ID" - cmd.Short = `Create an Endpoint.` - cmd.Long = `Create an Endpoint. + cmd.Short = `*Beta* Create an Endpoint.` + cmd.Long = `This command is in Beta and may change without notice. + +Create an Endpoint. Creates a new compute endpoint in the branch. @@ -503,6 +525,8 @@ func newCreateEndpoint() *cobra.Command { projects/my-app/branches/development/endpoints/primary.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -609,8 +633,10 @@ func newCreateProject() *cobra.Command { // TODO: complex arg: status cmd.Use = "create-project PROJECT_ID" - cmd.Short = `Create a Project.` - cmd.Long = `Create a Project. + cmd.Short = `*Beta* Create a Project.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Project. Creates a new Lakebase Autoscaling Postgres database project, which contains branches and compute endpoints. @@ -627,6 +653,8 @@ func newCreateProject() *cobra.Command { numbers, and hyphens. For example, my-app becomes projects/my-app.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -732,8 +760,10 @@ func newCreateRole() *cobra.Command { // TODO: complex arg: status cmd.Use = "create-role PARENT" - cmd.Short = `Create a Postgres Role for a Branch.` - cmd.Long = `Create a Postgres Role for a Branch. + cmd.Short = `*Beta* Create a Postgres Role for a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Postgres Role for a Branch. Creates a new Postgres role in the branch. @@ -747,6 +777,8 @@ func newCreateRole() *cobra.Command { projects/{project_id}/branches/{branch_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -851,8 +883,10 @@ func newCreateSyncedTable() *cobra.Command { // TODO: complex arg: status cmd.Use = "create-synced-table SYNCED_TABLE_ID" - cmd.Short = `Create a Synced Database Table.` - cmd.Long = `Create a Synced Database Table. + cmd.Short = `*Beta* Create a Synced Database Table.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Synced Database Table. Create a Synced Table. @@ -876,6 +910,8 @@ func newCreateSyncedTable() *cobra.Command { "{schema}" in the connected Postgres database` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -971,9 +1007,13 @@ func newDeleteBranch() *cobra.Command { cmd.Flags().BoolVar(&deleteBranchSkipWait, "no-wait", deleteBranchSkipWait, `do not wait to reach DONE state`) cmd.Flags().DurationVar(&deleteBranchTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) + cmd.Flags().BoolVar(&deleteBranchReq.Purge, "purge", deleteBranchReq.Purge, `If true, permanently delete the branch; if false, soft delete.`) + cmd.Use = "delete-branch NAME" - cmd.Short = `Delete a Branch.` - cmd.Long = `Delete a Branch. + cmd.Short = `*Beta* Delete a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Branch. Deletes the specified database branch. @@ -987,6 +1027,8 @@ func newDeleteBranch() *cobra.Command { projects/{project_id}/branches/{branch_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1072,8 +1114,10 @@ func newDeleteCatalog() *cobra.Command { cmd.Flags().DurationVar(&deleteCatalogTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) cmd.Use = "delete-catalog NAME" - cmd.Short = `Delete a Database Catalog.` - cmd.Long = `Delete a Database Catalog. + cmd.Short = `*Beta* Delete a Database Catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Database Catalog. This is a long-running operation. By default, the command waits for the operation to complete. Use --no-wait to return immediately with the raw @@ -1086,6 +1130,8 @@ func newDeleteCatalog() *cobra.Command { Format: "catalogs/{catalog_id}".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1187,6 +1233,8 @@ func newDeleteDatabase() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1272,8 +1320,10 @@ func newDeleteEndpoint() *cobra.Command { cmd.Flags().DurationVar(&deleteEndpointTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) cmd.Use = "delete-endpoint NAME" - cmd.Short = `Delete an Endpoint.` - cmd.Long = `Delete an Endpoint. + cmd.Short = `*Beta* Delete an Endpoint.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete an Endpoint. Deletes the specified compute endpoint. @@ -1287,6 +1337,8 @@ func newDeleteEndpoint() *cobra.Command { projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1371,9 +1423,13 @@ func newDeleteProject() *cobra.Command { cmd.Flags().BoolVar(&deleteProjectSkipWait, "no-wait", deleteProjectSkipWait, `do not wait to reach DONE state`) cmd.Flags().DurationVar(&deleteProjectTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) + cmd.Flags().BoolVar(&deleteProjectReq.Purge, "purge", deleteProjectReq.Purge, `If true, permanently deletes the project (hard delete).`) + cmd.Use = "delete-project NAME" - cmd.Short = `Delete a Project.` - cmd.Long = `Delete a Project. + cmd.Short = `*Beta* Delete a Project.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Project. Deletes the specified database project. @@ -1387,6 +1443,8 @@ func newDeleteProject() *cobra.Command { projects/{project_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1474,8 +1532,10 @@ func newDeleteRole() *cobra.Command { cmd.Flags().StringVar(&deleteRoleReq.ReassignOwnedTo, "reassign-owned-to", deleteRoleReq.ReassignOwnedTo, `Reassign objects.`) cmd.Use = "delete-role NAME" - cmd.Short = `Delete a Postgres Role from a Branch.` - cmd.Long = `Delete a Postgres Role from a Branch. + cmd.Short = `*Beta* Delete a Postgres Role from a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Postgres Role from a Branch. Deletes the specified Postgres role. @@ -1489,6 +1549,8 @@ func newDeleteRole() *cobra.Command { projects/{project_id}/branches/{branch_id}/roles/{role_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1574,8 +1636,10 @@ func newDeleteSyncedTable() *cobra.Command { cmd.Flags().DurationVar(&deleteSyncedTableTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) cmd.Use = "delete-synced-table NAME" - cmd.Short = `Delete a Synced Database Table.` - cmd.Long = `Delete a Synced Database Table. + cmd.Short = `*Beta* Delete a Synced Database Table.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Synced Database Table. Delete a Synced Table. @@ -1590,6 +1654,8 @@ func newDeleteSyncedTable() *cobra.Command { are the UC entity names.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1674,15 +1740,18 @@ func newGenerateDatabaseCredential() *cobra.Command { // TODO: array: claims cmd.Use = "generate-database-credential ENDPOINT" - cmd.Short = `Generate OAuth credentials for a Postgres database.` - cmd.Long = `Generate OAuth credentials for a Postgres database. + cmd.Short = `*Beta* Generate OAuth credentials for a Postgres database.` + cmd.Long = `This command is in Beta and may change without notice. + +Generate OAuth credentials for a Postgres database. Arguments: - ENDPOINT: This field is not yet supported. The endpoint for which this credential - will be generated. Format: - projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` + ENDPOINT: The endpoint resource name for which this credential will be generated. + Format: projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1752,8 +1821,10 @@ func newGetBranch() *cobra.Command { var getBranchReq postgres.GetBranchRequest cmd.Use = "get-branch NAME" - cmd.Short = `Get a Branch.` - cmd.Long = `Get a Branch. + cmd.Short = `*Beta* Get a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Branch. Retrieves information about the specified database branch. @@ -1762,6 +1833,8 @@ func newGetBranch() *cobra.Command { projects/{project_id}/branches/{branch_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1810,8 +1883,10 @@ func newGetCatalog() *cobra.Command { var getCatalogReq postgres.GetCatalogRequest cmd.Use = "get-catalog NAME" - cmd.Short = `Get a Database Catalog.` - cmd.Long = `Get a Database Catalog. + cmd.Short = `*Beta* Get a Database Catalog.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Database Catalog. Arguments: NAME: The full resource path of the catalog to retrieve. @@ -1819,6 +1894,8 @@ func newGetCatalog() *cobra.Command { Format: "catalogs/{catalog_id}".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1878,6 +1955,8 @@ func newGetDatabase() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1926,8 +2005,10 @@ func newGetEndpoint() *cobra.Command { var getEndpointReq postgres.GetEndpointRequest cmd.Use = "get-endpoint NAME" - cmd.Short = `Get an Endpoint.` - cmd.Long = `Get an Endpoint. + cmd.Short = `*Beta* Get an Endpoint.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an Endpoint. Retrieves information about the specified compute endpoint, including its connection details and operational state. @@ -1937,6 +2018,8 @@ func newGetEndpoint() *cobra.Command { projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1985,8 +2068,10 @@ func newGetOperation() *cobra.Command { var getOperationReq postgres.GetOperationRequest cmd.Use = "get-operation NAME" - cmd.Short = `Get an Operation.` - cmd.Long = `Get an Operation. + cmd.Short = `*Beta* Get an Operation.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an Operation. Retrieves the status of a long-running operation. @@ -1994,6 +2079,8 @@ func newGetOperation() *cobra.Command { NAME: The name of the operation resource.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2042,8 +2129,10 @@ func newGetProject() *cobra.Command { var getProjectReq postgres.GetProjectRequest cmd.Use = "get-project NAME" - cmd.Short = `Get a Project.` - cmd.Long = `Get a Project. + cmd.Short = `*Beta* Get a Project.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Project. Retrieves information about the specified database project. @@ -2052,6 +2141,8 @@ func newGetProject() *cobra.Command { projects/{project_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2100,8 +2191,10 @@ func newGetRole() *cobra.Command { var getRoleReq postgres.GetRoleRequest cmd.Use = "get-role NAME" - cmd.Short = `Get a Postgres Role for a Branch.` - cmd.Long = `Get a Postgres Role for a Branch. + cmd.Short = `*Beta* Get a Postgres Role for a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Postgres Role for a Branch. Retrieves information about the specified Postgres role, including its authentication method and permissions. @@ -2111,6 +2204,8 @@ func newGetRole() *cobra.Command { projects/{project_id}/branches/{branch_id}/roles/{role_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2159,16 +2254,21 @@ func newGetSyncedTable() *cobra.Command { var getSyncedTableReq postgres.GetSyncedTableRequest cmd.Use = "get-synced-table NAME" - cmd.Short = `Get a Synced Database Table.` - cmd.Long = `Get a Synced Database Table. + cmd.Short = `*Beta* Get a Synced Database Table.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Synced Database Table. Get a Synced Table. Arguments: - NAME: Format: "synced_tables/{catalog}.{schema}.{table}", where (catalog, - schema, table) are the entity names in the Unity Catalog.` + NAME: The Full resource name of the synced table. Format: + "synced_tables/{catalog}.{schema}.{table}", where (catalog, schema, table) + are the entity names in the Unity Catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2221,6 +2321,7 @@ func newListBranches() *cobra.Command { var listBranchesLimit int cmd.Flags().IntVar(&listBranchesReq.PageSize, "page-size", listBranchesReq.PageSize, `Upper bound for items returned.`) + cmd.Flags().BoolVar(&listBranchesReq.ShowDeleted, "show-deleted", listBranchesReq.ShowDeleted, `Whether to include soft-deleted branches in the response.`) // Limit flag for total result capping. cmd.Flags().IntVar(&listBranchesLimit, "limit", 0, `Maximum number of results to return.`) @@ -2230,8 +2331,10 @@ func newListBranches() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-branches PARENT" - cmd.Short = `List Branches.` - cmd.Long = `List Branches. + cmd.Short = `*Beta* List Branches.` + cmd.Long = `This command is in Beta and may change without notice. + +List Branches. Returns a paginated list of database branches in the project. @@ -2240,6 +2343,8 @@ func newListBranches() *cobra.Command { projects/{project_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2317,6 +2422,8 @@ func newListDatabases() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2381,8 +2488,10 @@ func newListEndpoints() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-endpoints PARENT" - cmd.Short = `List Endpoints.` - cmd.Long = `List Endpoints. + cmd.Short = `*Beta* List Endpoints.` + cmd.Long = `This command is in Beta and may change without notice. + +List Endpoints. Returns a paginated list of compute endpoints in the branch. @@ -2391,6 +2500,8 @@ func newListEndpoints() *cobra.Command { projects/{project_id}/branches/{branch_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2446,6 +2557,7 @@ func newListProjects() *cobra.Command { var listProjectsLimit int cmd.Flags().IntVar(&listProjectsReq.PageSize, "page-size", listProjectsReq.PageSize, `Upper bound for items returned.`) + cmd.Flags().BoolVar(&listProjectsReq.ShowDeleted, "show-deleted", listProjectsReq.ShowDeleted, `Whether to include soft-deleted projects in the response.`) // Limit flag for total result capping. cmd.Flags().IntVar(&listProjectsLimit, "limit", 0, `Maximum number of results to return.`) @@ -2455,13 +2567,17 @@ func newListProjects() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-projects" - cmd.Short = `List Projects.` - cmd.Long = `List Projects. + cmd.Short = `*Beta* List Projects.` + cmd.Long = `This command is in Beta and may change without notice. + +List Projects. Returns a paginated list of database projects in the workspace that the user has permission to access.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -2524,8 +2640,10 @@ func newListRoles() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-roles PARENT" - cmd.Short = `List Postgres Roles for a Branch.` - cmd.Long = `List Postgres Roles for a Branch. + cmd.Short = `*Beta* List Postgres Roles for a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +List Postgres Roles for a Branch. Returns a paginated list of Postgres roles in the branch. @@ -2534,6 +2652,8 @@ func newListRoles() *cobra.Command { projects/{project_id}/branches/{branch_id}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -2570,6 +2690,215 @@ func newListRoles() *cobra.Command { return cmd } +// start undelete-branch command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var undeleteBranchOverrides []func( + *cobra.Command, + *postgres.UndeleteBranchRequest, +) + +func newUndeleteBranch() *cobra.Command { + cmd := &cobra.Command{} + + var undeleteBranchReq postgres.UndeleteBranchRequest + + var undeleteBranchSkipWait bool + var undeleteBranchTimeout time.Duration + + cmd.Flags().BoolVar(&undeleteBranchSkipWait, "no-wait", undeleteBranchSkipWait, `do not wait to reach DONE state`) + cmd.Flags().DurationVar(&undeleteBranchTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) + + cmd.Use = "undelete-branch NAME" + cmd.Short = `Undelete a Branch.` + cmd.Long = `Undelete a Branch. + + Undeletes the specified database branch. + + This is a long-running operation. By default, the command waits for the + operation to complete. Use --no-wait to return immediately with the raw + operation details. The operation's 'name' field can then be used to poll for + completion using the get-operation command. + + Arguments: + NAME: The full resource path of the branch to undelete. Format: + projects/{project_id}/branches/{branch_id}` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + undeleteBranchReq.Name = args[0] + + // Determine which mode to execute based on flags. + switch { + case undeleteBranchSkipWait: + wait, err := w.Postgres.UndeleteBranch(ctx, undeleteBranchReq) + if err != nil { + return err + } + + // Return operation immediately without waiting. + operation, err := w.Postgres.GetOperation(ctx, postgres.GetOperationRequest{ + Name: wait.Name(), + }) + if err != nil { + return err + } + return cmdio.Render(ctx, operation) + + default: + wait, err := w.Postgres.UndeleteBranch(ctx, undeleteBranchReq) + if err != nil { + return err + } + + // Show spinner while waiting for completion. + sp := cmdio.NewSpinner(ctx) + sp.Update("Waiting for undelete-branch to complete...") + + // Wait for completion. + opts := api.WithTimeout(undeleteBranchTimeout) + + err = wait.Wait(ctx, opts) + if err != nil { + return err + } + sp.Close() + return nil + } + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range undeleteBranchOverrides { + fn(cmd, &undeleteBranchReq) + } + + return cmd +} + +// start undelete-project command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var undeleteProjectOverrides []func( + *cobra.Command, + *postgres.UndeleteProjectRequest, +) + +func newUndeleteProject() *cobra.Command { + cmd := &cobra.Command{} + + var undeleteProjectReq postgres.UndeleteProjectRequest + + var undeleteProjectSkipWait bool + var undeleteProjectTimeout time.Duration + + cmd.Flags().BoolVar(&undeleteProjectSkipWait, "no-wait", undeleteProjectSkipWait, `do not wait to reach DONE state`) + cmd.Flags().DurationVar(&undeleteProjectTimeout, "timeout", 0, `maximum amount of time to reach DONE state`) + + cmd.Use = "undelete-project NAME" + cmd.Short = `*Beta* Undelete a Project.` + cmd.Long = `This command is in Beta and may change without notice. + +Undelete a Project. + + Undeletes a soft-deleted project. + + This is a long-running operation. By default, the command waits for the + operation to complete. Use --no-wait to return immediately with the raw + operation details. The operation's 'name' field can then be used to poll for + completion using the get-operation command. + + Arguments: + NAME: The full resource path of the project to undelete. Format: + projects/{project_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + undeleteProjectReq.Name = args[0] + + // Determine which mode to execute based on flags. + switch { + case undeleteProjectSkipWait: + wait, err := w.Postgres.UndeleteProject(ctx, undeleteProjectReq) + if err != nil { + return err + } + + // Return operation immediately without waiting. + operation, err := w.Postgres.GetOperation(ctx, postgres.GetOperationRequest{ + Name: wait.Name(), + }) + if err != nil { + return err + } + return cmdio.Render(ctx, operation) + + default: + wait, err := w.Postgres.UndeleteProject(ctx, undeleteProjectReq) + if err != nil { + return err + } + + // Show spinner while waiting for completion. + sp := cmdio.NewSpinner(ctx) + sp.Update("Waiting for undelete-project to complete...") + + // Wait for completion. + opts := api.WithTimeout(undeleteProjectTimeout) + + err = wait.Wait(ctx, opts) + if err != nil { + return err + } + sp.Close() + return nil + } + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range undeleteProjectOverrides { + fn(cmd, &undeleteProjectReq) + } + + return cmd +} + // start update-branch command // Slice with functions to override default command behavior. @@ -2599,8 +2928,10 @@ func newUpdateBranch() *cobra.Command { // TODO: complex arg: status cmd.Use = "update-branch NAME UPDATE_MASK" - cmd.Short = `Update a Branch.` - cmd.Long = `Update a Branch. + cmd.Short = `*Beta* Update a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Branch. Updates the specified database branch. You can set this branch as the project's default branch, or protect/unprotect it. @@ -2617,6 +2948,8 @@ func newUpdateBranch() *cobra.Command { when possible.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -2743,6 +3076,8 @@ func newUpdateDatabase() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -2851,8 +3186,10 @@ func newUpdateEndpoint() *cobra.Command { // TODO: complex arg: status cmd.Use = "update-endpoint NAME UPDATE_MASK" - cmd.Short = `Update an Endpoint.` - cmd.Long = `Update an Endpoint. + cmd.Short = `*Beta* Update an Endpoint.` + cmd.Long = `This command is in Beta and may change without notice. + +Update an Endpoint. Updates the specified compute endpoint. You can update autoscaling limits, suspend timeout, or enable/disable the compute endpoint. @@ -2869,6 +3206,8 @@ func newUpdateEndpoint() *cobra.Command { when possible.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -2978,8 +3317,10 @@ func newUpdateProject() *cobra.Command { // TODO: complex arg: status cmd.Use = "update-project NAME UPDATE_MASK" - cmd.Short = `Update a Project.` - cmd.Long = `Update a Project. + cmd.Short = `*Beta* Update a Project.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Project. Updates the specified database project. @@ -2995,6 +3336,8 @@ func newUpdateProject() *cobra.Command { when possible.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -3103,8 +3446,10 @@ func newUpdateRole() *cobra.Command { // TODO: complex arg: status cmd.Use = "update-role NAME UPDATE_MASK" - cmd.Short = `Update a Postgres Role for a Branch.` - cmd.Long = `Update a Postgres Role for a Branch. + cmd.Short = `*Beta* Update a Postgres Role for a Branch.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Postgres Role for a Branch. Update a role for a branch. @@ -3120,6 +3465,8 @@ func newUpdateRole() *cobra.Command { will be updated when possible.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/provider-exchange-filters/provider-exchange-filters.go b/cmd/workspace/provider-exchange-filters/provider-exchange-filters.go index 2f3e43779b5..6b29d15b39e 100755 --- a/cmd/workspace/provider-exchange-filters/provider-exchange-filters.go +++ b/cmd/workspace/provider-exchange-filters/provider-exchange-filters.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "provider-exchange-filters", - Short: `Marketplace exchanges filters curate which groups can access an exchange.`, - Long: `Marketplace exchanges filters curate which groups can access an exchange.`, + Use: "provider-exchange-filters", + Short: `*Public Preview* Marketplace exchanges filters curate which groups can access an exchange.`, + Long: `This command is in Public Preview and may change without notice. + +Marketplace exchanges filters curate which groups can access an exchange.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -58,12 +64,16 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create" - cmd.Short = `Create a new exchange filter.` - cmd.Long = `Create a new exchange filter. + cmd.Short = `*Public Preview* Create a new exchange filter.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a new exchange filter. Add an exchange filter.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -120,12 +130,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteExchangeFilterRequest cmd.Use = "delete ID" - cmd.Short = `Delete an exchange filter.` - cmd.Long = `Delete an exchange filter. + cmd.Short = `*Public Preview* Delete an exchange filter.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an exchange filter. Delete an exchange filter` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -198,12 +212,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list EXCHANGE_ID" - cmd.Short = `List exchange filters.` - cmd.Long = `List exchange filters. + cmd.Short = `*Public Preview* List exchange filters.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List exchange filters. List exchange filter` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -258,12 +276,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update ID" - cmd.Short = `Update exchange filter.` - cmd.Long = `Update exchange filter. + cmd.Short = `*Public Preview* Update exchange filter.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update exchange filter. Update an exchange filter.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/provider-exchanges/provider-exchanges.go b/cmd/workspace/provider-exchanges/provider-exchanges.go index 5410ad8a038..d2c56498a3c 100755 --- a/cmd/workspace/provider-exchanges/provider-exchanges.go +++ b/cmd/workspace/provider-exchanges/provider-exchanges.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "provider-exchanges", - Short: `Marketplace exchanges allow providers to share their listings with a curated set of customers.`, - Long: `Marketplace exchanges allow providers to share their listings with a curated + Short: `*Public Preview* Marketplace exchanges allow providers to share their listings with a curated set of customers.`, + Long: `This command is in Public Preview and may change without notice. + +Marketplace exchanges allow providers to share their listings with a curated set of customers.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newAddListingToExchange()) cmd.AddCommand(newCreate()) @@ -64,12 +70,16 @@ func newAddListingToExchange() *cobra.Command { cmd.Flags().Var(&addListingToExchangeJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "add-listing-to-exchange LISTING_ID EXCHANGE_ID" - cmd.Short = `Add an exchange for listing.` - cmd.Long = `Add an exchange for listing. + cmd.Short = `*Public Preview* Add an exchange for listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Add an exchange for listing. Associate an exchange with a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -145,12 +155,16 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create" - cmd.Short = `Create an exchange.` - cmd.Long = `Create an exchange. + cmd.Short = `*Public Preview* Create an exchange.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create an exchange. Create an exchange` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -207,12 +221,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteExchangeRequest cmd.Use = "delete ID" - cmd.Short = `Delete an exchange.` - cmd.Long = `Delete an exchange. + cmd.Short = `*Public Preview* Delete an exchange.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete an exchange. This removes a listing from marketplace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -260,12 +278,16 @@ func newDeleteListingFromExchange() *cobra.Command { var deleteListingFromExchangeReq marketplace.RemoveExchangeForListingRequest cmd.Use = "delete-listing-from-exchange ID" - cmd.Short = `Remove an exchange for listing.` - cmd.Long = `Remove an exchange for listing. + cmd.Short = `*Public Preview* Remove an exchange for listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Remove an exchange for listing. Disassociate an exchange with a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -313,10 +335,14 @@ func newGet() *cobra.Command { var getReq marketplace.GetExchangeRequest cmd.Use = "get ID" - cmd.Short = `Get an exchange.` - cmd.Long = `Get an exchange.` + cmd.Short = `*Public Preview* Get an exchange.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get an exchange.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -378,12 +404,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List exchanges.` - cmd.Long = `List exchanges. + cmd.Short = `*Public Preview* List exchanges.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List exchanges. List exchanges visible to provider` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -446,12 +476,16 @@ func newListExchangesForListing() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-exchanges-for-listing LISTING_ID" - cmd.Short = `List exchanges for listing.` - cmd.Long = `List exchanges for listing. + cmd.Short = `*Public Preview* List exchanges for listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List exchanges for listing. List exchanges associated with a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -516,12 +550,16 @@ func newListListingsForExchange() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-listings-for-exchange EXCHANGE_ID" - cmd.Short = `List listings for exchange.` - cmd.Long = `List listings for exchange. + cmd.Short = `*Public Preview* List listings for exchange.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List listings for exchange. List listings associated with an exchange` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -576,12 +614,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update ID" - cmd.Short = `Update exchange.` - cmd.Long = `Update exchange. + cmd.Short = `*Public Preview* Update exchange.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update exchange. Update an exchange` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/provider-files/provider-files.go b/cmd/workspace/provider-files/provider-files.go index ceb4ff5479f..1c77119e0ab 100755 --- a/cmd/workspace/provider-files/provider-files.go +++ b/cmd/workspace/provider-files/provider-files.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "provider-files", - Short: `Marketplace offers a set of file APIs for various purposes such as preview notebooks and provider icons.`, - Long: `Marketplace offers a set of file APIs for various purposes such as preview + Short: `*Public Preview* Marketplace offers a set of file APIs for various purposes such as preview notebooks and provider icons.`, + Long: `This command is in Public Preview and may change without notice. + +Marketplace offers a set of file APIs for various purposes such as preview notebooks and provider icons.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -61,13 +67,17 @@ func newCreate() *cobra.Command { cmd.Flags().StringVar(&createReq.DisplayName, "display-name", createReq.DisplayName, ``) cmd.Use = "create" - cmd.Short = `Create a file.` - cmd.Long = `Create a file. + cmd.Short = `*Public Preview* Create a file.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a file. Create a file. Currently, only provider icons and attached notebooks are supported.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -124,12 +134,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteFileRequest cmd.Use = "delete FILE_ID" - cmd.Short = `Delete a file.` - cmd.Long = `Delete a file. + cmd.Short = `*Public Preview* Delete a file.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a file. Delete a file` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -189,12 +203,16 @@ func newGet() *cobra.Command { var getReq marketplace.GetFileRequest cmd.Use = "get FILE_ID" - cmd.Short = `Get a file.` - cmd.Long = `Get a file. + cmd.Short = `*Public Preview* Get a file.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a file. Get a file` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -271,12 +289,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List files.` - cmd.Long = `List files. + cmd.Short = `*Public Preview* List files.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List files. List files attached to a parent entity.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/provider-listings/provider-listings.go b/cmd/workspace/provider-listings/provider-listings.go index f50e2d743c2..854bdebfd7f 100755 --- a/cmd/workspace/provider-listings/provider-listings.go +++ b/cmd/workspace/provider-listings/provider-listings.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "provider-listings", - Short: `Listings are the core entities in the Marketplace.`, - Long: `Listings are the core entities in the Marketplace. They represent the products + Short: `*Public Preview* Listings are the core entities in the Marketplace.`, + Long: `This command is in Public Preview and may change without notice. + +Listings are the core entities in the Marketplace. They represent the products that are available for consumption.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -60,12 +66,16 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create" - cmd.Short = `Create a listing.` - cmd.Long = `Create a listing. + cmd.Short = `*Public Preview* Create a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a listing. Create a new listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -122,12 +132,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteListingRequest cmd.Use = "delete ID" - cmd.Short = `Delete a listing.` - cmd.Long = `Delete a listing. + cmd.Short = `*Public Preview* Delete a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a listing. Delete a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -187,12 +201,16 @@ func newGet() *cobra.Command { var getReq marketplace.GetListingRequest cmd.Use = "get ID" - cmd.Short = `Get a listing.` - cmd.Long = `Get a listing. + cmd.Short = `*Public Preview* Get a listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a listing. Get a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -266,12 +284,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List listings.` - cmd.Long = `List listings. + cmd.Short = `*Public Preview* List listings.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List listings. List listings owned by this provider` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -324,12 +346,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update ID" - cmd.Short = `Update listing.` - cmd.Long = `Update listing. + cmd.Short = `*Public Preview* Update listing.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update listing. Update a listing` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/provider-personalization-requests/provider-personalization-requests.go b/cmd/workspace/provider-personalization-requests/provider-personalization-requests.go index dac5c529062..f93215a96cd 100755 --- a/cmd/workspace/provider-personalization-requests/provider-personalization-requests.go +++ b/cmd/workspace/provider-personalization-requests/provider-personalization-requests.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "provider-personalization-requests", - Short: `Personalization requests are an alternate to instantly available listings.`, - Long: `Personalization requests are an alternate to instantly available listings. + Short: `*Public Preview* Personalization requests are an alternate to instantly available listings.`, + Long: `This command is in Public Preview and may change without notice. + +Personalization requests are an alternate to instantly available listings. Control the lifecycle of personalized solutions.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newList()) cmd.AddCommand(newUpdate()) @@ -67,13 +73,17 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `All personalization requests across all listings.` - cmd.Long = `All personalization requests across all listings. + cmd.Short = `*Public Preview* All personalization requests across all listings.` + cmd.Long = `This command is in Public Preview and may change without notice. + +All personalization requests across all listings. List personalization requests to this provider. This will return all personalization requests, regardless of which listing they are for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -129,13 +139,17 @@ func newUpdate() *cobra.Command { // TODO: complex arg: share cmd.Use = "update LISTING_ID REQUEST_ID STATUS" - cmd.Short = `Update personalization request status.` - cmd.Long = `Update personalization request status. + cmd.Short = `*Public Preview* Update personalization request status.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update personalization request status. Update personalization request. This method only permits updating the status of the request.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/provider-provider-analytics-dashboards/provider-provider-analytics-dashboards.go b/cmd/workspace/provider-provider-analytics-dashboards/provider-provider-analytics-dashboards.go index b03523c0ac2..a85c1878af7 100755 --- a/cmd/workspace/provider-provider-analytics-dashboards/provider-provider-analytics-dashboards.go +++ b/cmd/workspace/provider-provider-analytics-dashboards/provider-provider-analytics-dashboards.go @@ -17,13 +17,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "provider-provider-analytics-dashboards", - Short: `Manage templated analytics solution for providers.`, - Long: `Manage templated analytics solution for providers.`, + Use: "provider-provider-analytics-dashboards", + Short: `*Public Preview* Manage templated analytics solution for providers.`, + Long: `This command is in Public Preview and may change without notice. + +Manage templated analytics solution for providers.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newGet()) @@ -50,13 +56,17 @@ func newCreate() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "create" - cmd.Short = `Create provider analytics dashboard.` - cmd.Long = `Create provider analytics dashboard. + cmd.Short = `*Public Preview* Create provider analytics dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create provider analytics dashboard. Create provider analytics dashboard. Returns Marketplace specific id. Not to be confused with the Lakeview dashboard id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -94,10 +104,14 @@ func newGet() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "get" - cmd.Short = `Get provider analytics dashboard.` - cmd.Long = `Get provider analytics dashboard.` + cmd.Short = `*Public Preview* Get provider analytics dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get provider analytics dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -135,10 +149,14 @@ func newGetLatestVersion() *cobra.Command { cmd := &cobra.Command{} cmd.Use = "get-latest-version" - cmd.Short = `Get latest version of provider analytics dashboard.` - cmd.Long = `Get latest version of provider analytics dashboard.` + cmd.Short = `*Public Preview* Get latest version of provider analytics dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get latest version of provider analytics dashboard.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -184,13 +202,17 @@ func newUpdate() *cobra.Command { cmd.Flags().Int64Var(&updateReq.Version, "version", updateReq.Version, `this is the version of the dashboard template we want to update our user to current expectation is that it should be equal to latest version of the dashboard template.`) cmd.Use = "update ID" - cmd.Short = `Update provider analytics dashboard.` - cmd.Long = `Update provider analytics dashboard. + cmd.Short = `*Public Preview* Update provider analytics dashboard.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update provider analytics dashboard. Arguments: ID: id is immutable property and can't be updated.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/provider-providers/provider-providers.go b/cmd/workspace/provider-providers/provider-providers.go index 2646c166cdf..011068323ad 100755 --- a/cmd/workspace/provider-providers/provider-providers.go +++ b/cmd/workspace/provider-providers/provider-providers.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "provider-providers", - Short: `Providers are entities that manage assets in Marketplace.`, - Long: `Providers are entities that manage assets in Marketplace.`, + Use: "provider-providers", + Short: `*Public Preview* Providers are entities that manage assets in Marketplace.`, + Long: `This command is in Public Preview and may change without notice. + +Providers are entities that manage assets in Marketplace.`, GroupID: "marketplace", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -59,12 +65,16 @@ func newCreate() *cobra.Command { cmd.Flags().Var(&createJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "create" - cmd.Short = `Create a provider.` - cmd.Long = `Create a provider. + cmd.Short = `*Public Preview* Create a provider.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a provider. Create a provider` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -121,12 +131,16 @@ func newDelete() *cobra.Command { var deleteReq marketplace.DeleteProviderRequest cmd.Use = "delete ID" - cmd.Short = `Delete provider.` - cmd.Long = `Delete provider. + cmd.Short = `*Public Preview* Delete provider.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete provider. Delete provider` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -186,12 +200,16 @@ func newGet() *cobra.Command { var getReq marketplace.GetProviderRequest cmd.Use = "get ID" - cmd.Short = `Get provider.` - cmd.Long = `Get provider. + cmd.Short = `*Public Preview* Get provider.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get provider. Get provider profile` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -265,12 +283,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list" - cmd.Short = `List providers.` - cmd.Long = `List providers. + cmd.Short = `*Public Preview* List providers.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List providers. List provider profiles for account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -323,12 +345,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update ID" - cmd.Short = `Update provider.` - cmd.Long = `Update provider. + cmd.Short = `*Public Preview* Update provider.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update provider. Update provider profile` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/providers/providers.go b/cmd/workspace/providers/providers.go index 2c4521f553e..25d0ca73e9c 100755 --- a/cmd/workspace/providers/providers.go +++ b/cmd/workspace/providers/providers.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -78,6 +82,8 @@ func newCreate() *cobra.Command { Supported values: [DATABRICKS, OAUTH_CLIENT_CREDENTIALS, OIDC_FEDERATION, TOKEN]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -164,6 +170,8 @@ func newDelete() *cobra.Command { NAME: Name of the provider.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -234,6 +242,8 @@ func newGet() *cobra.Command { NAME: Name of the provider.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -320,6 +330,8 @@ func newList() *cobra.Command { the array.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -385,6 +397,8 @@ func newListProviderShareAssets() *cobra.Command { SHARE_NAME: The name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -458,6 +472,8 @@ func newListShares() *cobra.Command { NAME: Name of the provider in which to list shares.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -541,6 +557,8 @@ func newUpdate() *cobra.Command { NAME: Name of the provider.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/quality-monitor-v2/quality-monitor-v2.go b/cmd/workspace/quality-monitor-v2/quality-monitor-v2.go index 313cc96eb55..8290fbf92ac 100755 --- a/cmd/workspace/quality-monitor-v2/quality-monitor-v2.go +++ b/cmd/workspace/quality-monitor-v2/quality-monitor-v2.go @@ -20,14 +20,20 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "quality-monitor-v2", - Short: `Deprecated: Please use the Data Quality Monitoring API instead (REST: /api/data-quality/v1/monitors).`, - Long: `Deprecated: Please use the Data Quality Monitoring API instead (REST: + Short: `*Beta* Deprecated: Please use the Data Quality Monitoring API instead (REST: /api/data-quality/v1/monitors).`, + Long: `This command is in Beta and may change without notice. + +Deprecated: Please use the Data Quality Monitoring API instead (REST: /api/data-quality/v1/monitors). Manage data quality of UC objects (currently support schema).`, GroupID: "qualitymonitor", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newCreateQualityMonitor()) cmd.AddCommand(newDeleteQualityMonitor()) @@ -65,8 +71,10 @@ func newCreateQualityMonitor() *cobra.Command { // TODO: array: validity_check_configurations cmd.Use = "create-quality-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Create a quality monitor.` - cmd.Long = `Create a quality monitor. + cmd.Short = `*Beta* Create a quality monitor.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a quality monitor. Deprecated: Use Data Quality Monitoring API instead (/api/data-quality/v1/monitors). Create a quality monitor on UC object. @@ -76,6 +84,8 @@ func newCreateQualityMonitor() *cobra.Command { OBJECT_ID: The uuid of the request object. For example, schema id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -148,8 +158,10 @@ func newDeleteQualityMonitor() *cobra.Command { var deleteQualityMonitorReq qualitymonitorv2.DeleteQualityMonitorRequest cmd.Use = "delete-quality-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Delete a quality monitor.` - cmd.Long = `Delete a quality monitor. + cmd.Short = `*Beta* Delete a quality monitor.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a quality monitor. Deprecated: Use Data Quality Monitoring API instead (/api/data-quality/v1/monitors). Delete a quality monitor on UC object. @@ -159,6 +171,8 @@ func newDeleteQualityMonitor() *cobra.Command { OBJECT_ID: The uuid of the request object. For example, schema id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -207,8 +221,10 @@ func newGetQualityMonitor() *cobra.Command { var getQualityMonitorReq qualitymonitorv2.GetQualityMonitorRequest cmd.Use = "get-quality-monitor OBJECT_TYPE OBJECT_ID" - cmd.Short = `Read a quality monitor.` - cmd.Long = `Read a quality monitor. + cmd.Short = `*Beta* Read a quality monitor.` + cmd.Long = `This command is in Beta and may change without notice. + +Read a quality monitor. Deprecated: Use Data Quality Monitoring API instead (/api/data-quality/v1/monitors). Read a quality monitor on UC object. @@ -218,6 +234,8 @@ func newGetQualityMonitor() *cobra.Command { OBJECT_ID: The uuid of the request object. For example, schema id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -280,13 +298,17 @@ func newListQualityMonitor() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-quality-monitor" - cmd.Short = `List quality monitors.` - cmd.Long = `List quality monitors. + cmd.Short = `*Beta* List quality monitors.` + cmd.Long = `This command is in Beta and may change without notice. + +List quality monitors. Deprecated: Use Data Quality Monitoring API instead (/api/data-quality/v1/monitors). (Unimplemented) List quality monitors.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -343,8 +365,10 @@ func newUpdateQualityMonitor() *cobra.Command { // TODO: array: validity_check_configurations cmd.Use = "update-quality-monitor OBJECT_TYPE OBJECT_ID OBJECT_TYPE OBJECT_ID" - cmd.Short = `Update a quality monitor.` - cmd.Long = `Update a quality monitor. + cmd.Short = `*Beta* Update a quality monitor.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a quality monitor. Deprecated: Use Data Quality Monitoring API instead (/api/data-quality/v1/monitors). (Unimplemented) Update a quality monitor on @@ -357,6 +381,8 @@ func newUpdateQualityMonitor() *cobra.Command { OBJECT_ID: The uuid of the request object. For example, schema id.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/quality-monitors/quality-monitors.go b/cmd/workspace/quality-monitors/quality-monitors.go index ae801caa0ff..4e08a93ce46 100755 --- a/cmd/workspace/quality-monitors/quality-monitors.go +++ b/cmd/workspace/quality-monitors/quality-monitors.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCancelRefresh()) cmd.AddCommand(newCreate()) @@ -85,6 +89,8 @@ func newCancelRefresh() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -179,6 +185,8 @@ func newCreate() *cobra.Command { default user location via UI and Python APIs.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -275,6 +283,8 @@ func newDelete() *cobra.Command { corresponds to the {full_table_name_arg} arg in the endpoint path.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -345,6 +355,8 @@ func newGet() *cobra.Command { corresponds to the {full_table_name_arg} arg in the endpoint path.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -414,6 +426,8 @@ func newGetRefresh() *cobra.Command { REFRESH_ID: ID of the refresh.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -487,6 +501,8 @@ func newListRefreshes() *cobra.Command { insensitive and spaces are disallowed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -565,6 +581,8 @@ func newRegenerateDashboard() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -646,6 +664,8 @@ func newRunRefresh() *cobra.Command { insensitive and spaces are disallowed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -734,6 +754,8 @@ func newUpdate() *cobra.Command { be in 2-level format {catalog}.{schema}` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/queries-legacy/queries-legacy.go b/cmd/workspace/queries-legacy/queries-legacy.go index e3dcc2402b3..c82909cdca8 100755 --- a/cmd/workspace/queries-legacy/queries-legacy.go +++ b/cmd/workspace/queries-legacy/queries-legacy.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -96,6 +100,8 @@ func newCreate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -168,6 +174,8 @@ func newDelete() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -227,6 +235,8 @@ func newGet() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -306,6 +316,8 @@ func newList() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -367,6 +379,8 @@ func newRestore() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -438,6 +452,8 @@ func newUpdate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/queries/queries.go b/cmd/workspace/queries/queries.go index e654b765d7b..0e7bb05be59 100755 --- a/cmd/workspace/queries/queries.go +++ b/cmd/workspace/queries/queries.go @@ -29,6 +29,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,6 +76,8 @@ func newCreate() *cobra.Command { Creates a query.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -140,6 +146,8 @@ func newDelete() *cobra.Command { days.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -205,6 +213,8 @@ func newGet() *cobra.Command { Gets a query.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -286,6 +296,8 @@ func newList() *cobra.Command { throttling, service degradation, or a temporary ban.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -357,6 +369,8 @@ func newListVisualizations() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -446,6 +460,8 @@ func newUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/query-history/query-history.go b/cmd/workspace/query-history/query-history.go index b56fb149140..fc9b0a1aa6e 100755 --- a/cmd/workspace/query-history/query-history.go +++ b/cmd/workspace/query-history/query-history.go @@ -24,6 +24,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newList()) @@ -66,6 +70,8 @@ func newList() *cobra.Command { statuses.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/query-visualizations-legacy/query-visualizations-legacy.go b/cmd/workspace/query-visualizations-legacy/query-visualizations-legacy.go index 511ce8c60a6..caa073e6bf0 100755 --- a/cmd/workspace/query-visualizations-legacy/query-visualizations-legacy.go +++ b/cmd/workspace/query-visualizations-legacy/query-visualizations-legacy.go @@ -36,6 +36,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -81,6 +85,8 @@ func newCreate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -151,6 +157,8 @@ func newDelete() *cobra.Command { ID: Widget ID returned by :method:queryvisualizations/create` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -221,6 +229,8 @@ func newUpdate() *cobra.Command { [Learn more]: https://docs.databricks.com/en/sql/dbsql-api-latest.html` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/query-visualizations/query-visualizations.go b/cmd/workspace/query-visualizations/query-visualizations.go index 1017b51554e..44f9955cc3e 100755 --- a/cmd/workspace/query-visualizations/query-visualizations.go +++ b/cmd/workspace/query-visualizations/query-visualizations.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -70,6 +74,8 @@ func newCreate() *cobra.Command { Adds a visualization to a query.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -135,6 +141,8 @@ func newDelete() *cobra.Command { Removes a visualization.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -207,6 +215,8 @@ func newUpdate() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/recipient-activation/recipient-activation.go b/cmd/workspace/recipient-activation/recipient-activation.go index 8505cbfb98e..0260cd5785f 100755 --- a/cmd/workspace/recipient-activation/recipient-activation.go +++ b/cmd/workspace/recipient-activation/recipient-activation.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGetActivationUrlInfo()) cmd.AddCommand(newRetrieveToken()) @@ -68,6 +72,8 @@ func newGetActivationUrlInfo() *cobra.Command { ACTIVATION_URL: The one time activation url. It also accepts activation token.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -125,6 +131,8 @@ func newRetrieveToken() *cobra.Command { ACTIVATION_URL: The one time activation url. It also accepts activation token.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/recipient-federation-policies/recipient-federation-policies.go b/cmd/workspace/recipient-federation-policies/recipient-federation-policies.go index a8da9431289..8da45672e62 100755 --- a/cmd/workspace/recipient-federation-policies/recipient-federation-policies.go +++ b/cmd/workspace/recipient-federation-policies/recipient-federation-policies.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "recipient-federation-policies", - Short: `The Recipient Federation Policies APIs are only applicable in the open sharing model where the recipient object has the authentication type of OIDC_RECIPIENT, enabling data sharing from Databricks to non-Databricks recipients.`, - Long: `The Recipient Federation Policies APIs are only applicable in the open sharing + Short: `*Public Preview* The Recipient Federation Policies APIs are only applicable in the open sharing model where the recipient object has the authentication type of OIDC_RECIPIENT, enabling data sharing from Databricks to non-Databricks recipients.`, + Long: `This command is in Public Preview and may change without notice. + +The Recipient Federation Policies APIs are only applicable in the open sharing model where the recipient object has the authentication type of OIDC_RECIPIENT, enabling data sharing from Databricks to non-Databricks recipients. OIDC Token Federation enables secure, secret-less authentication @@ -50,6 +52,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -87,8 +93,10 @@ func newCreate() *cobra.Command { // TODO: complex arg: oidc_policy cmd.Use = "create RECIPIENT_NAME" - cmd.Short = `Create recipient federation policy.` - cmd.Long = `Create recipient federation policy. + cmd.Short = `*Public Preview* Create recipient federation policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create recipient federation policy. Create a federation policy for an OIDC_FEDERATION recipient for sharing data from Databricks to non-Databricks recipients. The caller must be the owner of @@ -122,6 +130,8 @@ func newCreate() *cobra.Command { policy is being created.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -182,8 +192,10 @@ func newDelete() *cobra.Command { var deleteReq sharing.DeleteFederationPolicyRequest cmd.Use = "delete RECIPIENT_NAME NAME" - cmd.Short = `Delete recipient federation policy.` - cmd.Long = `Delete recipient federation policy. + cmd.Short = `*Public Preview* Delete recipient federation policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete recipient federation policy. Deletes an existing federation policy for an OIDC_FEDERATION recipient. The caller must be the owner of the recipient. @@ -194,6 +206,8 @@ func newDelete() *cobra.Command { NAME: Name of the policy. This is the name of the policy to be deleted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -242,8 +256,10 @@ func newGetFederationPolicy() *cobra.Command { var getFederationPolicyReq sharing.GetFederationPolicyRequest cmd.Use = "get-federation-policy RECIPIENT_NAME NAME" - cmd.Short = `Get recipient federation policy.` - cmd.Long = `Get recipient federation policy. + cmd.Short = `*Public Preview* Get recipient federation policy.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get recipient federation policy. Reads an existing federation policy for an OIDC_FEDERATION recipient for sharing data from Databricks to non-Databricks recipients. The caller must @@ -255,6 +271,8 @@ func newGetFederationPolicy() *cobra.Command { NAME: Name of the policy. This is the name of the policy to be retrieved.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -317,8 +335,10 @@ func newList() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list RECIPIENT_NAME" - cmd.Short = `List recipient federation policies.` - cmd.Long = `List recipient federation policies. + cmd.Short = `*Public Preview* List recipient federation policies.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List recipient federation policies. Lists federation policies for an OIDC_FEDERATION recipient for sharing data from Databricks to non-Databricks recipients. The caller must have read access @@ -329,6 +349,8 @@ func newList() *cobra.Command { policies are being listed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/recipients/recipients.go b/cmd/workspace/recipients/recipients.go index 93eab8b7485..04da810e69b 100755 --- a/cmd/workspace/recipients/recipients.go +++ b/cmd/workspace/recipients/recipients.go @@ -42,6 +42,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -98,6 +102,8 @@ func newCreate() *cobra.Command { Supported values: [DATABRICKS, OAUTH_CLIENT_CREDENTIALS, OIDC_FEDERATION, TOKEN]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -184,6 +190,8 @@ func newDelete() *cobra.Command { NAME: Name of the recipient.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -242,6 +250,8 @@ func newGet() *cobra.Command { NAME: Name of the recipient.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -314,6 +324,8 @@ func newList() *cobra.Command { guarantee of a specific ordering of the elements in the array.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -380,6 +392,8 @@ func newRotateToken() *cobra.Command { immediately, negative number will return an error.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -468,6 +482,8 @@ func newSharePermissions() *cobra.Command { NAME: The name of the Recipient.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -537,6 +553,8 @@ func newUpdate() *cobra.Command { NAME: Name of the recipient.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/redash-config/redash-config.go b/cmd/workspace/redash-config/redash-config.go index ce173b6bb55..5497537c8a7 100755 --- a/cmd/workspace/redash-config/redash-config.go +++ b/cmd/workspace/redash-config/redash-config.go @@ -25,6 +25,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + // Add methods cmd.AddCommand(newGetConfig()) @@ -52,6 +56,8 @@ func newGetConfig() *cobra.Command { cmd.Long = `Read workspace configuration for Redash-v2.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/registered-models/registered-models.go b/cmd/workspace/registered-models/registered-models.go index e776c537aa3..66628aa810c 100755 --- a/cmd/workspace/registered-models/registered-models.go +++ b/cmd/workspace/registered-models/registered-models.go @@ -54,6 +54,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -121,6 +125,8 @@ func newCreate() *cobra.Command { schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -195,6 +201,8 @@ func newDelete() *cobra.Command { FULL_NAME: The three-level (fully qualified) name of the registered model` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -269,6 +277,8 @@ func newDeleteAlias() *cobra.Command { ALIAS: The name of the alias` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -334,6 +344,8 @@ func newGet() *cobra.Command { FULL_NAME: The three-level (fully qualified) name of the registered model` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -432,6 +444,8 @@ func newList() *cobra.Command { end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -500,6 +514,8 @@ func newSetAlias() *cobra.Command { VERSION_NUM: The version number of the model version to which the alias points` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -610,6 +626,8 @@ func newUpdate() *cobra.Command { FULL_NAME: The three-level (fully qualified) name of the registered model` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/repos/repos.go b/cmd/workspace/repos/repos.go index 40e4dacf3a7..dd8f1f3c53b 100755 --- a/cmd/workspace/repos/repos.go +++ b/cmd/workspace/repos/repos.go @@ -35,6 +35,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -85,11 +89,15 @@ func newCreate() *cobra.Command { Arguments: URL: URL of the Git repository to be linked. PROVIDER: Git provider. This field is case-insensitive. The available Git providers - are gitHub, bitbucketCloud, gitLab, azureDevOpsServices, - gitHubEnterprise, bitbucketServer, gitLabEnterpriseEdition and - awsCodeCommit.` + are gitHub, bitbucketCloud, gitLab, azureDevOpsServices (Azure + DevOps Services, including Microsoft Entra ID authentication), + gitHubEnterprise, bitbucketServer (Bitbucket Data Center), + gitLabEnterpriseEdition (GitLab Self-Managed), and awsCodeCommit + (deprecated by AWS, not accepting new customers).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -171,6 +179,8 @@ func newDelete() *cobra.Command { REPO_ID: The ID for the corresponding repo to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -242,6 +252,8 @@ func newGet() *cobra.Command { REPO_ID: ID of the Git folder (repo) object in the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -314,6 +326,8 @@ func newGetPermissionLevels() *cobra.Command { REPO_ID: The repo for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -384,6 +398,8 @@ func newGetPermissions() *cobra.Command { REPO_ID: The repo for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -464,6 +480,8 @@ func newList() *cobra.Command { next_page_token to iterate through additional pages.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -529,6 +547,8 @@ func newSetPermissions() *cobra.Command { REPO_ID: The repo for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -618,6 +638,8 @@ func newUpdate() *cobra.Command { REPO_ID: ID of the Git folder (repo) object in the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -707,6 +729,8 @@ func newUpdatePermissions() *cobra.Command { REPO_ID: The repo for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/resource-quotas/resource-quotas.go b/cmd/workspace/resource-quotas/resource-quotas.go index e5ac51456f4..b58233280e4 100755 --- a/cmd/workspace/resource-quotas/resource-quotas.go +++ b/cmd/workspace/resource-quotas/resource-quotas.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGetQuota()) cmd.AddCommand(newListQuotas()) @@ -75,6 +79,8 @@ func newGetQuota() *cobra.Command { added as a suffix.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -151,6 +157,8 @@ func newListQuotas() *cobra.Command { end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/restrict-workspace-admins/restrict-workspace-admins.go b/cmd/workspace/restrict-workspace-admins/restrict-workspace-admins.go index d81e8e34f03..bb947f91b34 100755 --- a/cmd/workspace/restrict-workspace-admins/restrict-workspace-admins.go +++ b/cmd/workspace/restrict-workspace-admins/restrict-workspace-admins.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "restrict-workspace-admins", - Short: `The Restrict Workspace Admins setting lets you control the capabilities of workspace admins.`, - Long: `The Restrict Workspace Admins setting lets you control the capabilities of + Short: `*Public Preview* The Restrict Workspace Admins setting lets you control the capabilities of workspace admins.`, + Long: `This command is in Public Preview and may change without notice. + +The Restrict Workspace Admins setting lets you control the capabilities of workspace admins. With the setting status set to ALLOW_ALL, workspace admins can create service principal personal access tokens on behalf of any service principal in their workspace. Workspace admins can also change a job owner to @@ -36,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -66,8 +72,10 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete the restrict workspace admins setting.` - cmd.Long = `Delete the restrict workspace admins setting. + cmd.Short = `*Public Preview* Delete the restrict workspace admins setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete the restrict workspace admins setting. Reverts the restrict workspace admins setting status for the workspace. A fresh etag needs to be provided in DELETE requests (as a query parameter). @@ -76,6 +84,8 @@ func newDelete() *cobra.Command { the request must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -124,12 +134,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the restrict workspace admins setting.` - cmd.Long = `Get the restrict workspace admins setting. + cmd.Short = `*Public Preview* Get the restrict workspace admins setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the restrict workspace admins setting. Gets the restrict workspace admins setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -179,8 +193,10 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the restrict workspace admins setting.` - cmd.Long = `Update the restrict workspace admins setting. + cmd.Short = `*Public Preview* Update the restrict workspace admins setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the restrict workspace admins setting. Updates the restrict workspace admins setting for the workspace. A fresh etag needs to be provided in PATCH requests (as part of the setting field). The @@ -189,6 +205,8 @@ func newUpdate() *cobra.Command { must be retried by using the fresh etag in the 409 response.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/rfa/rfa.go b/cmd/workspace/rfa/rfa.go index 51cd9b25cab..508c3db68a7 100755 --- a/cmd/workspace/rfa/rfa.go +++ b/cmd/workspace/rfa/rfa.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "rfa", - Short: `Request for Access enables users to request access for Unity Catalog securables.`, - Long: `Request for Access enables users to request access for Unity Catalog + Short: `*Public Preview* Request for Access enables users to request access for Unity Catalog securables.`, + Long: `This command is in Public Preview and may change without notice. + +Request for Access enables users to request access for Unity Catalog securables. These APIs provide a standardized way for securable owners (or users with @@ -30,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newBatchCreateAccessRequests()) cmd.AddCommand(newGetAccessRequestDestinations()) @@ -63,8 +69,10 @@ func newBatchCreateAccessRequests() *cobra.Command { // TODO: array: requests cmd.Use = "batch-create-access-requests" - cmd.Short = `Create Access Requests.` - cmd.Long = `Create Access Requests. + cmd.Short = `*Public Preview* Create Access Requests.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create Access Requests. Creates access requests for Unity Catalog permissions for a specified principal on a securable object. This Batch API can take in multiple @@ -77,6 +85,8 @@ func newBatchCreateAccessRequests() *cobra.Command { "registered_model", and "volume".` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -136,8 +146,10 @@ func newGetAccessRequestDestinations() *cobra.Command { var getAccessRequestDestinationsReq catalog.GetAccessRequestDestinationsRequest cmd.Use = "get-access-request-destinations SECURABLE_TYPE FULL_NAME" - cmd.Short = `Get Access Request Destinations.` - cmd.Long = `Get Access Request Destinations. + cmd.Short = `*Public Preview* Get Access Request Destinations.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get Access Request Destinations. Gets an array of access request destinations for the specified securable. Any caller can see URL destinations or the destinations on the metastore. @@ -153,6 +165,8 @@ func newGetAccessRequestDestinations() *cobra.Command { FULL_NAME: The full name of the securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -209,8 +223,10 @@ func newUpdateAccessRequestDestinations() *cobra.Command { // TODO: array: destinations cmd.Use = "update-access-request-destinations UPDATE_MASK SECURABLE" - cmd.Short = `Update Access Request Destinations.` - cmd.Long = `Update Access Request Destinations. + cmd.Short = `*Public Preview* Update Access Request Destinations.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update Access Request Destinations. Updates the access request destinations for the given securable. The caller must be a metastore admin, the owner of the securable, or a user that has the @@ -242,6 +258,8 @@ func newUpdateAccessRequestDestinations() *cobra.Command { or read.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/schemas/schemas.go b/cmd/workspace/schemas/schemas.go index dcc49042d12..768e6729bae 100755 --- a/cmd/workspace/schemas/schemas.go +++ b/cmd/workspace/schemas/schemas.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -79,6 +83,8 @@ func newCreate() *cobra.Command { CATALOG_NAME: Name of parent catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -163,6 +169,8 @@ func newDelete() *cobra.Command { FULL_NAME: Full name of the schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -223,6 +231,8 @@ func newGet() *cobra.Command { FULL_NAME: Full name of the schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -307,6 +317,8 @@ func newList() *cobra.Command { CATALOG_NAME: Parent catalog for schemas of interest.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -380,6 +392,8 @@ func newUpdate() *cobra.Command { FULL_NAME: Full name of the schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/secrets-uc/secrets-uc.go b/cmd/workspace/secrets-uc/secrets-uc.go new file mode 100755 index 00000000000..006a5f5d33d --- /dev/null +++ b/cmd/workspace/secrets-uc/secrets-uc.go @@ -0,0 +1,523 @@ +// Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +package secrets_uc + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + sdktime "github.com/databricks/databricks-sdk-go/common/types/time" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/spf13/cobra" +) + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var cmdOverrides []func(*cobra.Command) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "secrets-uc", + Short: `A secret is a Unity Catalog securable object that stores sensitive credential data (such as passwords, tokens, and keys) within a three-level namespace (**catalog_name.schema_name.secret_name**).`, + Long: `A secret is a Unity Catalog securable object that stores sensitive credential + data (such as passwords, tokens, and keys) within a three-level namespace + (**catalog_name.schema_name.secret_name**). + + Secrets can be managed using standard Unity Catalog permissions and are scoped + to a schema within a catalog.`, + GroupID: "catalog", + + // This service is being previewed; hide from help output. + Hidden: true, + RunE: root.ReportUnknownSubcommand, + } + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + // Add methods + cmd.AddCommand(newCreateSecret()) + cmd.AddCommand(newDeleteSecret()) + cmd.AddCommand(newGetSecret()) + cmd.AddCommand(newListSecrets()) + cmd.AddCommand(newUpdateSecret()) + + // Apply optional overrides to this command. + for _, fn := range cmdOverrides { + fn(cmd) + } + + return cmd +} + +// start create-secret command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createSecretOverrides []func( + *cobra.Command, + *catalog.CreateSecretRequest, +) + +func newCreateSecret() *cobra.Command { + cmd := &cobra.Command{} + + var createSecretReq catalog.CreateSecretRequest + createSecretReq.Secret = catalog.Secret{} + var createSecretJson flags.JsonFlag + + cmd.Flags().Var(&createSecretJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&createSecretReq.Secret.Comment, "comment", createSecretReq.Secret.Comment, `User-provided free-form text description of the secret.`) + var expireTimeParam string + cmd.Flags().StringVar(&expireTimeParam, "expire-time", expireTimeParam, `User-provided expiration time of the secret.`) + cmd.Flags().StringVar(&createSecretReq.Secret.Owner, "owner", createSecretReq.Secret.Owner, `The owner of the secret.`) + + cmd.Use = "create-secret NAME CATALOG_NAME SCHEMA_NAME VALUE" + cmd.Short = `Create a secret.` + cmd.Long = `Create a secret. + + Creates a new secret in Unity Catalog. + + You must be the owner of the parent schema or have the **CREATE_SECRET** and + **USE SCHEMA** privileges on the parent schema and **USE CATALOG** on the + parent catalog. + + The secret is stored in the specified catalog and schema, and the **value** + field contains the sensitive data to be securely stored. + + Arguments: + NAME: The name of the secret, relative to its parent schema. + CATALOG_NAME: The name of the catalog where the schema and the secret reside. + SCHEMA_NAME: The name of the schema where the secret resides. + VALUE: The secret value to store. This field is input-only and is not returned in + responses — use the **effective_value** field (via GetSecret with + **include_value** set to true) to read the secret value. The maximum size + is 60 KiB (pre-encryption). Accepted content includes passwords, tokens, + keys, and other sensitive credential data.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + 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 allowed. Provide 'name', 'catalog_name', 'schema_name', 'value' in your JSON input") + } + return nil + } + check := root.ExactArgs(4) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createSecretJson.Unmarshal(&createSecretReq.Secret) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + if !cmd.Flags().Changed("json") { + createSecretReq.Secret.Name = args[0] + } + if !cmd.Flags().Changed("json") { + createSecretReq.Secret.CatalogName = args[1] + } + if !cmd.Flags().Changed("json") { + createSecretReq.Secret.SchemaName = args[2] + } + if !cmd.Flags().Changed("json") { + createSecretReq.Secret.Value = args[3] + } + + if expireTimeParam != "" { + expireTimeBytes := []byte(fmt.Sprintf("\"%s\"", expireTimeParam)) + var expireTimeField sdktime.Time + err = json.Unmarshal(expireTimeBytes, &expireTimeField) + if err != nil { + return fmt.Errorf("invalid EXPIRE_TIME: %s", expireTimeParam) + } + createSecretReq.Secret.ExpireTime = &expireTimeField + } + + response, err := w.SecretsUc.CreateSecret(ctx, createSecretReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createSecretOverrides { + fn(cmd, &createSecretReq) + } + + return cmd +} + +// start delete-secret command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteSecretOverrides []func( + *cobra.Command, + *catalog.DeleteSecretRequest, +) + +func newDeleteSecret() *cobra.Command { + cmd := &cobra.Command{} + + var deleteSecretReq catalog.DeleteSecretRequest + + cmd.Use = "delete-secret FULL_NAME" + cmd.Short = `Delete a secret.` + cmd.Long = `Delete a secret. + + Deletes a secret by its three-level (fully qualified) name. + + You must be the owner of the secret or a metastore admin. + + Arguments: + FULL_NAME: The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**).` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteSecretReq.FullName = args[0] + + err = w.SecretsUc.DeleteSecret(ctx, deleteSecretReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteSecretOverrides { + fn(cmd, &deleteSecretReq) + } + + return cmd +} + +// start get-secret command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getSecretOverrides []func( + *cobra.Command, + *catalog.GetSecretRequest, +) + +func newGetSecret() *cobra.Command { + cmd := &cobra.Command{} + + var getSecretReq catalog.GetSecretRequest + + cmd.Flags().BoolVar(&getSecretReq.IncludeBrowse, "include-browse", getSecretReq.IncludeBrowse, `Whether to include secrets in the response for which you only have the **BROWSE** privilege, which limits access to metadata.`) + + cmd.Use = "get-secret FULL_NAME" + cmd.Short = `Get a secret.` + cmd.Long = `Get a secret. + + Gets a secret by its three-level (fully qualified) name. + + You must be a metastore admin, the owner of the secret, or have the **MANAGE** + privilege on the secret. + + The secret value isn't returned by default. To retrieve it, you must also have + the **READ_SECRET** privilege and set **include_value** to true in the + request. + + Arguments: + FULL_NAME: The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**).` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getSecretReq.FullName = args[0] + + response, err := w.SecretsUc.GetSecret(ctx, getSecretReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getSecretOverrides { + fn(cmd, &getSecretReq) + } + + return cmd +} + +// start list-secrets command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listSecretsOverrides []func( + *cobra.Command, + *catalog.ListSecretsRequest, +) + +func newListSecrets() *cobra.Command { + cmd := &cobra.Command{} + + var listSecretsReq catalog.ListSecretsRequest + // 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 listSecretsLimit int + + cmd.Flags().StringVar(&listSecretsReq.CatalogName, "catalog-name", listSecretsReq.CatalogName, `The name of the catalog under which to list secrets.`) + cmd.Flags().BoolVar(&listSecretsReq.IncludeBrowse, "include-browse", listSecretsReq.IncludeBrowse, `Whether to include secrets in the response for which you only have the **BROWSE** privilege, which limits access to metadata.`) + cmd.Flags().IntVar(&listSecretsReq.PageSize, "page-size", listSecretsReq.PageSize, `Maximum number of secrets to return.`) + cmd.Flags().StringVar(&listSecretsReq.SchemaName, "schema-name", listSecretsReq.SchemaName, `The name of the schema under which to list secrets.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listSecretsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listSecretsReq.PageToken, "page-token", listSecretsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-secrets" + cmd.Short = `List secrets.` + cmd.Long = `List secrets. + + Lists secrets in Unity Catalog. + + You must be a metastore admin, the owner of the secret, or have the **MANAGE** + privilege on the secret. + + Both **catalog_name** and **schema_name** must be specified together to filter + secrets within a specific schema. Results are paginated; use the + **page_token** field from the response to retrieve subsequent pages.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + response := w.SecretsUc.ListSecrets(ctx, listSecretsReq) + if listSecretsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listSecretsLimit) + } + if listSecretsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listSecretsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listSecretsOverrides { + fn(cmd, &listSecretsReq) + } + + return cmd +} + +// start update-secret command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateSecretOverrides []func( + *cobra.Command, + *catalog.UpdateSecretRequest, +) + +func newUpdateSecret() *cobra.Command { + cmd := &cobra.Command{} + + var updateSecretReq catalog.UpdateSecretRequest + updateSecretReq.Secret = catalog.Secret{} + var updateSecretJson flags.JsonFlag + + cmd.Flags().Var(&updateSecretJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&updateSecretReq.Secret.Comment, "comment", updateSecretReq.Secret.Comment, `User-provided free-form text description of the secret.`) + var expireTimeParam string + cmd.Flags().StringVar(&expireTimeParam, "expire-time", expireTimeParam, `User-provided expiration time of the secret.`) + cmd.Flags().StringVar(&updateSecretReq.Secret.Owner, "owner", updateSecretReq.Secret.Owner, `The owner of the secret.`) + + cmd.Use = "update-secret FULL_NAME UPDATE_MASK NAME CATALOG_NAME SCHEMA_NAME VALUE" + cmd.Short = `Update a secret.` + cmd.Long = `Update a secret. + + Updates an existing secret in Unity Catalog. + + You must be the owner of the secret or a metastore admin. If you are a + metastore admin, only the **owner** field can be changed. + + Use the **update_mask** field to specify which fields to update. Supported + updatable fields include **value**, **comment**, **owner**, and + **expire_time**. + + Arguments: + FULL_NAME: The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**). + UPDATE_MASK: The field mask specifying which fields of the secret to update. Supported + fields: **value**, **comment**, **owner**, **expire_time**. + NAME: The name of the secret, relative to its parent schema. + CATALOG_NAME: The name of the catalog where the schema and the secret reside. + SCHEMA_NAME: The name of the schema where the secret resides. + VALUE: The secret value to store. This field is input-only and is not returned in + responses — use the **effective_value** field (via GetSecret with + **include_value** set to true) to read the secret value. The maximum size + is 60 KiB (pre-encryption). Accepted content includes passwords, tokens, + keys, and other sensitive credential data.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only FULL_NAME, UPDATE_MASK as positional arguments. Provide 'name', 'catalog_name', 'schema_name', 'value' in your JSON input") + } + return nil + } + check := root.ExactArgs(6) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateSecretJson.Unmarshal(&updateSecretReq.Secret) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateSecretReq.FullName = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateSecretReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + updateSecretReq.Secret.Name = args[2] + } + if !cmd.Flags().Changed("json") { + updateSecretReq.Secret.CatalogName = args[3] + } + if !cmd.Flags().Changed("json") { + updateSecretReq.Secret.SchemaName = args[4] + } + if !cmd.Flags().Changed("json") { + updateSecretReq.Secret.Value = args[5] + } + + if expireTimeParam != "" { + expireTimeBytes := []byte(fmt.Sprintf("\"%s\"", expireTimeParam)) + var expireTimeField sdktime.Time + err = json.Unmarshal(expireTimeBytes, &expireTimeField) + if err != nil { + return fmt.Errorf("invalid EXPIRE_TIME: %s", expireTimeParam) + } + updateSecretReq.Secret.ExpireTime = &expireTimeField + } + + response, err := w.SecretsUc.UpdateSecret(ctx, updateSecretReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateSecretOverrides { + fn(cmd, &updateSecretReq) + } + + return cmd +} + +// end service SecretsUc diff --git a/cmd/workspace/secrets/secrets.go b/cmd/workspace/secrets/secrets.go index 0edd4f049f2..198abdcadf7 100755 --- a/cmd/workspace/secrets/secrets.go +++ b/cmd/workspace/secrets/secrets.go @@ -37,6 +37,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateScope()) cmd.AddCommand(newDeleteAcl()) @@ -124,6 +128,8 @@ func newCreateScope() *cobra.Command { SCOPE: Scope name requested by the user. Scope names are unique.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -218,6 +224,8 @@ func newDeleteAcl() *cobra.Command { PRINCIPAL: The principal to remove an existing ACL from.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -312,6 +320,8 @@ func newDeleteScope() *cobra.Command { SCOPE: Name of the scope to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -405,6 +415,8 @@ func newDeleteSecret() *cobra.Command { KEY: Name of the secret to delete.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -499,6 +511,8 @@ func newGetAcl() *cobra.Command { PRINCIPAL: The principal to fetch ACL information for.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -584,6 +598,8 @@ func newGetSecret() *cobra.Command { KEY: Name of the secret to fetch value information.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -664,6 +680,8 @@ func newListAcls() *cobra.Command { SCOPE: The name of the scope to fetch ACL information from.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -737,6 +755,8 @@ func newListScopes() *cobra.Command { API call.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -814,6 +834,8 @@ func newListSecrets() *cobra.Command { SCOPE: The name of the scope to list secrets within.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -912,6 +934,8 @@ func newPutAcl() *cobra.Command { Supported values: [MANAGE, READ, WRITE]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/service-principal-secrets-proxy/service-principal-secrets-proxy.go b/cmd/workspace/service-principal-secrets-proxy/service-principal-secrets-proxy.go index f556a26644c..1f0fde3cee8 100755 --- a/cmd/workspace/service-principal-secrets-proxy/service-principal-secrets-proxy.go +++ b/cmd/workspace/service-principal-secrets-proxy/service-principal-secrets-proxy.go @@ -40,6 +40,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -82,6 +86,8 @@ func newCreate() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -152,6 +158,8 @@ func newDelete() *cobra.Command { SECRET_ID: The secret ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -224,6 +232,8 @@ func newList() *cobra.Command { SERVICE_PRINCIPAL_ID: The service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/service-principals-v2/service-principals-v2.go b/cmd/workspace/service-principals-v2/service-principals-v2.go index 6e23e348923..2bce2b79cbf 100755 --- a/cmd/workspace/service-principals-v2/service-principals-v2.go +++ b/cmd/workspace/service-principals-v2/service-principals-v2.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "service-principals-v2", - Short: `Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.`, - Long: `Identities for use with jobs, automated tools, and systems such as scripts, + Short: `*Public Preview* Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.`, + Long: `This command is in Public Preview and may change without notice. + +Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. Databricks recommends creating service principals to run production jobs or modify production data. If all processes that act on production data run with service principals, interactive users do not need any @@ -31,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -75,12 +81,16 @@ func newCreate() *cobra.Command { // TODO: array: schemas cmd.Use = "create" - cmd.Short = `Create a service principal.` - cmd.Long = `Create a service principal. + cmd.Short = `*Public Preview* Create a service principal.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a service principal. Creates a new service principal in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -140,8 +150,10 @@ func newDelete() *cobra.Command { var deleteReq iam.DeleteServicePrincipalRequest cmd.Use = "delete ID" - cmd.Short = `Delete a service principal.` - cmd.Long = `Delete a service principal. + cmd.Short = `*Public Preview* Delete a service principal.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a service principal. Delete a single service principal in the Databricks workspace. @@ -149,6 +161,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a service principal in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -196,8 +210,10 @@ func newGet() *cobra.Command { var getReq iam.GetServicePrincipalRequest cmd.Use = "get ID" - cmd.Short = `Get service principal details.` - cmd.Long = `Get service principal details. + cmd.Short = `*Public Preview* Get service principal details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get service principal details. Gets the details for a single service principal define in the Databricks workspace. @@ -206,6 +222,8 @@ func newGet() *cobra.Command { ID: Unique ID for a service principal in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -273,12 +291,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" - cmd.Short = `List service principals.` - cmd.Long = `List service principals. + cmd.Short = `*Public Preview* List service principals.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List service principals. Gets the set of service principals associated with a Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -334,8 +356,10 @@ func newPatch() *cobra.Command { // TODO: array: schemas cmd.Use = "patch ID" - cmd.Short = `Update service principal details.` - cmd.Long = `Update service principal details. + cmd.Short = `*Public Preview* Update service principal details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update service principal details. Partially updates the details of a single service principal in the Databricks workspace. @@ -344,6 +368,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -415,8 +441,10 @@ func newUpdate() *cobra.Command { // TODO: array: schemas cmd.Use = "update ID" - cmd.Short = `Replace service principal.` - cmd.Long = `Replace service principal. + cmd.Short = `*Public Preview* Replace service principal.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Replace service principal. Updates the details of a single service principal. @@ -426,6 +454,8 @@ func newUpdate() *cobra.Command { ID: Databricks service principal ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/serving-endpoints/serving-endpoints.go b/cmd/workspace/serving-endpoints/serving-endpoints.go index 88c70401064..4462a0d488c 100755 --- a/cmd/workspace/serving-endpoints/serving-endpoints.go +++ b/cmd/workspace/serving-endpoints/serving-endpoints.go @@ -39,6 +39,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newBuildLogs()) cmd.AddCommand(newCreate()) @@ -97,6 +101,8 @@ func newBuildLogs() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -173,6 +179,8 @@ func newCreate() *cobra.Command { alphanumeric characters, dashes, and underscores.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -268,10 +276,14 @@ func newCreateProvisionedThroughputEndpoint() *cobra.Command { // TODO: array: tags cmd.Use = "create-provisioned-throughput-endpoint" - cmd.Short = `Create a new PT serving endpoint.` - cmd.Long = `Create a new PT serving endpoint.` + cmd.Short = `*Public Preview* Create a new PT serving endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a new PT serving endpoint.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -344,6 +356,8 @@ func newDelete() *cobra.Command { cmd.Long = `Delete a serving endpoint.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -402,6 +416,8 @@ func newExportMetrics() *cobra.Command { required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -460,6 +476,8 @@ func newGet() *cobra.Command { NAME: The name of the serving endpoint. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -508,8 +526,10 @@ func newGetOpenApi() *cobra.Command { var getOpenApiReq serving.GetOpenApiRequest cmd.Use = "get-open-api NAME" - cmd.Short = `Get the schema for a serving endpoint.` - cmd.Long = `Get the schema for a serving endpoint. + cmd.Short = `*Public Preview* Get the schema for a serving endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the schema for a serving endpoint. Get the query schema of the serving endpoint in OpenAPI format. The schema contains information for the supported paths, input and output format and @@ -520,6 +540,8 @@ func newGetOpenApi() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -578,6 +600,8 @@ func newGetPermissionLevels() *cobra.Command { SERVING_ENDPOINT_ID: The serving endpoint for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -636,6 +660,8 @@ func newGetPermissions() *cobra.Command { SERVING_ENDPOINT_ID: The serving endpoint for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -703,6 +729,8 @@ func newHttpRequest() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -768,6 +796,8 @@ func newList() *cobra.Command { cmd.Long = `Get all serving endpoints.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -823,6 +853,8 @@ func newLogs() *cobra.Command { is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -889,6 +921,8 @@ func newPatch() *cobra.Command { required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -954,8 +988,10 @@ func newPut() *cobra.Command { // TODO: array: rate_limits cmd.Use = "put NAME" - cmd.Short = `Update rate limits of a serving endpoint.` - cmd.Long = `Update rate limits of a serving endpoint. + cmd.Short = `*Public Preview* Update rate limits of a serving endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update rate limits of a serving endpoint. Deprecated: Please use AI Gateway to manage rate limits instead. @@ -964,6 +1000,8 @@ func newPut() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1045,6 +1083,8 @@ func newPutAiGateway() *cobra.Command { field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1134,6 +1174,8 @@ func newQuery() *cobra.Command { via the path parameter.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1210,6 +1252,8 @@ func newSetPermissions() *cobra.Command { SERVING_ENDPOINT_ID: The serving endpoint for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1296,6 +1340,8 @@ func newUpdateConfig() *cobra.Command { NAME: The name of the serving endpoint to update. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1383,6 +1429,8 @@ func newUpdateNotifications() *cobra.Command { This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1458,6 +1506,8 @@ func newUpdatePermissions() *cobra.Command { SERVING_ENDPOINT_ID: The serving endpoint for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -1527,8 +1577,10 @@ func newUpdateProvisionedThroughputEndpointConfig() *cobra.Command { cmd.Flags().Var(&updateProvisionedThroughputEndpointConfigJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update-provisioned-throughput-endpoint-config NAME" - cmd.Short = `Update config of a PT serving endpoint.` - cmd.Long = `Update config of a PT serving endpoint. + cmd.Short = `*Public Preview* Update config of a PT serving endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update config of a PT serving endpoint. Updates any combination of the pt endpoint's served entities, the compute configuration of those served entities, and the endpoint's traffic config. @@ -1538,6 +1590,8 @@ func newUpdateProvisionedThroughputEndpointConfig() *cobra.Command { NAME: The name of the pt endpoint to update. This field is required.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/settings/settings.go b/cmd/workspace/settings/settings.go index 7dedfbfab1b..8518f522dd8 100755 --- a/cmd/workspace/settings/settings.go +++ b/cmd/workspace/settings/settings.go @@ -37,6 +37,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add subservices cmd.AddCommand(aibi_dashboard_embedding_access_policy.New()) cmd.AddCommand(aibi_dashboard_embedding_approved_domains.New()) diff --git a/cmd/workspace/shares/shares.go b/cmd/workspace/shares/shares.go index 47615ce4d7f..b06b36389d8 100755 --- a/cmd/workspace/shares/shares.go +++ b/cmd/workspace/shares/shares.go @@ -30,6 +30,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -79,6 +83,8 @@ func newCreate() *cobra.Command { NAME: Name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -158,6 +164,8 @@ func newDelete() *cobra.Command { NAME: The name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -217,6 +225,8 @@ func newGet() *cobra.Command { NAME: The name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -287,6 +297,8 @@ func newListShares() *cobra.Command { ordering of the elements in the array.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -349,6 +361,8 @@ func newSharePermissions() *cobra.Command { NAME: The name of the Recipient.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -431,6 +445,8 @@ func newUpdate() *cobra.Command { NAME: The name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -511,6 +527,8 @@ func newUpdatePermissions() *cobra.Command { NAME: The name of the share.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/sql-results-download/sql-results-download.go b/cmd/workspace/sql-results-download/sql-results-download.go index b929a79cc49..049e94d6d5e 100755 --- a/cmd/workspace/sql-results-download/sql-results-download.go +++ b/cmd/workspace/sql-results-download/sql-results-download.go @@ -20,13 +20,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "sql-results-download", - Short: `Controls whether users within the workspace are allowed to download results from the SQL Editor and AI/BI Dashboards UIs.`, - Long: `Controls whether users within the workspace are allowed to download results + Short: `*Public Preview* Controls whether users within the workspace are allowed to download results from the SQL Editor and AI/BI Dashboards UIs.`, + Long: `This command is in Public Preview and may change without notice. + +Controls whether users within the workspace are allowed to download results from the SQL Editor and AI/BI Dashboards UIs. By default, this setting is enabled (set to true)`, RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newGet()) @@ -57,12 +63,16 @@ func newDelete() *cobra.Command { cmd.Flags().StringVar(&deleteReq.Etag, "etag", deleteReq.Etag, `etag used for versioning.`) cmd.Use = "delete" - cmd.Short = `Delete the SQL Results Download setting.` - cmd.Long = `Delete the SQL Results Download setting. + cmd.Short = `*Public Preview* Delete the SQL Results Download setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete the SQL Results Download setting. Reverts the SQL Results Download setting to its default value.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -111,12 +121,16 @@ func newGet() *cobra.Command { cmd.Flags().StringVar(&getReq.Etag, "etag", getReq.Etag, `etag used for versioning.`) cmd.Use = "get" - cmd.Short = `Get the SQL Results Download setting.` - cmd.Long = `Get the SQL Results Download setting. + cmd.Short = `*Public Preview* Get the SQL Results Download setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get the SQL Results Download setting. Gets the SQL Results Download setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -166,12 +180,16 @@ func newUpdate() *cobra.Command { cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update" - cmd.Short = `Update the SQL Results Download setting.` - cmd.Long = `Update the SQL Results Download setting. + cmd.Short = `*Public Preview* Update the SQL Results Download setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the SQL Results Download setting. Updates the SQL Results Download setting.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/storage-credentials/storage-credentials.go b/cmd/workspace/storage-credentials/storage-credentials.go index 878ba367a8f..ffeec286005 100755 --- a/cmd/workspace/storage-credentials/storage-credentials.go +++ b/cmd/workspace/storage-credentials/storage-credentials.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -94,6 +98,8 @@ func newCreate() *cobra.Command { credentials within the metastore.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -175,6 +181,8 @@ func newDelete() *cobra.Command { NAME: Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -233,6 +241,8 @@ func newGet() *cobra.Command { NAME: Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -314,6 +324,8 @@ func newList() *cobra.Command { indication that the end of results has been reached.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -391,6 +403,8 @@ func newUpdate() *cobra.Command { NAME: Name of the storage credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -481,6 +495,8 @@ func newValidate() *cobra.Command { credential.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/supervisor-agents/supervisor-agents.go b/cmd/workspace/supervisor-agents/supervisor-agents.go new file mode 100755 index 00000000000..b8a654de92a --- /dev/null +++ b/cmd/workspace/supervisor-agents/supervisor-agents.go @@ -0,0 +1,1559 @@ +// Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +package supervisor_agents + +import ( + "fmt" + "strings" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/common/types/fieldmask" + "github.com/databricks/databricks-sdk-go/service/supervisoragents" + "github.com/spf13/cobra" +) + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var cmdOverrides []func(*cobra.Command) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "supervisor-agents", + Short: `*Beta* Manage Supervisor Agents and related resources.`, + Long: `This command is in Beta and may change without notice. + +Manage Supervisor Agents and related resources.`, + GroupID: "agentbricks", + RunE: root.ReportUnknownSubcommand, + } + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + // Add methods + cmd.AddCommand(newCreateExample()) + cmd.AddCommand(newCreateSupervisorAgent()) + cmd.AddCommand(newCreateTool()) + cmd.AddCommand(newDeleteExample()) + cmd.AddCommand(newDeleteSupervisorAgent()) + cmd.AddCommand(newDeleteTool()) + cmd.AddCommand(newGetExample()) + cmd.AddCommand(newGetPermissionLevels()) + cmd.AddCommand(newGetPermissions()) + cmd.AddCommand(newGetSupervisorAgent()) + cmd.AddCommand(newGetTool()) + cmd.AddCommand(newListExamples()) + cmd.AddCommand(newListSupervisorAgents()) + cmd.AddCommand(newListTools()) + cmd.AddCommand(newSetPermissions()) + cmd.AddCommand(newUpdateExample()) + cmd.AddCommand(newUpdatePermissions()) + cmd.AddCommand(newUpdateSupervisorAgent()) + cmd.AddCommand(newUpdateTool()) + + // Apply optional overrides to this command. + for _, fn := range cmdOverrides { + fn(cmd) + } + + return cmd +} + +// start create-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createExampleOverrides []func( + *cobra.Command, + *supervisoragents.CreateExampleRequest, +) + +func newCreateExample() *cobra.Command { + cmd := &cobra.Command{} + + var createExampleReq supervisoragents.CreateExampleRequest + createExampleReq.Example = supervisoragents.Example{} + var createExampleJson flags.JsonFlag + + cmd.Flags().Var(&createExampleJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&createExampleReq.Example.Name, "name", createExampleReq.Example.Name, `Full resource name: supervisor-agents/{supervisor_agent_id}/examples/{example_id}.`) + + cmd.Use = "create-example PARENT QUESTION GUIDELINES" + cmd.Short = `*Beta* Create an example for a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Create an example for a Supervisor Agent. + + Creates an example for a Supervisor Agent. + + Arguments: + PARENT: Parent resource where this example will be created. Format: + supervisor-agents/{supervisor_agent_id} + QUESTION: The example question. + GUIDELINES: Guidelines for answering the question.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(1)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT as positional arguments. Provide 'question', 'guidelines' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createExampleJson.Unmarshal(&createExampleReq.Example) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createExampleReq.Parent = args[0] + if !cmd.Flags().Changed("json") { + createExampleReq.Example.Question = args[1] + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[2], &createExampleReq.Example.Guidelines) + if err != nil { + return fmt.Errorf("invalid GUIDELINES: %s", args[2]) + } + + } + + response, err := w.SupervisorAgents.CreateExample(ctx, createExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createExampleOverrides { + fn(cmd, &createExampleReq) + } + + return cmd +} + +// start create-supervisor-agent command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createSupervisorAgentOverrides []func( + *cobra.Command, + *supervisoragents.CreateSupervisorAgentRequest, +) + +func newCreateSupervisorAgent() *cobra.Command { + cmd := &cobra.Command{} + + var createSupervisorAgentReq supervisoragents.CreateSupervisorAgentRequest + createSupervisorAgentReq.SupervisorAgent = supervisoragents.SupervisorAgent{} + var createSupervisorAgentJson flags.JsonFlag + + cmd.Flags().Var(&createSupervisorAgentJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&createSupervisorAgentReq.SupervisorAgent.Description, "description", createSupervisorAgentReq.SupervisorAgent.Description, `Description of what this agent can do (user-facing).`) + cmd.Flags().StringVar(&createSupervisorAgentReq.SupervisorAgent.Instructions, "instructions", createSupervisorAgentReq.SupervisorAgent.Instructions, `Optional natural-language instructions for the supervisor agent.`) + cmd.Flags().StringVar(&createSupervisorAgentReq.SupervisorAgent.Name, "name", createSupervisorAgentReq.SupervisorAgent.Name, `The resource name of the SupervisorAgent.`) + + cmd.Use = "create-supervisor-agent DISPLAY_NAME" + cmd.Short = `*Beta* Create a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Supervisor Agent. + + Creates a new Supervisor Agent. + + Arguments: + DISPLAY_NAME: The display name of the Supervisor Agent, unique at workspace level.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + 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 allowed. Provide 'display_name' in your JSON input") + } + return nil + } + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createSupervisorAgentJson.Unmarshal(&createSupervisorAgentReq.SupervisorAgent) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + if !cmd.Flags().Changed("json") { + createSupervisorAgentReq.SupervisorAgent.DisplayName = args[0] + } + + response, err := w.SupervisorAgents.CreateSupervisorAgent(ctx, createSupervisorAgentReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createSupervisorAgentOverrides { + fn(cmd, &createSupervisorAgentReq) + } + + return cmd +} + +// start create-tool command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createToolOverrides []func( + *cobra.Command, + *supervisoragents.CreateToolRequest, +) + +func newCreateTool() *cobra.Command { + cmd := &cobra.Command{} + + var createToolReq supervisoragents.CreateToolRequest + createToolReq.Tool = supervisoragents.Tool{} + var createToolJson flags.JsonFlag + + cmd.Flags().Var(&createToolJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: complex arg: app + cmd.Flags().StringVar(&createToolReq.Tool.Description, "description", createToolReq.Tool.Description, `Description of what this tool does (user-facing).`) + // TODO: complex arg: genie_space + // TODO: complex arg: knowledge_assistant + cmd.Flags().StringVar(&createToolReq.Tool.Name, "name", createToolReq.Tool.Name, `Full resource name: supervisor-agents/{supervisor_agent_id}/tools/{tool_id}.`) + // TODO: complex arg: uc_connection + // TODO: complex arg: uc_function + // TODO: complex arg: volume + + cmd.Use = "create-tool PARENT TOOL_ID TOOL_TYPE" + cmd.Short = `*Beta* Create a Tool.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a Tool. + + Creates a Tool under a Supervisor Agent. Specify one of "genie_space", + "knowledge_assistant", "uc_function", "uc_connection", "app", "volume", + "dashboard", "table", "vector_search_index", "catalog", "schema", + "supervisor_agent", "web_search" in the request body. The legacy values + "lakeview_dashboard" and "uc_table" are also accepted and remain equivalent to + "dashboard" and "table" respectively. + + Arguments: + PARENT: Parent resource where this tool will be created. Format: + supervisor-agents/{supervisor_agent_id} + TOOL_ID: The ID to use for the tool, which will become the final component of the + tool's resource name. + TOOL_TYPE: Tool type. Must be one of: "genie_space", "knowledge_assistant", + "uc_function", "uc_connection", "app", "volume", "dashboard", + "serving_endpoint", "table", "vector_search_index", "catalog", "schema", + "supervisor_agent", "web_search". The legacy values "lakeview_dashboard" + and "uc_table" are also accepted and remain equivalent to "dashboard" and + "table" respectively.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PARENT, TOOL_ID as positional arguments. Provide 'tool_type' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createToolJson.Unmarshal(&createToolReq.Tool) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + createToolReq.Parent = args[0] + createToolReq.ToolId = args[1] + if !cmd.Flags().Changed("json") { + createToolReq.Tool.ToolType = args[2] + } + + response, err := w.SupervisorAgents.CreateTool(ctx, createToolReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createToolOverrides { + fn(cmd, &createToolReq) + } + + return cmd +} + +// start delete-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteExampleOverrides []func( + *cobra.Command, + *supervisoragents.DeleteExampleRequest, +) + +func newDeleteExample() *cobra.Command { + cmd := &cobra.Command{} + + var deleteExampleReq supervisoragents.DeleteExampleRequest + + cmd.Use = "delete-example NAME" + cmd.Short = `*Beta* Delete an example from a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete an example from a Supervisor Agent. + + Deletes an example from a Supervisor Agent. + + Arguments: + NAME: The resource name of the example to delete. Format: + supervisor-agents/{supervisor_agent_id}/examples/{example_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteExampleReq.Name = args[0] + + err = w.SupervisorAgents.DeleteExample(ctx, deleteExampleReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteExampleOverrides { + fn(cmd, &deleteExampleReq) + } + + return cmd +} + +// start delete-supervisor-agent command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteSupervisorAgentOverrides []func( + *cobra.Command, + *supervisoragents.DeleteSupervisorAgentRequest, +) + +func newDeleteSupervisorAgent() *cobra.Command { + cmd := &cobra.Command{} + + var deleteSupervisorAgentReq supervisoragents.DeleteSupervisorAgentRequest + + cmd.Use = "delete-supervisor-agent NAME" + cmd.Short = `*Beta* Delete a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Supervisor Agent. + + Deletes a Supervisor Agent. + + Arguments: + NAME: The resource name of the Supervisor Agent. Format: + supervisor-agents/{supervisor_agent_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteSupervisorAgentReq.Name = args[0] + + err = w.SupervisorAgents.DeleteSupervisorAgent(ctx, deleteSupervisorAgentReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteSupervisorAgentOverrides { + fn(cmd, &deleteSupervisorAgentReq) + } + + return cmd +} + +// start delete-tool command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteToolOverrides []func( + *cobra.Command, + *supervisoragents.DeleteToolRequest, +) + +func newDeleteTool() *cobra.Command { + cmd := &cobra.Command{} + + var deleteToolReq supervisoragents.DeleteToolRequest + + cmd.Use = "delete-tool NAME" + cmd.Short = `*Beta* Delete a Tool.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a Tool. + + Deletes a Tool. + + Arguments: + NAME: The resource name of the Tool. Format: + supervisor-agents/{supervisor_agent_id}/tools/{tool_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + deleteToolReq.Name = args[0] + + err = w.SupervisorAgents.DeleteTool(ctx, deleteToolReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteToolOverrides { + fn(cmd, &deleteToolReq) + } + + return cmd +} + +// start get-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getExampleOverrides []func( + *cobra.Command, + *supervisoragents.GetExampleRequest, +) + +func newGetExample() *cobra.Command { + cmd := &cobra.Command{} + + var getExampleReq supervisoragents.GetExampleRequest + + cmd.Use = "get-example NAME" + cmd.Short = `*Beta* Get an example from a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Get an example from a Supervisor Agent. + + Gets an example from a Supervisor Agent. + + Arguments: + NAME: The resource name of the example. Format: + supervisor-agents/{supervisor_agent_id}/examples/{example_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getExampleReq.Name = args[0] + + response, err := w.SupervisorAgents.GetExample(ctx, getExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getExampleOverrides { + fn(cmd, &getExampleReq) + } + + return cmd +} + +// start get-permission-levels command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionLevelsOverrides []func( + *cobra.Command, + *supervisoragents.GetSupervisorAgentPermissionLevelsRequest, +) + +func newGetPermissionLevels() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionLevelsReq supervisoragents.GetSupervisorAgentPermissionLevelsRequest + + cmd.Use = "get-permission-levels SUPERVISOR_AGENT_ID" + cmd.Short = `*Beta* Get supervisor agent permission levels.` + cmd.Long = `This command is in Beta and may change without notice. + +Get supervisor agent permission levels. + + Gets the permission levels that a user can have on an object. + + Arguments: + SUPERVISOR_AGENT_ID: The supervisor agent for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionLevelsReq.SupervisorAgentId = args[0] + + response, err := w.SupervisorAgents.GetPermissionLevels(ctx, getPermissionLevelsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionLevelsOverrides { + fn(cmd, &getPermissionLevelsReq) + } + + return cmd +} + +// start get-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionsOverrides []func( + *cobra.Command, + *supervisoragents.GetSupervisorAgentPermissionsRequest, +) + +func newGetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionsReq supervisoragents.GetSupervisorAgentPermissionsRequest + + cmd.Use = "get-permissions SUPERVISOR_AGENT_ID" + cmd.Short = `*Beta* Get supervisor agent permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Get supervisor agent permissions. + + Gets the permissions of a supervisor agent. Supervisor agents can inherit + permissions from their root object. + + Arguments: + SUPERVISOR_AGENT_ID: The supervisor agent for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionsReq.SupervisorAgentId = args[0] + + response, err := w.SupervisorAgents.GetPermissions(ctx, getPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionsOverrides { + fn(cmd, &getPermissionsReq) + } + + return cmd +} + +// start get-supervisor-agent command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getSupervisorAgentOverrides []func( + *cobra.Command, + *supervisoragents.GetSupervisorAgentRequest, +) + +func newGetSupervisorAgent() *cobra.Command { + cmd := &cobra.Command{} + + var getSupervisorAgentReq supervisoragents.GetSupervisorAgentRequest + + cmd.Use = "get-supervisor-agent NAME" + cmd.Short = `*Beta* Get a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Supervisor Agent. + + Gets a Supervisor Agent. + + Arguments: + NAME: The resource name of the Supervisor Agent. Format: + supervisor-agents/{supervisor_agent_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getSupervisorAgentReq.Name = args[0] + + response, err := w.SupervisorAgents.GetSupervisorAgent(ctx, getSupervisorAgentReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getSupervisorAgentOverrides { + fn(cmd, &getSupervisorAgentReq) + } + + return cmd +} + +// start get-tool command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getToolOverrides []func( + *cobra.Command, + *supervisoragents.GetToolRequest, +) + +func newGetTool() *cobra.Command { + cmd := &cobra.Command{} + + var getToolReq supervisoragents.GetToolRequest + + cmd.Use = "get-tool NAME" + cmd.Short = `*Beta* Get a Tool.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a Tool. + + Gets a Tool. + + Arguments: + NAME: The resource name of the Tool. Format: + supervisor-agents/{supervisor_agent_id}/tools/{tool_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getToolReq.Name = args[0] + + response, err := w.SupervisorAgents.GetTool(ctx, getToolReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getToolOverrides { + fn(cmd, &getToolReq) + } + + return cmd +} + +// start list-examples command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listExamplesOverrides []func( + *cobra.Command, + *supervisoragents.ListExamplesRequest, +) + +func newListExamples() *cobra.Command { + cmd := &cobra.Command{} + + var listExamplesReq supervisoragents.ListExamplesRequest + // 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 listExamplesLimit int + + cmd.Flags().IntVar(&listExamplesReq.PageSize, "page-size", listExamplesReq.PageSize, `The maximum number of examples to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listExamplesLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listExamplesReq.PageToken, "page-token", listExamplesReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-examples PARENT" + cmd.Short = `*Beta* List examples for a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +List examples for a Supervisor Agent. + + Lists examples under a Supervisor Agent. + + Arguments: + PARENT: Parent resource to list from. Format: + supervisor-agents/{supervisor_agent_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listExamplesReq.Parent = args[0] + + response := w.SupervisorAgents.ListExamples(ctx, listExamplesReq) + if listExamplesLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listExamplesLimit) + } + if listExamplesLimit > 0 { + ctx = cmdio.WithLimit(ctx, listExamplesLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listExamplesOverrides { + fn(cmd, &listExamplesReq) + } + + return cmd +} + +// start list-supervisor-agents command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listSupervisorAgentsOverrides []func( + *cobra.Command, + *supervisoragents.ListSupervisorAgentsRequest, +) + +func newListSupervisorAgents() *cobra.Command { + cmd := &cobra.Command{} + + var listSupervisorAgentsReq supervisoragents.ListSupervisorAgentsRequest + // 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 listSupervisorAgentsLimit int + + cmd.Flags().IntVar(&listSupervisorAgentsReq.PageSize, "page-size", listSupervisorAgentsReq.PageSize, `The maximum number of supervisor agents to return.`) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listSupervisorAgentsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listSupervisorAgentsReq.PageToken, "page-token", listSupervisorAgentsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-supervisor-agents" + cmd.Short = `*Beta* List Supervisor Agents.` + cmd.Long = `This command is in Beta and may change without notice. + +List Supervisor Agents. + + Lists Supervisor Agents.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + response := w.SupervisorAgents.ListSupervisorAgents(ctx, listSupervisorAgentsReq) + if listSupervisorAgentsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listSupervisorAgentsLimit) + } + if listSupervisorAgentsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listSupervisorAgentsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listSupervisorAgentsOverrides { + fn(cmd, &listSupervisorAgentsReq) + } + + return cmd +} + +// start list-tools command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listToolsOverrides []func( + *cobra.Command, + *supervisoragents.ListToolsRequest, +) + +func newListTools() *cobra.Command { + cmd := &cobra.Command{} + + var listToolsReq supervisoragents.ListToolsRequest + // 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 listToolsLimit int + + cmd.Flags().IntVar(&listToolsReq.PageSize, "page-size", listToolsReq.PageSize, ``) + + // Limit flag for total result capping. + cmd.Flags().IntVar(&listToolsLimit, "limit", 0, `Maximum number of results to return.`) + + // Hidden pagination flags (internal API parameters). + cmd.Flags().StringVar(&listToolsReq.PageToken, "page-token", listToolsReq.PageToken, `Pagination token.`) + cmd.Flags().Lookup("page-token").Hidden = true + + cmd.Use = "list-tools PARENT" + cmd.Short = `*Beta* List Tools.` + cmd.Long = `This command is in Beta and may change without notice. + +List Tools. + + Lists Tools under a Supervisor Agent. + + Arguments: + PARENT: Parent resource to list from. Format: + supervisor-agents/{supervisor_agent_id}` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + listToolsReq.Parent = args[0] + + response := w.SupervisorAgents.ListTools(ctx, listToolsReq) + if listToolsLimit < 0 { + return fmt.Errorf("--limit must be a non-negative integer, got %d", listToolsLimit) + } + if listToolsLimit > 0 { + ctx = cmdio.WithLimit(ctx, listToolsLimit) + } + + return cmdio.RenderIterator(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listToolsOverrides { + fn(cmd, &listToolsReq) + } + + return cmd +} + +// start set-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var setPermissionsOverrides []func( + *cobra.Command, + *supervisoragents.SupervisorAgentPermissionsRequest, +) + +func newSetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var setPermissionsReq supervisoragents.SupervisorAgentPermissionsRequest + var setPermissionsJson flags.JsonFlag + + cmd.Flags().Var(&setPermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "set-permissions SUPERVISOR_AGENT_ID" + cmd.Short = `*Beta* Set supervisor agent permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Set supervisor agent permissions. + + Sets permissions on an object, replacing existing permissions if they exist. + Deletes all direct permissions if none are specified. Objects can inherit + permissions from their root object. + + Arguments: + SUPERVISOR_AGENT_ID: The supervisor agent for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := setPermissionsJson.Unmarshal(&setPermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + setPermissionsReq.SupervisorAgentId = args[0] + + response, err := w.SupervisorAgents.SetPermissions(ctx, setPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range setPermissionsOverrides { + fn(cmd, &setPermissionsReq) + } + + return cmd +} + +// start update-example command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateExampleOverrides []func( + *cobra.Command, + *supervisoragents.UpdateExampleRequest, +) + +func newUpdateExample() *cobra.Command { + cmd := &cobra.Command{} + + var updateExampleReq supervisoragents.UpdateExampleRequest + updateExampleReq.Example = supervisoragents.Example{} + var updateExampleJson flags.JsonFlag + + cmd.Flags().Var(&updateExampleJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&updateExampleReq.Example.Name, "name", updateExampleReq.Example.Name, `Full resource name: supervisor-agents/{supervisor_agent_id}/examples/{example_id}.`) + + cmd.Use = "update-example NAME UPDATE_MASK QUESTION GUIDELINES" + cmd.Short = `*Beta* Update an example in a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Update an example in a Supervisor Agent. + + Updates an example in a Supervisor Agent. + + Arguments: + NAME: The resource name of the example to update. Format: + supervisor-agents/{supervisor_agent_id}/examples/{example_id} + UPDATE_MASK: Comma-delimited list of fields to update on the example. Allowed values: + question, guidelines. Examples: - question - question,guidelines + QUESTION: The example question. + GUIDELINES: Guidelines for answering the question.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME, UPDATE_MASK as positional arguments. Provide 'question', 'guidelines' in your JSON input") + } + return nil + } + check := root.ExactArgs(4) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateExampleJson.Unmarshal(&updateExampleReq.Example) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateExampleReq.Name = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateExampleReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + updateExampleReq.Example.Question = args[2] + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[3], &updateExampleReq.Example.Guidelines) + if err != nil { + return fmt.Errorf("invalid GUIDELINES: %s", args[3]) + } + + } + + response, err := w.SupervisorAgents.UpdateExample(ctx, updateExampleReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateExampleOverrides { + fn(cmd, &updateExampleReq) + } + + return cmd +} + +// start update-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updatePermissionsOverrides []func( + *cobra.Command, + *supervisoragents.SupervisorAgentPermissionsRequest, +) + +func newUpdatePermissions() *cobra.Command { + cmd := &cobra.Command{} + + var updatePermissionsReq supervisoragents.SupervisorAgentPermissionsRequest + var updatePermissionsJson flags.JsonFlag + + cmd.Flags().Var(&updatePermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "update-permissions SUPERVISOR_AGENT_ID" + cmd.Short = `*Beta* Update supervisor agent permissions.` + cmd.Long = `This command is in Beta and may change without notice. + +Update supervisor agent permissions. + + Updates the permissions on a supervisor agent. Supervisor agents can inherit + permissions from their root object. + + Arguments: + SUPERVISOR_AGENT_ID: The supervisor agent for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updatePermissionsJson.Unmarshal(&updatePermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updatePermissionsReq.SupervisorAgentId = args[0] + + response, err := w.SupervisorAgents.UpdatePermissions(ctx, updatePermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updatePermissionsOverrides { + fn(cmd, &updatePermissionsReq) + } + + return cmd +} + +// start update-supervisor-agent command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateSupervisorAgentOverrides []func( + *cobra.Command, + *supervisoragents.UpdateSupervisorAgentRequest, +) + +func newUpdateSupervisorAgent() *cobra.Command { + cmd := &cobra.Command{} + + var updateSupervisorAgentReq supervisoragents.UpdateSupervisorAgentRequest + updateSupervisorAgentReq.SupervisorAgent = supervisoragents.SupervisorAgent{} + var updateSupervisorAgentJson flags.JsonFlag + + cmd.Flags().Var(&updateSupervisorAgentJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().StringVar(&updateSupervisorAgentReq.SupervisorAgent.Description, "description", updateSupervisorAgentReq.SupervisorAgent.Description, `Description of what this agent can do (user-facing).`) + cmd.Flags().StringVar(&updateSupervisorAgentReq.SupervisorAgent.Instructions, "instructions", updateSupervisorAgentReq.SupervisorAgent.Instructions, `Optional natural-language instructions for the supervisor agent.`) + cmd.Flags().StringVar(&updateSupervisorAgentReq.SupervisorAgent.Name, "name", updateSupervisorAgentReq.SupervisorAgent.Name, `The resource name of the SupervisorAgent.`) + + cmd.Use = "update-supervisor-agent NAME UPDATE_MASK DISPLAY_NAME" + cmd.Short = `*Beta* Update a Supervisor Agent.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Supervisor Agent. + + Updates a Supervisor Agent. The fields that are required depend on the paths + specified in update_mask. Only fields included in the mask will be updated. + + Arguments: + NAME: The resource name of the SupervisorAgent. Format: + supervisor-agents/{supervisor_agent_id} + UPDATE_MASK: Field mask for fields to be updated. + DISPLAY_NAME: The display name of the Supervisor Agent, unique at workspace level.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME, UPDATE_MASK as positional arguments. Provide 'display_name' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateSupervisorAgentJson.Unmarshal(&updateSupervisorAgentReq.SupervisorAgent) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateSupervisorAgentReq.Name = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateSupervisorAgentReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + updateSupervisorAgentReq.SupervisorAgent.DisplayName = args[2] + } + + response, err := w.SupervisorAgents.UpdateSupervisorAgent(ctx, updateSupervisorAgentReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateSupervisorAgentOverrides { + fn(cmd, &updateSupervisorAgentReq) + } + + return cmd +} + +// start update-tool command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateToolOverrides []func( + *cobra.Command, + *supervisoragents.UpdateToolRequest, +) + +func newUpdateTool() *cobra.Command { + cmd := &cobra.Command{} + + var updateToolReq supervisoragents.UpdateToolRequest + updateToolReq.Tool = supervisoragents.Tool{} + var updateToolJson flags.JsonFlag + + cmd.Flags().Var(&updateToolJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: complex arg: app + cmd.Flags().StringVar(&updateToolReq.Tool.Description, "description", updateToolReq.Tool.Description, `Description of what this tool does (user-facing).`) + // TODO: complex arg: genie_space + // TODO: complex arg: knowledge_assistant + cmd.Flags().StringVar(&updateToolReq.Tool.Name, "name", updateToolReq.Tool.Name, `Full resource name: supervisor-agents/{supervisor_agent_id}/tools/{tool_id}.`) + // TODO: complex arg: uc_connection + // TODO: complex arg: uc_function + // TODO: complex arg: volume + + cmd.Use = "update-tool NAME UPDATE_MASK TOOL_TYPE" + cmd.Short = `*Beta* Update a Tool.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a Tool. + + Updates a Tool. Only the description field can be updated. To change + immutable fields such as tool type, spec, or tool ID, delete the tool and + recreate it. + + Arguments: + NAME: Full resource name: + supervisor-agents/{supervisor_agent_id}/tools/{tool_id} + UPDATE_MASK: Field mask for fields to be updated. + TOOL_TYPE: Tool type. Must be one of: "genie_space", "knowledge_assistant", + "uc_function", "uc_connection", "app", "volume", "dashboard", + "serving_endpoint", "table", "vector_search_index", "catalog", "schema", + "supervisor_agent", "web_search". The legacy values "lakeview_dashboard" + and "uc_table" are also accepted and remain equivalent to "dashboard" and + "table" respectively.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only NAME, UPDATE_MASK as positional arguments. Provide 'tool_type' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateToolJson.Unmarshal(&updateToolReq.Tool) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updateToolReq.Name = args[0] + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateToolReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + updateToolReq.Tool.ToolType = args[2] + } + + response, err := w.SupervisorAgents.UpdateTool(ctx, updateToolReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateToolOverrides { + fn(cmd, &updateToolReq) + } + + return cmd +} + +// end service SupervisorAgents diff --git a/cmd/workspace/system-schemas/system-schemas.go b/cmd/workspace/system-schemas/system-schemas.go index 8b41369ba37..a1bcba0cc7c 100755 --- a/cmd/workspace/system-schemas/system-schemas.go +++ b/cmd/workspace/system-schemas/system-schemas.go @@ -20,14 +20,20 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "system-schemas", - Short: `A system schema is a schema that lives within the system catalog.`, - Long: `A system schema is a schema that lives within the system catalog. A system + Short: `*Public Preview* A system schema is a schema that lives within the system catalog.`, + Long: `This command is in Public Preview and may change without notice. + +A system schema is a schema that lives within the system catalog. A system schema may contain information about customer usage of Unity Catalog such as audit-logs, billing-logs, lineage information, etc.`, GroupID: "catalog", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newDisable()) cmd.AddCommand(newEnable()) @@ -56,8 +62,10 @@ func newDisable() *cobra.Command { var disableReq catalog.DisableRequest cmd.Use = "disable METASTORE_ID SCHEMA_NAME" - cmd.Short = `Disable a system schema.` - cmd.Long = `Disable a system schema. + cmd.Short = `*Public Preview* Disable a system schema.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Disable a system schema. Disables the system schema and removes it from the system catalog. The caller must be an account admin or a metastore admin. @@ -67,6 +75,8 @@ func newDisable() *cobra.Command { SCHEMA_NAME: Full name of the system schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -120,8 +130,10 @@ func newEnable() *cobra.Command { cmd.Flags().StringVar(&enableReq.CatalogName, "catalog-name", enableReq.CatalogName, `the catalog for which the system schema is to enabled in.`) cmd.Use = "enable METASTORE_ID SCHEMA_NAME" - cmd.Short = `Enable a system schema.` - cmd.Long = `Enable a system schema. + cmd.Short = `*Public Preview* Enable a system schema.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Enable a system schema. Enables the system schema and adds it to the system catalog. The caller must be an account admin or a metastore admin. @@ -131,6 +143,8 @@ func newEnable() *cobra.Command { SCHEMA_NAME: Full name of the system schema.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -204,8 +218,10 @@ func newList() *cobra.Command { cmd.Flags().Lookup("max-results").Hidden = true cmd.Use = "list METASTORE_ID" - cmd.Short = `List system schemas.` - cmd.Long = `List system schemas. + cmd.Short = `*Public Preview* List system schemas.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List system schemas. Gets an array of system schemas for a metastore. The caller must be an account admin or a metastore admin. @@ -222,6 +238,8 @@ func newList() *cobra.Command { METASTORE_ID: The ID for the metastore in which the system schema resides.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/table-constraints/table-constraints.go b/cmd/workspace/table-constraints/table-constraints.go index 369907f0108..f01a2c9b345 100755 --- a/cmd/workspace/table-constraints/table-constraints.go +++ b/cmd/workspace/table-constraints/table-constraints.go @@ -38,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -83,6 +87,8 @@ func newCreate() *cobra.Command { table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -160,6 +166,8 @@ func newDelete() *cobra.Command { constraints.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) diff --git a/cmd/workspace/tables/tables.go b/cmd/workspace/tables/tables.go index 3003fdfd3a7..c132bc9fa10 100755 --- a/cmd/workspace/tables/tables.go +++ b/cmd/workspace/tables/tables.go @@ -34,6 +34,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -72,8 +76,10 @@ func newCreate() *cobra.Command { // TODO: map via StringToStringVar: properties cmd.Use = "create NAME CATALOG_NAME SCHEMA_NAME TABLE_TYPE DATA_SOURCE_FORMAT STORAGE_LOCATION" - cmd.Short = `Create a table.` - cmd.Long = `Create a table. + cmd.Short = `*Public Preview* Create a table.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a table. Creates a new table in the specified catalog and schema. @@ -152,6 +158,8 @@ func newCreate() *cobra.Command { STORAGE_LOCATION: Storage root URL for table (for **MANAGED**, **EXTERNAL** tables).` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -257,6 +265,8 @@ func newDelete() *cobra.Command { FULL_NAME: Full name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -320,6 +330,8 @@ func newExists() *cobra.Command { FULL_NAME: Full name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -387,6 +399,8 @@ func newGet() *cobra.Command { FULL_NAME: Full name of the table.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -480,6 +494,8 @@ func newList() *cobra.Command { SCHEMA_NAME: Parent schema of tables.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -572,6 +588,8 @@ func newListSummaries() *cobra.Command { CATALOG_NAME: Name of parent catalog for tables of interest.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -644,6 +662,8 @@ func newUpdate() *cobra.Command { cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/tag-policies/tag-policies.go b/cmd/workspace/tag-policies/tag-policies.go index 178d9b399a4..02ef1a7af31 100755 --- a/cmd/workspace/tag-policies/tag-policies.go +++ b/cmd/workspace/tag-policies/tag-policies.go @@ -32,6 +32,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateTagPolicy()) cmd.AddCommand(newDeleteTagPolicy()) @@ -80,6 +84,8 @@ func newCreateTagPolicy() *cobra.Command { [Tag Policy Terraform documentation]: https://registry.terraform.io/providers/databricks/databricks/latest/docs/resources/tag_policy` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -159,6 +165,8 @@ func newDeleteTagPolicy() *cobra.Command { [Tag Policy Terraform documentation]: https://registry.terraform.io/providers/databricks/databricks/latest/docs/resources/tag_policy` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -217,6 +225,8 @@ func newGetTagPolicy() *cobra.Command { [Tag Policy Terraform documentation]: https://registry.terraform.io/providers/databricks/databricks/latest/docs/data-sources/tag_policy` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -289,6 +299,8 @@ func newListTagPolicies() *cobra.Command { [Tag Policy Terraform documentation]: https://registry.terraform.io/providers/databricks/databricks/latest/docs/data-sources/tag_policies` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -370,6 +382,8 @@ func newUpdateTagPolicy() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/temporary-path-credentials/temporary-path-credentials.go b/cmd/workspace/temporary-path-credentials/temporary-path-credentials.go index 98eda3ccf54..a8abcf57ad7 100755 --- a/cmd/workspace/temporary-path-credentials/temporary-path-credentials.go +++ b/cmd/workspace/temporary-path-credentials/temporary-path-credentials.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "temporary-path-credentials", - Short: `Temporary Path Credentials refer to short-lived, downscoped credentials used to access external cloud storage locations registered in Databricks.`, - Long: `Temporary Path Credentials refer to short-lived, downscoped credentials used + Short: `*Public Preview* Temporary Path Credentials refer to short-lived, downscoped credentials used to access external cloud storage locations registered in Databricks.`, + Long: `This command is in Public Preview and may change without notice. + +Temporary Path Credentials refer to short-lived, downscoped credentials used to access external cloud storage locations registered in Databricks. These credentials are employed to provide secure and time-limited access to data in cloud environments such as AWS, Azure, and Google Cloud. Each cloud provider @@ -35,22 +37,24 @@ func New() *cobra.Command { temporary path credentials API, a metastore admin needs to enable the external_access_enabled flag (off by default) at the metastore level. A user needs to be granted the EXTERNAL USE LOCATION permission by external location - owner. For requests on existing external tables, user also needs to be granted - the EXTERNAL USE SCHEMA permission at the schema level by catalog admin. + owner. For requests on existing external tables and external volumes, user + also needs to be granted the EXTERNAL USE SCHEMA permission at the schema + level by catalog owner. Note that EXTERNAL USE SCHEMA is a schema level permission that can only be - granted by catalog admin explicitly and is not included in schema ownership or + granted by catalog owner explicitly and is not included in schema ownership or ALL PRIVILEGES on the schema for security reasons. Similarly, EXTERNAL USE LOCATION is an external location level permission that can only be granted by external location owner explicitly and is not included in external location - ownership or ALL PRIVILEGES on the external location for security reasons. - - This API only supports temporary path credentials for external locations and - external tables, and volumes will be supported in the future.`, + ownership or ALL PRIVILEGES on the external location for security reasons.`, GroupID: "catalog", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGenerateTemporaryPathCredentials()) @@ -82,8 +86,10 @@ func newGenerateTemporaryPathCredentials() *cobra.Command { cmd.Flags().BoolVar(&generateTemporaryPathCredentialsReq.DryRun, "dry-run", generateTemporaryPathCredentialsReq.DryRun, `Optional.`) cmd.Use = "generate-temporary-path-credentials URL OPERATION" - cmd.Short = `Generate a temporary path credential.` - cmd.Long = `Generate a temporary path credential. + cmd.Short = `*Public Preview* Generate a temporary path credential.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Generate a temporary path credential. Get a short-lived credential for directly accessing cloud storage locations registered in Databricks. The Generate Temporary Path Credentials API is only @@ -102,6 +108,8 @@ func newGenerateTemporaryPathCredentials() *cobra.Command { Supported values: [PATH_CREATE_TABLE, PATH_READ, PATH_READ_WRITE]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/temporary-table-credentials/temporary-table-credentials.go b/cmd/workspace/temporary-table-credentials/temporary-table-credentials.go index ebc93e63ec1..2967d260194 100755 --- a/cmd/workspace/temporary-table-credentials/temporary-table-credentials.go +++ b/cmd/workspace/temporary-table-credentials/temporary-table-credentials.go @@ -18,8 +18,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "temporary-table-credentials", - Short: `Temporary Table Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where table data is stored in Databricks.`, - Long: `Temporary Table Credentials refer to short-lived, downscoped credentials used + Short: `*Public Preview* Temporary Table Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where table data is stored in Databricks.`, + Long: `This command is in Public Preview and may change without notice. + +Temporary Table Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where table data is stored in Databricks. These credentials are employed to provide secure and time-limited access to data in cloud environments such as AWS, Azure, and Google Cloud. Each cloud @@ -33,13 +35,17 @@ func New() *cobra.Command { temporary table credentials API, a metastore admin needs to enable the external_access_enabled flag (off by default) at the metastore level, and user needs to be granted the EXTERNAL USE SCHEMA permission at the schema level by - catalog admin. Note that EXTERNAL USE SCHEMA is a schema level permission that - can only be granted by catalog admin explicitly and is not included in schema + catalog owner. Note that EXTERNAL USE SCHEMA is a schema level permission that + can only be granted by catalog owner explicitly and is not included in schema ownership or ALL PRIVILEGES on the schema for security reasons.`, GroupID: "catalog", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGenerateTemporaryTableCredentials()) @@ -72,8 +78,10 @@ func newGenerateTemporaryTableCredentials() *cobra.Command { cmd.Flags().StringVar(&generateTemporaryTableCredentialsReq.TableId, "table-id", generateTemporaryTableCredentialsReq.TableId, `UUID of the table to read or write.`) cmd.Use = "generate-temporary-table-credentials" - cmd.Short = `Generate a temporary table credential.` - cmd.Long = `Generate a temporary table credential. + cmd.Short = `*Public Preview* Generate a temporary table credential.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Generate a temporary table credential. Get a short-lived credential for directly accessing the table data on cloud storage. The metastore must have **external_access_enabled** flag set to true @@ -81,6 +89,8 @@ func newGenerateTemporaryTableCredentials() *cobra.Command { the parent schema and this privilege can only be granted by catalog owners.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/temporary-volume-credentials/temporary-volume-credentials.go b/cmd/workspace/temporary-volume-credentials/temporary-volume-credentials.go new file mode 100755 index 00000000000..1afe54d31f5 --- /dev/null +++ b/cmd/workspace/temporary-volume-credentials/temporary-volume-credentials.go @@ -0,0 +1,138 @@ +// Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +package temporary_volume_credentials + +import ( + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/service/catalog" + "github.com/spf13/cobra" +) + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var cmdOverrides []func(*cobra.Command) + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "temporary-volume-credentials", + Short: `*Public Preview* Temporary Volume Credentials refer to short-lived, downscoped credentials used to access cloud storage locations where volume data is stored in Databricks.`, + Long: `This command is in Public Preview and may change without notice. + +Temporary Volume Credentials refer to short-lived, downscoped credentials used + to access cloud storage locations where volume data is stored in Databricks. + These credentials are employed to provide secure and time-limited access to + data in cloud environments such as AWS, Azure, and Google Cloud. Each cloud + provider has its own type of credentials: AWS uses temporary session tokens + via AWS Security Token Service (STS), Azure utilizes Shared Access Signatures + (SAS) for its data storage services, and Google Cloud supports temporary + credentials through OAuth 2.0. + + Temporary volume credentials ensure that data access is limited in scope and + duration, reducing the risk of unauthorized access or misuse. To use the + temporary volume credentials API, a metastore admin needs to enable the + external_access_enabled flag (off by default) at the metastore level, and user + needs to be granted the EXTERNAL USE SCHEMA permission at the schema level by + catalog owner. Note that EXTERNAL USE SCHEMA is a schema level permission that + can only be granted by catalog owner explicitly and is not included in schema + ownership or ALL PRIVILEGES on the schema for security reasons.`, + GroupID: "catalog", + RunE: root.ReportUnknownSubcommand, + } + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + + // Add methods + cmd.AddCommand(newGenerateTemporaryVolumeCredentials()) + + // Apply optional overrides to this command. + for _, fn := range cmdOverrides { + fn(cmd) + } + + return cmd +} + +// start generate-temporary-volume-credentials command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var generateTemporaryVolumeCredentialsOverrides []func( + *cobra.Command, + *catalog.GenerateTemporaryVolumeCredentialRequest, +) + +func newGenerateTemporaryVolumeCredentials() *cobra.Command { + cmd := &cobra.Command{} + + var generateTemporaryVolumeCredentialsReq catalog.GenerateTemporaryVolumeCredentialRequest + var generateTemporaryVolumeCredentialsJson flags.JsonFlag + + cmd.Flags().Var(&generateTemporaryVolumeCredentialsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Flags().Var(&generateTemporaryVolumeCredentialsReq.Operation, "operation", `The operation performed against the volume data, either READ_VOLUME or WRITE_VOLUME. Supported values: [READ_VOLUME, WRITE_VOLUME]`) + cmd.Flags().StringVar(&generateTemporaryVolumeCredentialsReq.VolumeId, "volume-id", generateTemporaryVolumeCredentialsReq.VolumeId, `Id of the volume to read or write.`) + + cmd.Use = "generate-temporary-volume-credentials" + cmd.Short = `*Public Preview* Generate a temporary volume credential.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Generate a temporary volume credential. + + Get a short-lived credential for directly accessing the volume data on cloud + storage. The metastore must have **external_access_enabled** flag set to true + (default false). The caller must have the **EXTERNAL_USE_SCHEMA** privilege on + the parent schema and this privilege can only be granted by catalog owners.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := generateTemporaryVolumeCredentialsJson.Unmarshal(&generateTemporaryVolumeCredentialsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + + response, err := w.TemporaryVolumeCredentials.GenerateTemporaryVolumeCredentials(ctx, generateTemporaryVolumeCredentialsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range generateTemporaryVolumeCredentialsOverrides { + fn(cmd, &generateTemporaryVolumeCredentialsReq) + } + + return cmd +} + +// end service TemporaryVolumeCredentials diff --git a/cmd/workspace/token-management/token-management.go b/cmd/workspace/token-management/token-management.go index b62e185f460..a89fab4ff33 100755 --- a/cmd/workspace/token-management/token-management.go +++ b/cmd/workspace/token-management/token-management.go @@ -28,6 +28,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateOboToken()) cmd.AddCommand(newDelete()) @@ -77,6 +81,8 @@ func newCreateOboToken() *cobra.Command { APPLICATION_ID: Application ID of the service principal.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -171,6 +177,8 @@ func newDelete() *cobra.Command { TOKEN_ID: The ID of the token to revoke.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -239,6 +247,8 @@ func newGet() *cobra.Command { TOKEN_ID: The ID of the token to get.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -302,6 +312,8 @@ func newGetPermissionLevels() *cobra.Command { Gets the permission levels that a user can have on an object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -346,6 +358,8 @@ func newGetPermissions() *cobra.Command { root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -404,6 +418,8 @@ func newList() *cobra.Command { Lists all tokens associated with the specified workspace or user.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -466,6 +482,8 @@ func newSetPermissions() *cobra.Command { permissions from their root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -537,6 +555,8 @@ func newUpdatePermissions() *cobra.Command { their root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/tokens/tokens.go b/cmd/workspace/tokens/tokens.go index 5acda52a379..e7411bc27f0 100755 --- a/cmd/workspace/tokens/tokens.go +++ b/cmd/workspace/tokens/tokens.go @@ -27,10 +27,15 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) cmd.AddCommand(newList()) + cmd.AddCommand(newUpdate()) // Apply optional overrides to this command. for _, fn := range cmdOverrides { @@ -71,6 +76,8 @@ func newCreate() *cobra.Command { an error **QUOTA_EXCEEDED**.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -145,6 +152,8 @@ func newDelete() *cobra.Command { TOKEN_ID: The ID of the token to be revoked.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -240,6 +249,8 @@ func newList() *cobra.Command { Lists all the valid tokens for a user-workspace pair.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -268,4 +279,83 @@ func newList() *cobra.Command { return cmd } +// start update command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateOverrides []func( + *cobra.Command, + *settings.UpdateTokenRequest, +) + +func newUpdate() *cobra.Command { + cmd := &cobra.Command{} + + var updateReq settings.UpdateTokenRequest + var updateJson flags.JsonFlag + + cmd.Flags().Var(&updateJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + cmd.Use = "update TOKEN_ID" + cmd.Short = `Update a user token.` + cmd.Long = `Update a user token. + + Updates the comment or scopes of a token. + + If a token with the specified ID is not valid, this call returns an error + **RESOURCE_DOES_NOT_EXIST**. + + Arguments: + TOKEN_ID: The SHA-256 hash of the token to be updated.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateJson.Unmarshal(&updateReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } else { + return fmt.Errorf("please provide command input in JSON format by specifying the --json flag") + } + updateReq.TokenId = args[0] + + response, err := w.Tokens.Update(ctx, updateReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateOverrides { + fn(cmd, &updateReq) + } + + return cmd +} + // end service Tokens diff --git a/cmd/workspace/users-v2/users-v2.go b/cmd/workspace/users-v2/users-v2.go index e54d8c2377f..3d7b0c63415 100755 --- a/cmd/workspace/users-v2/users-v2.go +++ b/cmd/workspace/users-v2/users-v2.go @@ -20,8 +20,10 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "users-v2", - Short: `User identities recognized by Databricks and represented by email addresses.`, - Long: `User identities recognized by Databricks and represented by email addresses. + Short: `*Public Preview* User identities recognized by Databricks and represented by email addresses.`, + Long: `This command is in Public Preview and may change without notice. + +User identities recognized by Databricks and represented by email addresses. Databricks recommends using SCIM provisioning to sync users and groups automatically from your identity provider to your Databricks workspace. SCIM @@ -36,6 +38,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -86,13 +92,17 @@ func newCreate() *cobra.Command { cmd.Flags().StringVar(&createReq.UserName, "user-name", createReq.UserName, `Email address of the Databricks user.`) cmd.Use = "create" - cmd.Short = `Create a new user.` - cmd.Long = `Create a new user. + cmd.Short = `*Public Preview* Create a new user.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Create a new user. Creates a new user in the Databricks workspace. This new user will also be added to the Databricks account.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -152,8 +162,10 @@ func newDelete() *cobra.Command { var deleteReq iam.DeleteUserRequest cmd.Use = "delete ID" - cmd.Short = `Delete a user.` - cmd.Long = `Delete a user. + cmd.Short = `*Public Preview* Delete a user.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Delete a user. Deletes a user. Deleting a user from a Databricks workspace also removes objects associated with the user. @@ -162,6 +174,8 @@ func newDelete() *cobra.Command { ID: Unique ID for a user in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -217,8 +231,10 @@ func newGet() *cobra.Command { cmd.Flags().IntVar(&getReq.StartIndex, "start-index", getReq.StartIndex, `Specifies the index of the first result.`) cmd.Use = "get ID" - cmd.Short = `Get user details.` - cmd.Long = `Get user details. + cmd.Short = `*Public Preview* Get user details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get user details. Gets information for a specific user in Databricks workspace. @@ -226,6 +242,8 @@ func newGet() *cobra.Command { ID: Unique ID for a user in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -274,12 +292,16 @@ func newGetPermissionLevels() *cobra.Command { var getPermissionLevelsReq iam.GetPasswordPermissionLevelsRequest cmd.Use = "get-permission-levels" - cmd.Short = `Get password permission levels.` - cmd.Long = `Get password permission levels. + cmd.Short = `*Public Preview* Get password permission levels.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get password permission levels. Gets the permission levels that a user can have on an object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -321,13 +343,17 @@ func newGetPermissions() *cobra.Command { var getPermissionsReq iam.GetPasswordPermissionsRequest cmd.Use = "get-permissions" - cmd.Short = `Get password permissions.` - cmd.Long = `Get password permissions. + cmd.Short = `*Public Preview* Get password permissions.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get password permissions. Gets the permissions of all passwords. Passwords can inherit permissions from their root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -388,12 +414,16 @@ func newList() *cobra.Command { cmd.Flags().Lookup("count").Hidden = true cmd.Use = "list" - cmd.Short = `List users.` - cmd.Long = `List users. + cmd.Short = `*Public Preview* List users.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List users. Gets details for all the users associated with a Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -449,8 +479,10 @@ func newPatch() *cobra.Command { // TODO: array: schemas cmd.Use = "patch ID" - cmd.Short = `Update user details.` - cmd.Long = `Update user details. + cmd.Short = `*Public Preview* Update user details.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update user details. Partially updates a user resource by applying the supplied operations on specific user attributes. @@ -459,6 +491,8 @@ func newPatch() *cobra.Command { ID: Unique ID in the Databricks workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -523,14 +557,18 @@ func newSetPermissions() *cobra.Command { // TODO: array: access_control_list cmd.Use = "set-permissions" - cmd.Short = `Set password permissions.` - cmd.Long = `Set password permissions. + cmd.Short = `*Public Preview* Set password permissions.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Set password permissions. Sets permissions on an object, replacing existing permissions if they exist. Deletes all direct permissions if none are specified. Objects can inherit permissions from their root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -604,8 +642,10 @@ func newUpdate() *cobra.Command { cmd.Flags().StringVar(&updateReq.UserName, "user-name", updateReq.UserName, `Email address of the Databricks user.`) cmd.Use = "update ID" - cmd.Short = `Replace a user.` - cmd.Long = `Replace a user. + cmd.Short = `*Public Preview* Replace a user.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Replace a user. Replaces a user's information with the data supplied in request. @@ -613,6 +653,8 @@ func newUpdate() *cobra.Command { ID: Databricks user ID.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -677,13 +719,17 @@ func newUpdatePermissions() *cobra.Command { // TODO: array: access_control_list cmd.Use = "update-permissions" - cmd.Short = `Update password permissions.` - cmd.Long = `Update password permissions. + cmd.Short = `*Public Preview* Update password permissions.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update password permissions. Updates the permissions on all passwords. Passwords can inherit permissions from their root object.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) diff --git a/cmd/workspace/vector-search-endpoints/vector-search-endpoints.go b/cmd/workspace/vector-search-endpoints/vector-search-endpoints.go index 6a0afdd565c..9342d2999d0 100755 --- a/cmd/workspace/vector-search-endpoints/vector-search-endpoints.go +++ b/cmd/workspace/vector-search-endpoints/vector-search-endpoints.go @@ -21,21 +21,29 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "vector-search-endpoints", - Short: `**Endpoint**: Represents the compute resources to host vector search indexes.`, - Long: `**Endpoint**: Represents the compute resources to host vector search indexes.`, + Short: `**Endpoint**: Represents the compute resources to host AI Search indexes.`, + Long: `**Endpoint**: Represents the compute resources to host AI Search indexes.`, GroupID: "vectorsearch", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateEndpoint()) cmd.AddCommand(newDeleteEndpoint()) cmd.AddCommand(newGetEndpoint()) + cmd.AddCommand(newGetPermissionLevels()) + cmd.AddCommand(newGetPermissions()) cmd.AddCommand(newListEndpoints()) cmd.AddCommand(newPatchEndpoint()) cmd.AddCommand(newRetrieveUserVisibleMetrics()) + cmd.AddCommand(newSetPermissions()) cmd.AddCommand(newUpdateEndpointBudgetPolicy()) cmd.AddCommand(newUpdateEndpointCustomTags()) + cmd.AddCommand(newUpdatePermissions()) // Apply optional overrides to this command. for _, fn := range cmdOverrides { @@ -69,7 +77,7 @@ func newCreateEndpoint() *cobra.Command { cmd.Flags().Var(&createEndpointJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Flags().StringVar(&createEndpointReq.BudgetPolicyId, "budget-policy-id", createEndpointReq.BudgetPolicyId, `The budget policy id to be applied.`) - cmd.Flags().Int64Var(&createEndpointReq.MinQps, "min-qps", createEndpointReq.MinQps, `Min QPS for the endpoint.`) + cmd.Flags().Int64Var(&createEndpointReq.TargetQps, "target-qps", createEndpointReq.TargetQps, `Target QPS for the endpoint.`) cmd.Flags().StringVar(&createEndpointReq.UsagePolicyId, "usage-policy-id", createEndpointReq.UsagePolicyId, `The usage policy id to be applied once we've migrated to usage policies.`) cmd.Use = "create-endpoint NAME ENDPOINT_TYPE" @@ -79,11 +87,13 @@ func newCreateEndpoint() *cobra.Command { Create a new endpoint. Arguments: - NAME: Name of the vector search endpoint + NAME: Name of the AI Search endpoint ENDPOINT_TYPE: Type of endpoint Supported values: [STANDARD, STORAGE_OPTIMIZED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -181,12 +191,14 @@ func newDeleteEndpoint() *cobra.Command { cmd.Short = `Delete an endpoint.` cmd.Long = `Delete an endpoint. - Delete a vector search endpoint. + Delete an AI Search endpoint. Arguments: - ENDPOINT_NAME: Name of the vector search endpoint` + ENDPOINT_NAME: Name of the AI Search endpoint` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -237,12 +249,14 @@ func newGetEndpoint() *cobra.Command { cmd.Short = `Get an endpoint.` cmd.Long = `Get an endpoint. - Get details for a single vector search endpoint. + Get details for a single AI Search endpoint. Arguments: ENDPOINT_NAME: Name of the endpoint` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -276,6 +290,125 @@ func newGetEndpoint() *cobra.Command { return cmd } +// start get-permission-levels command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionLevelsOverrides []func( + *cobra.Command, + *vectorsearch.GetVectorSearchEndpointPermissionLevelsRequest, +) + +func newGetPermissionLevels() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionLevelsReq vectorsearch.GetVectorSearchEndpointPermissionLevelsRequest + + cmd.Use = "get-permission-levels ENDPOINT_ID" + cmd.Short = `Get vector search endpoint permission levels.` + cmd.Long = `Get vector search endpoint permission levels. + + Gets the permission levels that a user can have on an object. + + Arguments: + ENDPOINT_ID: The vector search endpoint for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionLevelsReq.EndpointId = args[0] + + response, err := w.VectorSearchEndpoints.GetPermissionLevels(ctx, getPermissionLevelsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionLevelsOverrides { + fn(cmd, &getPermissionLevelsReq) + } + + return cmd +} + +// start get-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getPermissionsOverrides []func( + *cobra.Command, + *vectorsearch.GetVectorSearchEndpointPermissionsRequest, +) + +func newGetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var getPermissionsReq vectorsearch.GetVectorSearchEndpointPermissionsRequest + + cmd.Use = "get-permissions ENDPOINT_ID" + cmd.Short = `Get vector search endpoint permissions.` + cmd.Long = `Get vector search endpoint permissions. + + Gets the permissions of a vector search endpoint. Vector search endpoints can + inherit permissions from their root object. + + Arguments: + ENDPOINT_ID: The vector search endpoint for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + getPermissionsReq.EndpointId = args[0] + + response, err := w.VectorSearchEndpoints.GetPermissions(ctx, getPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getPermissionsOverrides { + fn(cmd, &getPermissionsReq) + } + + return cmd +} + // start list-endpoints command // Slice with functions to override default command behavior. @@ -305,9 +438,11 @@ func newListEndpoints() *cobra.Command { cmd.Short = `List all endpoints.` cmd.Long = `List all endpoints. - List all vector search endpoints in the workspace.` + List all AI Search endpoints in the workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -359,18 +494,22 @@ func newPatchEndpoint() *cobra.Command { cmd.Flags().Var(&patchEndpointJson, "json", `either inline JSON string or @path/to/file.json with request body`) - cmd.Flags().Int64Var(&patchEndpointReq.MinQps, "min-qps", patchEndpointReq.MinQps, `Min QPS for the endpoint.`) + cmd.Flags().Int64Var(&patchEndpointReq.TargetQps, "target-qps", patchEndpointReq.TargetQps, `Target QPS for the endpoint.`) cmd.Use = "patch-endpoint ENDPOINT_NAME" - cmd.Short = `Update an endpoint.` - cmd.Long = `Update an endpoint. + cmd.Short = `*Public Preview* Update an endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update an endpoint. Update an endpoint Arguments: - ENDPOINT_NAME: Name of the vector search endpoint` + ENDPOINT_NAME: Name of the AI Search endpoint` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -446,9 +585,11 @@ func newRetrieveUserVisibleMetrics() *cobra.Command { Retrieve user-visible metrics for an endpoint Arguments: - NAME: Vector search endpoint name` + NAME: AI Search endpoint name` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -494,6 +635,84 @@ func newRetrieveUserVisibleMetrics() *cobra.Command { return cmd } +// start set-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var setPermissionsOverrides []func( + *cobra.Command, + *vectorsearch.VectorSearchEndpointPermissionsRequest, +) + +func newSetPermissions() *cobra.Command { + cmd := &cobra.Command{} + + var setPermissionsReq vectorsearch.VectorSearchEndpointPermissionsRequest + var setPermissionsJson flags.JsonFlag + + cmd.Flags().Var(&setPermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "set-permissions ENDPOINT_ID" + cmd.Short = `Set vector search endpoint permissions.` + cmd.Long = `Set vector search endpoint permissions. + + Sets permissions on an object, replacing existing permissions if they exist. + Deletes all direct permissions if none are specified. Objects can inherit + permissions from their root object. + + Arguments: + ENDPOINT_ID: The vector search endpoint for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := setPermissionsJson.Unmarshal(&setPermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + setPermissionsReq.EndpointId = args[0] + + response, err := w.VectorSearchEndpoints.SetPermissions(ctx, setPermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range setPermissionsOverrides { + fn(cmd, &setPermissionsReq) + } + + return cmd +} + // start update-endpoint-budget-policy command // Slice with functions to override default command behavior. @@ -512,16 +731,20 @@ func newUpdateEndpointBudgetPolicy() *cobra.Command { cmd.Flags().Var(&updateEndpointBudgetPolicyJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "update-endpoint-budget-policy ENDPOINT_NAME BUDGET_POLICY_ID" - cmd.Short = `Update the budget policy of an endpoint.` - cmd.Long = `Update the budget policy of an endpoint. + cmd.Short = `*Public Preview* Update the budget policy of an endpoint.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update the budget policy of an endpoint. Update the budget policy of an endpoint Arguments: - ENDPOINT_NAME: Name of the vector search endpoint + ENDPOINT_NAME: Name of the AI Search endpoint BUDGET_POLICY_ID: The budget policy id to be applied` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -599,12 +822,14 @@ func newUpdateEndpointCustomTags() *cobra.Command { cmd.Long = `Update the custom tags of an endpoint. Arguments: - ENDPOINT_NAME: Name of the vector search endpoint` + ENDPOINT_NAME: Name of the AI Search endpoint` // This command is being previewed; hide from help output. cmd.Hidden = true cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -652,4 +877,81 @@ func newUpdateEndpointCustomTags() *cobra.Command { return cmd } +// start update-permissions command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updatePermissionsOverrides []func( + *cobra.Command, + *vectorsearch.VectorSearchEndpointPermissionsRequest, +) + +func newUpdatePermissions() *cobra.Command { + cmd := &cobra.Command{} + + var updatePermissionsReq vectorsearch.VectorSearchEndpointPermissionsRequest + var updatePermissionsJson flags.JsonFlag + + cmd.Flags().Var(&updatePermissionsJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: access_control_list + + cmd.Use = "update-permissions ENDPOINT_ID" + cmd.Short = `Update vector search endpoint permissions.` + cmd.Long = `Update vector search endpoint permissions. + + Updates the permissions on a vector search endpoint. Vector search endpoints + can inherit permissions from their root object. + + Arguments: + ENDPOINT_ID: The vector search endpoint for which to get or manage permissions.` + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updatePermissionsJson.Unmarshal(&updatePermissionsReq) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + updatePermissionsReq.EndpointId = args[0] + + response, err := w.VectorSearchEndpoints.UpdatePermissions(ctx, updatePermissionsReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updatePermissionsOverrides { + fn(cmd, &updatePermissionsReq) + } + + return cmd +} + // end service VectorSearchEndpoints diff --git a/cmd/workspace/vector-search-indexes/vector-search-indexes.go b/cmd/workspace/vector-search-indexes/vector-search-indexes.go index 60802ee9f11..53b5c250716 100755 --- a/cmd/workspace/vector-search-indexes/vector-search-indexes.go +++ b/cmd/workspace/vector-search-indexes/vector-search-indexes.go @@ -24,16 +24,20 @@ func New() *cobra.Command { Long: `**Index**: An efficient representation of your embedding vectors that supports real-time and efficient approximate nearest neighbor (ANN) search queries. - There are 2 types of Vector Search indexes: - **Delta Sync Index**: An index - that automatically syncs with a source Delta Table, automatically and - incrementally updating the index as the underlying data in the Delta Table - changes. - **Direct Vector Access Index**: An index that supports direct read - and write of vectors and metadata through our REST and SDK APIs. With this - model, the user manages index updates.`, + There are 2 types of AI Search indexes: - **Delta Sync Index**: An index that + automatically syncs with a source Delta Table, automatically and incrementally + updating the index as the underlying data in the Delta Table changes. - + **Direct Vector Access Index**: An index that supports direct read and write + of vectors and metadata through our REST and SDK APIs. With this model, the + user manages index updates.`, GroupID: "vectorsearch", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreateIndex()) cmd.AddCommand(newDeleteDataVectorIndex()) @@ -89,6 +93,8 @@ func newCreateIndex() *cobra.Command { Supported values: [DELTA_SYNC, DIRECT_ACCESS]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -184,6 +190,8 @@ func newDeleteDataVectorIndex() *cobra.Command { Vector Access Index.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -253,6 +261,8 @@ func newDeleteIndex() *cobra.Command { INDEX_NAME: Name of the index` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -309,6 +319,8 @@ func newGetIndex() *cobra.Command { INDEX_NAME: Name of the index` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -377,6 +389,8 @@ func newListIndexes() *cobra.Command { ENDPOINT_NAME: Name of the endpoint` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -449,6 +463,8 @@ func newQueryIndex() *cobra.Command { INDEX_NAME: Name of the vector index to query.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -527,6 +543,8 @@ func newQueryNextPage() *cobra.Command { INDEX_NAME: Name of the vector index to query.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -603,6 +621,8 @@ func newScanIndex() *cobra.Command { INDEX_NAME: Name of the vector index to scan.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -672,6 +692,8 @@ func newSyncIndex() *cobra.Command { INDEX_NAME: Name of the vector index to synchronize. Must be a Delta Sync Index.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -733,6 +755,8 @@ func newUpsertDataVectorIndex() *cobra.Command { INPUTS_JSON: JSON string representing the data to be upserted.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { diff --git a/cmd/workspace/volumes/volumes.go b/cmd/workspace/volumes/volumes.go index 994c61656f4..29750eb3e3b 100755 --- a/cmd/workspace/volumes/volumes.go +++ b/cmd/workspace/volumes/volumes.go @@ -33,6 +33,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newDelete()) @@ -104,6 +108,8 @@ func newCreate() *cobra.Command { Supported values: [EXTERNAL, MANAGED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -199,6 +205,8 @@ func newDelete() *cobra.Command { NAME: The three-level (fully qualified) name of the volume` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -297,6 +305,8 @@ func newList() *cobra.Command { SCHEMA_NAME: The identifier of the schema` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -365,6 +375,8 @@ func newRead() *cobra.Command { NAME: The three-level (fully qualified) name of the volume` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -448,6 +460,8 @@ func newUpdate() *cobra.Command { NAME: The three-level (fully qualified) name of the volume` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/warehouses/warehouses.go b/cmd/workspace/warehouses/warehouses.go index ff7160ec8c5..a98c00325f1 100755 --- a/cmd/workspace/warehouses/warehouses.go +++ b/cmd/workspace/warehouses/warehouses.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newCreate()) cmd.AddCommand(newCreateDefaultWarehouseOverride()) @@ -103,6 +107,8 @@ func newCreate() *cobra.Command { Creates a new SQL warehouse.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -187,8 +193,10 @@ func newCreateDefaultWarehouseOverride() *cobra.Command { cmd.Flags().StringVar(&createDefaultWarehouseOverrideReq.DefaultWarehouseOverride.WarehouseId, "warehouse-id", createDefaultWarehouseOverrideReq.DefaultWarehouseOverride.WarehouseId, `The specific warehouse ID when type is CUSTOM.`) cmd.Use = "create-default-warehouse-override DEFAULT_WAREHOUSE_OVERRIDE_ID TYPE" - cmd.Short = `Create default warehouse override.` - cmd.Long = `Create default warehouse override. + cmd.Short = `*Beta* Create default warehouse override.` + cmd.Long = `This command is in Beta and may change without notice. + +Create default warehouse override. Creates a new default warehouse override for a user. Users can create their own override. Admins can create overrides for any user. @@ -201,6 +209,8 @@ func newCreateDefaultWarehouseOverride() *cobra.Command { Supported values: [CUSTOM, LAST_SELECTED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -284,6 +294,8 @@ func newDelete() *cobra.Command { ID: Required. Id of the SQL warehouse.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -343,8 +355,10 @@ func newDeleteDefaultWarehouseOverride() *cobra.Command { var deleteDefaultWarehouseOverrideReq sql.DeleteDefaultWarehouseOverrideRequest cmd.Use = "delete-default-warehouse-override NAME" - cmd.Short = `Delete default warehouse override.` - cmd.Long = `Delete default warehouse override. + cmd.Short = `*Beta* Delete default warehouse override.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete default warehouse override. Deletes the default warehouse override for a user. Users can delete their own override. Admins can delete overrides for any user. After deletion, the @@ -357,6 +371,8 @@ func newDeleteDefaultWarehouseOverride() *cobra.Command { string "me" for the current user.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -448,6 +464,8 @@ func newEdit() *cobra.Command { ID: Required. Id of the warehouse to configure.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -547,6 +565,8 @@ func newGet() *cobra.Command { ID: Required. Id of the SQL warehouse.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -607,8 +627,10 @@ func newGetDefaultWarehouseOverride() *cobra.Command { var getDefaultWarehouseOverrideReq sql.GetDefaultWarehouseOverrideRequest cmd.Use = "get-default-warehouse-override NAME" - cmd.Short = `Get default warehouse override.` - cmd.Long = `Get default warehouse override. + cmd.Short = `*Beta* Get default warehouse override.` + cmd.Long = `This command is in Beta and may change without notice. + +Get default warehouse override. Returns the default warehouse override for a user. Users can fetch their own override. Admins can fetch overrides for any user. If no override exists, the @@ -621,6 +643,8 @@ func newGetDefaultWarehouseOverride() *cobra.Command { string "me" for the current user.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -690,6 +714,8 @@ func newGetPermissionLevels() *cobra.Command { WAREHOUSE_ID: The SQL warehouse for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -760,6 +786,8 @@ func newGetPermissions() *cobra.Command { WAREHOUSE_ID: The SQL warehouse for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -824,6 +852,8 @@ func newGetWorkspaceWarehouseConfig() *cobra.Command { a workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -884,6 +914,8 @@ func newList() *cobra.Command { Lists all SQL warehouses that a user has access to.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -946,13 +978,17 @@ func newListDefaultWarehouseOverrides() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-default-warehouse-overrides" - cmd.Short = `List default warehouse overrides.` - cmd.Long = `List default warehouse overrides. + cmd.Short = `*Beta* List default warehouse overrides.` + cmd.Long = `This command is in Beta and may change without notice. + +List default warehouse overrides. Lists all default warehouse overrides in the workspace. Only workspace administrators can list all overrides.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1018,6 +1054,8 @@ func newSetPermissions() *cobra.Command { WAREHOUSE_ID: The SQL warehouse for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1111,6 +1149,8 @@ func newSetWorkspaceWarehouseConfig() *cobra.Command { a workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -1184,6 +1224,8 @@ func newStart() *cobra.Command { ID: Required. Id of the SQL warehouse.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1277,6 +1319,8 @@ func newStop() *cobra.Command { ID: Required. Id of the SQL warehouse.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -1363,8 +1407,10 @@ func newUpdateDefaultWarehouseOverride() *cobra.Command { cmd.Flags().StringVar(&updateDefaultWarehouseOverrideReq.DefaultWarehouseOverride.WarehouseId, "warehouse-id", updateDefaultWarehouseOverrideReq.DefaultWarehouseOverride.WarehouseId, `The specific warehouse ID when type is CUSTOM.`) cmd.Use = "update-default-warehouse-override NAME UPDATE_MASK TYPE" - cmd.Short = `Update default warehouse override.` - cmd.Long = `Update default warehouse override. + cmd.Short = `*Beta* Update default warehouse override.` + cmd.Long = `This command is in Beta and may change without notice. + +Update default warehouse override. Updates an existing default warehouse override for a user. Users can update their own override. Admins can update overrides for any user. @@ -1379,6 +1425,8 @@ func newUpdateDefaultWarehouseOverride() *cobra.Command { Supported values: [CUSTOM, LAST_SELECTED]` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -1472,6 +1520,8 @@ func newUpdatePermissions() *cobra.Command { WAREHOUSE_ID: The SQL warehouse for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/workspace-bindings/workspace-bindings.go b/cmd/workspace/workspace-bindings/workspace-bindings.go index abb927ce0fe..2b26185465a 100755 --- a/cmd/workspace/workspace-bindings/workspace-bindings.go +++ b/cmd/workspace/workspace-bindings/workspace-bindings.go @@ -44,6 +44,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGet()) cmd.AddCommand(newGetBindings()) @@ -83,6 +87,8 @@ func newGet() *cobra.Command { NAME: The name of the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -164,6 +170,8 @@ func newGetBindings() *cobra.Command { SECURABLE_NAME: The name of the securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -232,6 +240,8 @@ func newUpdate() *cobra.Command { NAME: The name of the catalog.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -310,6 +320,8 @@ func newUpdateBindings() *cobra.Command { SECURABLE_NAME: The name of the securable.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/cmd/workspace/workspace-conf/workspace-conf.go b/cmd/workspace/workspace-conf/workspace-conf.go index 9fc4e2b750a..16b3fd44a03 100755 --- a/cmd/workspace/workspace-conf/workspace-conf.go +++ b/cmd/workspace/workspace-conf/workspace-conf.go @@ -26,6 +26,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newGetStatus()) cmd.AddCommand(newSetStatus()) @@ -59,6 +63,8 @@ func newGetStatus() *cobra.Command { Gets the configuration status for a workspace.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -117,6 +123,8 @@ func newSetStatus() *cobra.Command { it.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { diff --git a/cmd/workspace/workspace-entity-tag-assignments/workspace-entity-tag-assignments.go b/cmd/workspace/workspace-entity-tag-assignments/workspace-entity-tag-assignments.go index c0154deaa9e..89e6ff34c58 100755 --- a/cmd/workspace/workspace-entity-tag-assignments/workspace-entity-tag-assignments.go +++ b/cmd/workspace/workspace-entity-tag-assignments/workspace-entity-tag-assignments.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "workspace-entity-tag-assignments", - Short: `Manage tag assignments on workspace-scoped objects.`, - Long: `Manage tag assignments on workspace-scoped objects.`, + Use: "workspace-entity-tag-assignments", + Short: `*Beta* Manage tag assignments on workspace-scoped objects.`, + Long: `This command is in Beta and may change without notice. + +Manage tag assignments on workspace-scoped objects.`, GroupID: "tags", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods cmd.AddCommand(newCreateTagAssignment()) cmd.AddCommand(newDeleteTagAssignment()) @@ -62,8 +68,10 @@ func newCreateTagAssignment() *cobra.Command { cmd.Flags().StringVar(&createTagAssignmentReq.TagAssignment.TagValue, "tag-value", createTagAssignmentReq.TagAssignment.TagValue, `The value of the tag.`) cmd.Use = "create-tag-assignment ENTITY_TYPE ENTITY_ID TAG_KEY" - cmd.Short = `Create a tag assignment for an entity.` - cmd.Long = `Create a tag assignment for an entity. + cmd.Short = `*Beta* Create a tag assignment for an entity.` + cmd.Long = `This command is in Beta and may change without notice. + +Create a tag assignment for an entity. Create a tag assignment @@ -76,6 +84,8 @@ func newCreateTagAssignment() *cobra.Command { are not allowed` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -151,8 +161,10 @@ func newDeleteTagAssignment() *cobra.Command { var deleteTagAssignmentReq tags.DeleteTagAssignmentRequest cmd.Use = "delete-tag-assignment ENTITY_TYPE ENTITY_ID TAG_KEY" - cmd.Short = `Delete a tag assignment for an entity.` - cmd.Long = `Delete a tag assignment for an entity. + cmd.Short = `*Beta* Delete a tag assignment for an entity.` + cmd.Long = `This command is in Beta and may change without notice. + +Delete a tag assignment for an entity. Delete a tag assignment @@ -165,6 +177,8 @@ func newDeleteTagAssignment() *cobra.Command { are not allowed` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -214,8 +228,10 @@ func newGetTagAssignment() *cobra.Command { var getTagAssignmentReq tags.GetTagAssignmentRequest cmd.Use = "get-tag-assignment ENTITY_TYPE ENTITY_ID TAG_KEY" - cmd.Short = `Get a tag assignment for an entity.` - cmd.Long = `Get a tag assignment for an entity. + cmd.Short = `*Beta* Get a tag assignment for an entity.` + cmd.Long = `This command is in Beta and may change without notice. + +Get a tag assignment for an entity. Get a tag assignment @@ -228,6 +244,8 @@ func newGetTagAssignment() *cobra.Command { are not allowed` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(3) @@ -291,8 +309,10 @@ func newListTagAssignments() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-tag-assignments ENTITY_TYPE ENTITY_ID" - cmd.Short = `List tag assignments for an entity.` - cmd.Long = `List tag assignments for an entity. + cmd.Short = `*Beta* List tag assignments for an entity.` + cmd.Long = `This command is in Beta and may change without notice. + +List tag assignments for an entity. List the tag assignments for an entity @@ -303,6 +323,8 @@ func newListTagAssignments() *cobra.Command { entity_id is the app name` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -361,8 +383,10 @@ func newUpdateTagAssignment() *cobra.Command { cmd.Flags().StringVar(&updateTagAssignmentReq.TagAssignment.TagValue, "tag-value", updateTagAssignmentReq.TagAssignment.TagValue, `The value of the tag.`) cmd.Use = "update-tag-assignment ENTITY_TYPE ENTITY_ID TAG_KEY UPDATE_MASK" - cmd.Short = `Update a tag assignment for an entity.` - cmd.Long = `Update a tag assignment for an entity. + cmd.Short = `*Beta* Update a tag assignment for an entity.` + cmd.Long = `This command is in Beta and may change without notice. + +Update a tag assignment for an entity. Update a tag assignment @@ -386,6 +410,8 @@ func newUpdateTagAssignment() *cobra.Command { future.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(4) diff --git a/cmd/workspace/workspace-iam-v2/workspace-iam-v2.go b/cmd/workspace/workspace-iam-v2/workspace-iam-v2.go index 2596b765cbc..f46706976cf 100755 --- a/cmd/workspace/workspace-iam-v2/workspace-iam-v2.go +++ b/cmd/workspace/workspace-iam-v2/workspace-iam-v2.go @@ -4,11 +4,13 @@ package workspace_iam_v2 import ( "fmt" + "strings" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/cmdctx" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" + "github.com/databricks/databricks-sdk-go/common/types/fieldmask" "github.com/databricks/databricks-sdk-go/service/iamv2" "github.com/spf13/cobra" ) @@ -20,18 +22,29 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ Use: "workspace-iam-v2", - Short: `These APIs are used to manage identities and the workspace access of these identities in .`, - Long: `These APIs are used to manage identities and the workspace access of these + Short: `*Beta* These APIs are used to manage identities and the workspace access of these identities in .`, + Long: `This command is in Beta and may change without notice. + +These APIs are used to manage identities and the workspace access of these identities in .`, GroupID: "iam", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" + // Add methods + cmd.AddCommand(newCreateWorkspaceAssignmentDetailProxy()) + cmd.AddCommand(newDeleteWorkspaceAssignmentDetailProxy()) cmd.AddCommand(newGetWorkspaceAccessDetailLocal()) + cmd.AddCommand(newGetWorkspaceAssignmentDetailProxy()) + cmd.AddCommand(newListWorkspaceAssignmentDetailsProxy()) cmd.AddCommand(newResolveGroupProxy()) cmd.AddCommand(newResolveServicePrincipalProxy()) cmd.AddCommand(newResolveUserProxy()) + cmd.AddCommand(newUpdateWorkspaceAssignmentDetailProxy()) // Apply optional overrides to this command. for _, fn := range cmdOverrides { @@ -41,6 +54,173 @@ func New() *cobra.Command { return cmd } +// start create-workspace-assignment-detail-proxy command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var createWorkspaceAssignmentDetailProxyOverrides []func( + *cobra.Command, + *iamv2.CreateWorkspaceAssignmentDetailProxyRequest, +) + +func newCreateWorkspaceAssignmentDetailProxy() *cobra.Command { + cmd := &cobra.Command{} + + var createWorkspaceAssignmentDetailProxyReq iamv2.CreateWorkspaceAssignmentDetailProxyRequest + createWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail = iamv2.WorkspaceAssignmentDetail{} + var createWorkspaceAssignmentDetailProxyJson flags.JsonFlag + + cmd.Flags().Var(&createWorkspaceAssignmentDetailProxyJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: entitlements + + cmd.Use = "create-workspace-assignment-detail-proxy PRINCIPAL_ID" + cmd.Short = `Create a workspace assignment detail for a workspace.` + cmd.Long = `Create a workspace assignment detail for a workspace. + + Creates a workspace assignment detail for a principal (workspace-level proxy). + Entitlement grants are applied individually and non-atomically — if a + failure occurs partway through, the principal will be assigned to the + workspace but with only a subset of the requested entitlements. Use + GetWorkspaceAssignmentDetail to confirm which entitlements were successfully + granted. + + Arguments: + PRINCIPAL_ID: The internal ID of the principal (user/sp/group) in Databricks.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + 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 allowed. Provide 'principal_id' in your JSON input") + } + return nil + } + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := createWorkspaceAssignmentDetailProxyJson.Unmarshal(&createWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[0], &createWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[0]) + } + + } + + response, err := w.WorkspaceIamV2.CreateWorkspaceAssignmentDetailProxy(ctx, createWorkspaceAssignmentDetailProxyReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range createWorkspaceAssignmentDetailProxyOverrides { + fn(cmd, &createWorkspaceAssignmentDetailProxyReq) + } + + return cmd +} + +// start delete-workspace-assignment-detail-proxy command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var deleteWorkspaceAssignmentDetailProxyOverrides []func( + *cobra.Command, + *iamv2.DeleteWorkspaceAssignmentDetailProxyRequest, +) + +func newDeleteWorkspaceAssignmentDetailProxy() *cobra.Command { + cmd := &cobra.Command{} + + var deleteWorkspaceAssignmentDetailProxyReq iamv2.DeleteWorkspaceAssignmentDetailProxyRequest + + cmd.Use = "delete-workspace-assignment-detail-proxy PRINCIPAL_ID" + cmd.Short = `Delete a workspace assignment detail for a workspace.` + cmd.Long = `Delete a workspace assignment detail for a workspace. + + Deletes a workspace assignment detail for a principal (workspace-level proxy), + revoking all associated entitlements. Entitlement revocations are applied + individually and non-atomically — if a failure occurs partway through, the + principal remains assigned with a subset of its original entitlements, and the + operation is safe to retry. + + Arguments: + PRINCIPAL_ID: Required. ID of the principal in Databricks to delete workspace assignment + for.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + _, err = fmt.Sscan(args[0], &deleteWorkspaceAssignmentDetailProxyReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[0]) + } + + err = w.WorkspaceIamV2.DeleteWorkspaceAssignmentDetailProxy(ctx, deleteWorkspaceAssignmentDetailProxyReq) + if err != nil { + return err + } + return nil + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range deleteWorkspaceAssignmentDetailProxyOverrides { + fn(cmd, &deleteWorkspaceAssignmentDetailProxyReq) + } + + return cmd +} + // start get-workspace-access-detail-local command // Slice with functions to override default command behavior. @@ -58,8 +238,10 @@ func newGetWorkspaceAccessDetailLocal() *cobra.Command { cmd.Flags().Var(&getWorkspaceAccessDetailLocalReq.View, "view", `Controls what fields are returned. Supported values: [BASIC, FULL]`) cmd.Use = "get-workspace-access-detail-local PRINCIPAL_ID" - cmd.Short = `Get workspace access details for a principal.` - cmd.Long = `Get workspace access details for a principal. + cmd.Short = `*Beta* Get workspace access details for a principal.` + cmd.Long = `This command is in Beta and may change without notice. + +Get workspace access details for a principal. Returns the access details for a principal in the current workspace. Allows for checking access details for any provisioned principal (user, service @@ -73,6 +255,8 @@ func newGetWorkspaceAccessDetailLocal() *cobra.Command { access details are being requested.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -109,6 +293,133 @@ func newGetWorkspaceAccessDetailLocal() *cobra.Command { return cmd } +// start get-workspace-assignment-detail-proxy command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var getWorkspaceAssignmentDetailProxyOverrides []func( + *cobra.Command, + *iamv2.GetWorkspaceAssignmentDetailProxyRequest, +) + +func newGetWorkspaceAssignmentDetailProxy() *cobra.Command { + cmd := &cobra.Command{} + + var getWorkspaceAssignmentDetailProxyReq iamv2.GetWorkspaceAssignmentDetailProxyRequest + + cmd.Use = "get-workspace-assignment-detail-proxy PRINCIPAL_ID" + cmd.Short = `Get workspace assignment details for a principal.` + cmd.Long = `Get workspace assignment details for a principal. + + Returns the assignment details for a principal in a workspace (workspace-level + proxy). + + Arguments: + PRINCIPAL_ID: Required. The internal ID of the principal (user/sp/group) for which the + assignment details are being requested.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(1) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + _, err = fmt.Sscan(args[0], &getWorkspaceAssignmentDetailProxyReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[0]) + } + + response, err := w.WorkspaceIamV2.GetWorkspaceAssignmentDetailProxy(ctx, getWorkspaceAssignmentDetailProxyReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range getWorkspaceAssignmentDetailProxyOverrides { + fn(cmd, &getWorkspaceAssignmentDetailProxyReq) + } + + return cmd +} + +// start list-workspace-assignment-details-proxy command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var listWorkspaceAssignmentDetailsProxyOverrides []func( + *cobra.Command, + *iamv2.ListWorkspaceAssignmentDetailsProxyRequest, +) + +func newListWorkspaceAssignmentDetailsProxy() *cobra.Command { + cmd := &cobra.Command{} + + var listWorkspaceAssignmentDetailsProxyReq iamv2.ListWorkspaceAssignmentDetailsProxyRequest + + cmd.Flags().IntVar(&listWorkspaceAssignmentDetailsProxyReq.PageSize, "page-size", listWorkspaceAssignmentDetailsProxyReq.PageSize, `The maximum number of workspace assignment details to return.`) + cmd.Flags().StringVar(&listWorkspaceAssignmentDetailsProxyReq.PageToken, "page-token", listWorkspaceAssignmentDetailsProxyReq.PageToken, `A page token, received from a previous ListWorkspaceAssignmentDetailsProxy call.`) + + cmd.Use = "list-workspace-assignment-details-proxy" + cmd.Short = `List workspace assignment details for a workspace.` + cmd.Long = `List workspace assignment details for a workspace. + + Lists workspace assignment details for a workspace (workspace-level proxy).` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + check := root.ExactArgs(0) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + response, err := w.WorkspaceIamV2.ListWorkspaceAssignmentDetailsProxy(ctx, listWorkspaceAssignmentDetailsProxyReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range listWorkspaceAssignmentDetailsProxyOverrides { + fn(cmd, &listWorkspaceAssignmentDetailsProxyReq) + } + + return cmd +} + // start resolve-group-proxy command // Slice with functions to override default command behavior. @@ -127,8 +438,10 @@ func newResolveGroupProxy() *cobra.Command { cmd.Flags().Var(&resolveGroupProxyJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-group-proxy EXTERNAL_ID" - cmd.Short = `Resolve an external group in the Databricks account.` - cmd.Long = `Resolve an external group in the Databricks account. + cmd.Short = `*Beta* Resolve an external group in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external group in the Databricks account. Resolves a group with the given external ID from the customer's IdP. If the group does not exist, it will be created in the account. If the customer is @@ -139,6 +452,8 @@ func newResolveGroupProxy() *cobra.Command { EXTERNAL_ID: Required. The external ID of the group in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -211,8 +526,10 @@ func newResolveServicePrincipalProxy() *cobra.Command { cmd.Flags().Var(&resolveServicePrincipalProxyJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-service-principal-proxy EXTERNAL_ID" - cmd.Short = `Resolve an external service principal in the Databricks account.` - cmd.Long = `Resolve an external service principal in the Databricks account. + cmd.Short = `*Beta* Resolve an external service principal in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external service principal in the Databricks account. Resolves an SP with the given external ID from the customer's IdP. If the SP does not exist, it will be created. If the customer is not onboarded onto @@ -222,6 +539,8 @@ func newResolveServicePrincipalProxy() *cobra.Command { EXTERNAL_ID: Required. The external ID of the service principal in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -294,8 +613,10 @@ func newResolveUserProxy() *cobra.Command { cmd.Flags().Var(&resolveUserProxyJson, "json", `either inline JSON string or @path/to/file.json with request body`) cmd.Use = "resolve-user-proxy EXTERNAL_ID" - cmd.Short = `Resolve an external user in the Databricks account.` - cmd.Long = `Resolve an external user in the Databricks account. + cmd.Short = `*Beta* Resolve an external user in the Databricks account.` + cmd.Long = `This command is in Beta and may change without notice. + +Resolve an external user in the Databricks account. Resolves a user with the given external ID from the customer's IdP. If the user does not exist, it will be created. If the customer is not onboarded onto @@ -305,6 +626,8 @@ func newResolveUserProxy() *cobra.Command { EXTERNAL_ID: Required. The external ID of the user in the customer's IdP.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_BETA" + cmd.Annotations["launch_stage_display"] = "Beta" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -359,4 +682,112 @@ func newResolveUserProxy() *cobra.Command { return cmd } +// start update-workspace-assignment-detail-proxy command + +// Slice with functions to override default command behavior. +// Functions can be added from the `init()` function in manually curated files in this directory. +var updateWorkspaceAssignmentDetailProxyOverrides []func( + *cobra.Command, + *iamv2.UpdateWorkspaceAssignmentDetailProxyRequest, +) + +func newUpdateWorkspaceAssignmentDetailProxy() *cobra.Command { + cmd := &cobra.Command{} + + var updateWorkspaceAssignmentDetailProxyReq iamv2.UpdateWorkspaceAssignmentDetailProxyRequest + updateWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail = iamv2.WorkspaceAssignmentDetail{} + var updateWorkspaceAssignmentDetailProxyJson flags.JsonFlag + + cmd.Flags().Var(&updateWorkspaceAssignmentDetailProxyJson, "json", `either inline JSON string or @path/to/file.json with request body`) + + // TODO: array: entitlements + + cmd.Use = "update-workspace-assignment-detail-proxy PRINCIPAL_ID UPDATE_MASK PRINCIPAL_ID" + cmd.Short = `Update a workspace assignment detail for a workspace.` + cmd.Long = `Update a workspace assignment detail for a workspace. + + Updates the entitlements of a directly assigned principal in a workspace + (workspace-level proxy). Entitlement changes are applied individually and + non-atomically — if a failure occurs partway through, only a subset of the + requested changes may have been applied. Use GetWorkspaceAssignmentDetail to + confirm the final state. + + Arguments: + PRINCIPAL_ID: Required. ID of the principal in Databricks. + UPDATE_MASK: Required. The list of fields to update. + PRINCIPAL_ID: The internal ID of the principal (user/sp/group) in Databricks.` + + // This command is being previewed; hide from help output. + cmd.Hidden = true + + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PRIVATE_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Private Preview" + + cmd.Args = func(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("json") { + err := root.ExactArgs(2)(cmd, args) + if err != nil { + return fmt.Errorf("when --json flag is specified, provide only PRINCIPAL_ID, UPDATE_MASK as positional arguments. Provide 'principal_id' in your JSON input") + } + return nil + } + check := root.ExactArgs(3) + return check(cmd, args) + } + + cmd.PreRunE = root.MustWorkspaceClient + cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + ctx := cmd.Context() + w := cmdctx.WorkspaceClient(ctx) + + if cmd.Flags().Changed("json") { + diags := updateWorkspaceAssignmentDetailProxyJson.Unmarshal(&updateWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail) + if diags.HasError() { + return diags.Error() + } + if len(diags) > 0 { + err := cmdio.RenderDiagnostics(ctx, diags) + if err != nil { + return err + } + } + } + _, err = fmt.Sscan(args[0], &updateWorkspaceAssignmentDetailProxyReq.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[0]) + } + + if args[1] != "" { + updateMaskArray := strings.Split(args[1], ",") + updateWorkspaceAssignmentDetailProxyReq.UpdateMask = *fieldmask.New(updateMaskArray) + } + if !cmd.Flags().Changed("json") { + _, err = fmt.Sscan(args[2], &updateWorkspaceAssignmentDetailProxyReq.WorkspaceAssignmentDetail.PrincipalId) + if err != nil { + return fmt.Errorf("invalid PRINCIPAL_ID: %s", args[2]) + } + + } + + response, err := w.WorkspaceIamV2.UpdateWorkspaceAssignmentDetailProxy(ctx, updateWorkspaceAssignmentDetailProxyReq) + if err != nil { + return err + } + + return cmdio.Render(ctx, response) + } + + // Disable completions since they are not applicable. + // Can be overridden by manual implementation in `override.go`. + cmd.ValidArgsFunction = cobra.NoFileCompletions + + // Apply optional overrides to this command. + for _, fn := range updateWorkspaceAssignmentDetailProxyOverrides { + fn(cmd, &updateWorkspaceAssignmentDetailProxyReq) + } + + return cmd +} + // end service WorkspaceIamV2 diff --git a/cmd/workspace/workspace-settings-v2/workspace-settings-v2.go b/cmd/workspace/workspace-settings-v2/workspace-settings-v2.go index d4e3d213b06..64ceb60cedc 100755 --- a/cmd/workspace/workspace-settings-v2/workspace-settings-v2.go +++ b/cmd/workspace/workspace-settings-v2/workspace-settings-v2.go @@ -19,13 +19,19 @@ var cmdOverrides []func(*cobra.Command) func New() *cobra.Command { cmd := &cobra.Command{ - Use: "workspace-settings-v2", - Short: `APIs to manage workspace level settings.`, - Long: `APIs to manage workspace level settings`, + Use: "workspace-settings-v2", + Short: `*Public Preview* APIs to manage workspace level settings.`, + Long: `This command is in Public Preview and may change without notice. + +APIs to manage workspace level settings`, GroupID: "settings", RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" + // Add methods cmd.AddCommand(newGetPublicWorkspaceSetting()) cmd.AddCommand(newListWorkspaceSettingsMetadata()) @@ -54,8 +60,10 @@ func newGetPublicWorkspaceSetting() *cobra.Command { var getPublicWorkspaceSettingReq settingsv2.GetPublicWorkspaceSettingRequest cmd.Use = "get-public-workspace-setting NAME" - cmd.Short = `Get a workspace setting.` - cmd.Long = `Get a workspace setting. + cmd.Short = `*Public Preview* Get a workspace setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Get a workspace setting. Get a setting value at workspace level. See :method:settingsv2/listworkspacesettingsmetadata for list of setting available @@ -65,6 +73,8 @@ func newGetPublicWorkspaceSetting() *cobra.Command { NAME: Name of the setting` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -126,14 +136,18 @@ func newListWorkspaceSettingsMetadata() *cobra.Command { cmd.Flags().Lookup("page-token").Hidden = true cmd.Use = "list-workspace-settings-metadata" - cmd.Short = `List valid setting keys and their metadata.` - cmd.Long = `List valid setting keys and their metadata. + cmd.Short = `*Public Preview* List valid setting keys and their metadata.` + cmd.Long = `This command is in Public Preview and may change without notice. + +List valid setting keys and their metadata. List valid setting keys and metadata. These settings are available to be referenced via GET :method:settingsv2/getpublicworkspacesetting and PATCH :method:settingsv2/patchpublicworkspacesetting APIs` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(0) @@ -188,25 +202,31 @@ func newPatchPublicWorkspaceSetting() *cobra.Command { // TODO: complex arg: aibi_dashboard_embedding_access_policy // TODO: complex arg: aibi_dashboard_embedding_approved_domains + // TODO: complex arg: allowed_apps_user_api_scopes // TODO: complex arg: automatic_cluster_update_workspace // TODO: complex arg: boolean_val // TODO: complex arg: effective_aibi_dashboard_embedding_access_policy // TODO: complex arg: effective_aibi_dashboard_embedding_approved_domains + // TODO: complex arg: effective_allowed_apps_user_api_scopes // TODO: complex arg: effective_automatic_cluster_update_workspace // TODO: complex arg: effective_boolean_val // TODO: complex arg: effective_integer_val + // TODO: complex arg: effective_operational_email_custom_recipient // TODO: complex arg: effective_personal_compute // TODO: complex arg: effective_restrict_workspace_admins // TODO: complex arg: effective_string_val // TODO: complex arg: integer_val cmd.Flags().StringVar(&patchPublicWorkspaceSettingReq.Setting.Name, "name", patchPublicWorkspaceSettingReq.Setting.Name, `Name of the setting.`) + // TODO: complex arg: operational_email_custom_recipient // TODO: complex arg: personal_compute // TODO: complex arg: restrict_workspace_admins // TODO: complex arg: string_val cmd.Use = "patch-public-workspace-setting NAME" - cmd.Short = `Update a workspace setting.` - cmd.Long = `Update a workspace setting. + cmd.Short = `*Public Preview* Update a workspace setting.` + cmd.Long = `This command is in Public Preview and may change without notice. + +Update a workspace setting. Patch a setting value at workspace level. See :method:settingsv2/listworkspacesettingsmetadata for list of setting available @@ -220,6 +240,8 @@ func newPatchPublicWorkspaceSetting() *cobra.Command { NAME: Name of the setting` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "PUBLIC_PREVIEW" + cmd.Annotations["launch_stage_display"] = "Public Preview" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) diff --git a/cmd/workspace/workspace/import_dir.go b/cmd/workspace/workspace/import_dir.go index 1dbc43cc958..daaf3ef0dd8 100644 --- a/cmd/workspace/workspace/import_dir.go +++ b/cmd/workspace/workspace/import_dir.go @@ -23,6 +23,22 @@ type importDirOptions struct { overwrite bool } +// defaultSkipDirs are directory names skipped when walking the source tree. +// The previous behavior copied these verbatim into the workspace, which: +// - leaks .git/config (often containing template-repo origin URLs and +// occasionally cached credentials) into deployed app source trees +// - copies the local bundle cache (.databricks) on top of any remote one +// - uploads node_modules/ for JS/TS apps, which is large and gets +// reinstalled in the runtime anyway +// +// Reported as DEPLOY-04 #2 in the EMEA Apps gaps doc; users have been +// working around it by post-deploy scrubbing scripts. +var defaultSkipDirs = map[string]struct{}{ + ".git": {}, + ".databricks": {}, + "node_modules": {}, +} + // The callback function imports the file specified at sourcePath. This function is // meant to be used in conjunction with fs.WalkDir // @@ -48,6 +64,15 @@ func (opts importDirOptions) callback(ctx context.Context, workspaceFiler filer. return err } + // Skip default-excluded directories (e.g. .git, .databricks). The check + // excludes the explicit root so a user who passes ".git" as the source + // can still copy it deliberately. + if d.IsDir() && sourcePath != sourceDir { + if _, skip := defaultSkipDirs[d.Name()]; skip { + return fs.SkipDir + } + } + // localName is the name for the file in the local file system localName, err := filepath.Rel(sourceDir, sourcePath) if err != nil { @@ -117,6 +142,10 @@ func newImportDir() *cobra.Command { cmd.Long = ` Import a directory recursively from the local file system to a Databricks workspace. Notebooks will have their extensions (one of .scala, .py, .sql, .ipynb, .r) stripped + +By default, .git, .databricks, and node_modules directories encountered during +the recursive import are skipped. To import one of these directories deliberately, +pass it as SOURCE_PATH. ` cmd.Annotations = make(map[string]string) diff --git a/cmd/workspace/workspace/import_dir_test.go b/cmd/workspace/workspace/import_dir_test.go new file mode 100644 index 00000000000..0cb89fc008b --- /dev/null +++ b/cmd/workspace/workspace/import_dir_test.go @@ -0,0 +1,156 @@ +package workspace + +import ( + "context" + "io" + "io/fs" + "os" + "path/filepath" + "slices" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/filer" + "github.com/databricks/cli/libs/flags" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// recordingFiler captures Mkdir and Write calls so a test can assert which +// paths were visited by the import-dir walker. +type recordingFiler struct { + dirs []string + files []string +} + +func (r *recordingFiler) Mkdir(ctx context.Context, p string) error { + r.dirs = append(r.dirs, p) + return nil +} + +func (r *recordingFiler) Write(ctx context.Context, p string, reader io.Reader, mode ...filer.WriteMode) error { + r.files = append(r.files, p) + return nil +} + +func (r *recordingFiler) Read(ctx context.Context, p string) (io.ReadCloser, error) { + return nil, fs.ErrNotExist +} + +func (r *recordingFiler) Delete(ctx context.Context, p string, mode ...filer.DeleteMode) error { + return nil +} + +func (r *recordingFiler) ReadDir(ctx context.Context, p string) ([]fs.DirEntry, error) { + return nil, fs.ErrNotExist +} + +func (r *recordingFiler) Stat(ctx context.Context, name string) (fs.FileInfo, error) { + return nil, fs.ErrNotExist +} + +func writeFile(t *testing.T, root, rel, contents string) { + t.Helper() + full := filepath.Join(root, rel) + require.NoError(t, os.MkdirAll(filepath.Dir(full), 0o755)) + require.NoError(t, os.WriteFile(full, []byte(contents), 0o644)) +} + +func runWalk(t *testing.T, sourceDir string) *recordingFiler { + t.Helper() + rec := &recordingFiler{} + ctx := cmdio.InContext(t.Context(), + cmdio.NewIO(t.Context(), flags.OutputText, nil, io.Discard, io.Discard, "", "")) + opts := importDirOptions{sourceDir: sourceDir, targetDir: "/Workspace/x", overwrite: true} + cb := opts.callback(ctx, rec) + require.NoError(t, filepath.WalkDir(sourceDir, cb)) + return rec +} + +func TestImportDirSkipsGitDirectory(t *testing.T) { + src := t.TempDir() + writeFile(t, src, "app.py", "print('hi')") + writeFile(t, src, ".git/config", "[remote]\n url = git@github.com:org/template.git") + writeFile(t, src, ".git/HEAD", "ref: refs/heads/main") + writeFile(t, src, ".git/objects/abc123", "binary") + + rec := runWalk(t, src) + + slices.Sort(rec.files) + assert.Equal(t, []string{"app.py"}, rec.files) + for _, d := range rec.dirs { + assert.NotContains(t, d, ".git", "no .git directory should be created in the workspace") + } +} + +func TestImportDirSkipsNestedGitDirectory(t *testing.T) { + src := t.TempDir() + writeFile(t, src, "app.py", "print('hi')") + writeFile(t, src, "vendor/sub/.git/config", "[remote]\n url = ...") + writeFile(t, src, "vendor/sub/lib.py", "def f(): pass") + + rec := runWalk(t, src) + + slices.Sort(rec.files) + assert.Equal(t, []string{"app.py", filepath.ToSlash("vendor/sub/lib.py")}, rec.files) + for _, d := range rec.dirs { + assert.NotContains(t, d, ".git") + } +} + +func TestImportDirSkipsDatabricksCacheDirectory(t *testing.T) { + src := t.TempDir() + writeFile(t, src, "databricks.yml", "bundle:\n name: x") + writeFile(t, src, ".databricks/bundle/state.json", "{}") + + rec := runWalk(t, src) + + slices.Sort(rec.files) + assert.Equal(t, []string{"databricks.yml"}, rec.files) + for _, d := range rec.dirs { + assert.NotContains(t, d, ".databricks") + } +} + +func TestImportDirSkipsNodeModulesDirectory(t *testing.T) { + src := t.TempDir() + writeFile(t, src, "package.json", "{}") + writeFile(t, src, "app.js", "console.log('hi')") + writeFile(t, src, "node_modules/react/index.js", "module.exports = {}") + writeFile(t, src, "node_modules/.package-lock.json", "{}") + + rec := runWalk(t, src) + + slices.Sort(rec.files) + assert.Equal(t, []string{"app.js", "package.json"}, rec.files) + for _, d := range rec.dirs { + assert.NotContains(t, d, "node_modules") + } +} + +func TestImportDirCopiesGitignoreFile(t *testing.T) { + src := t.TempDir() + writeFile(t, src, ".gitignore", "*.pyc\n") + writeFile(t, src, "app.py", "print('hi')") + + rec := runWalk(t, src) + + slices.Sort(rec.files) + assert.Equal(t, []string{".gitignore", "app.py"}, rec.files) +} + +func TestImportDirAllowsExplicitGitRoot(t *testing.T) { + // If a user explicitly passes a .git directory as the source root, copy + // it: the skip rule applies to .git dirs encountered during the walk, + // not to a deliberately-named root. + src := t.TempDir() + gitRoot := filepath.Join(src, ".git") + require.NoError(t, os.MkdirAll(gitRoot, 0o755)) + writeFile(t, gitRoot, "HEAD", "ref: refs/heads/main") + writeFile(t, gitRoot, "config", "[core]\n") + + rec := runWalk(t, gitRoot) + + slices.Sort(rec.files) + assert.Equal(t, []string{"HEAD", "config"}, rec.files) +} diff --git a/cmd/workspace/workspace/workspace.go b/cmd/workspace/workspace/workspace.go index b1ad85707da..af3554bc7ec 100755 --- a/cmd/workspace/workspace/workspace.go +++ b/cmd/workspace/workspace/workspace.go @@ -31,6 +31,10 @@ func New() *cobra.Command { RunE: root.ReportUnknownSubcommand, } + cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" + // Add methods cmd.AddCommand(newDelete()) cmd.AddCommand(newExport()) @@ -74,10 +78,9 @@ func newDelete() *cobra.Command { cmd.Short = `Delete a workspace object.` cmd.Long = `Delete a workspace object. - Deprecated: use WorkspaceHierarchyService.DeleteTreeNode instead. Deletes an - object or a directory (and optionally recursively deletes all objects in the - directory). * If path does not exist, this call returns an error - RESOURCE_DOES_NOT_EXIST. * If path is a non-empty directory and + Deletes an object or a directory (and optionally recursively deletes all + objects in the directory). * If path does not exist, this call returns an + error RESOURCE_DOES_NOT_EXIST. * If path is a non-empty directory and recursive is set to false, this call returns an error DIRECTORY_NOT_EMPTY. @@ -88,6 +91,8 @@ func newDelete() *cobra.Command { PATH: The absolute path of the notebook or directory.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -199,6 +204,8 @@ func newExport() *cobra.Command { only supported for the DBC, SOURCE, and AUTO format.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) (err error) { @@ -271,6 +278,8 @@ func newGetPermissionLevels() *cobra.Command { WORKSPACE_OBJECT_ID: The workspace object for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -333,6 +342,8 @@ func newGetPermissions() *cobra.Command { WORKSPACE_OBJECT_ID: The workspace object for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -385,14 +396,15 @@ func newGetStatus() *cobra.Command { cmd.Short = `Get status.` cmd.Long = `Get status. - Deprecated: use WorkspaceHierarchyService.GetTreeNode instead. Gets the status - of an object or a directory. If path does not exist, this call returns an - error RESOURCE_DOES_NOT_EXIST. + Gets the status of an object or a directory. If path does not exist, this + call returns an error RESOURCE_DOES_NOT_EXIST. Arguments: PATH: The absolute path of the notebook or directory.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -472,6 +484,8 @@ func newImport() *cobra.Command { only supported for the DBC and SOURCE formats.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -554,14 +568,16 @@ func newList() *cobra.Command { cmd.Short = `List contents.` cmd.Long = `List contents. - Deprecated: use WorkspaceHierarchyService.ListTreeNodes instead. Lists the - contents of a directory, or the object if it is not a directory. If the input - path does not exist, this call returns an error RESOURCE_DOES_NOT_EXIST. + Lists the contents of a directory, or the object if it is not a directory. If + the input path does not exist, this call returns an error + RESOURCE_DOES_NOT_EXIST. Arguments: PATH: The absolute path of the notebook or directory.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(1) @@ -619,10 +635,9 @@ func newMkdirs() *cobra.Command { cmd.Short = `Create a directory.` cmd.Long = `Create a directory. - Deprecated: use WorkspaceHierarchyService.CreateTreeNode instead. Creates the - specified directory (and necessary parent directories if they do not exist). - If there is an object (not a directory) at any prefix of the input path, this - call returns an error RESOURCE_ALREADY_EXISTS. + Creates the specified directory (and necessary parent directories if they do + not exist). If there is an object (not a directory) at any prefix of the input + path, this call returns an error RESOURCE_ALREADY_EXISTS. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories. @@ -633,6 +648,8 @@ func newMkdirs() *cobra.Command { command will do nothing and succeed.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed("json") { @@ -735,6 +752,8 @@ func newSetPermissions() *cobra.Command { WORKSPACE_OBJECT_ID: The workspace object for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) @@ -814,6 +833,8 @@ func newUpdatePermissions() *cobra.Command { WORKSPACE_OBJECT_ID: The workspace object for which to get or manage permissions.` cmd.Annotations = make(map[string]string) + cmd.Annotations["launch_stage"] = "GA" + cmd.Annotations["launch_stage_display"] = "GA" cmd.Args = func(cmd *cobra.Command, args []string) error { check := root.ExactArgs(2) diff --git a/experimental/aitools/README.md b/experimental/aitools/README.md index ec12ed10f7c..092552250c0 100644 --- a/experimental/aitools/README.md +++ b/experimental/aitools/README.md @@ -2,11 +2,12 @@ `databricks experimental aitools` is the remaining experimental surface for coding-agent workflows. +The skills-management commands (`install`, `update`, `uninstall`, `list`, `version`) have been promoted to top-level `databricks aitools`. The old paths under `databricks experimental aitools` keep working as deprecated aliases that print a notice pointing to the new path. + Current commands: - `databricks experimental aitools skills list` - `databricks experimental aitools skills install [skill-name]` -- `databricks experimental aitools install [skill-name]` - `databricks experimental aitools tools query` - `databricks experimental aitools tools discover-schema` - `databricks experimental aitools tools get-default-warehouse` @@ -17,8 +18,7 @@ Current commands: Current behavior: -- `skills install` installs Databricks skills for detected coding agents. -- `install` is a compatibility alias for `skills install`. +- `skills install` installs Databricks skills for detected coding agents (delegates to `databricks aitools install`). - `tools` exposes a small set of AI-oriented workspace helpers. - `tools query` accepts a single SQL or multiple SQLs in one invocation. Pass several positional arguments and/or repeat `--file` to run them in parallel diff --git a/experimental/aitools/cmd/aitools.go b/experimental/aitools/cmd/aitools.go index f037ac1a22e..c3e6227392b 100644 --- a/experimental/aitools/cmd/aitools.go +++ b/experimental/aitools/cmd/aitools.go @@ -1,6 +1,9 @@ package aitools import ( + "fmt" + + aitoolscmd "github.com/databricks/cli/cmd/aitools" "github.com/spf13/cobra" ) @@ -9,20 +12,30 @@ func NewAitoolsCmd() *cobra.Command { Use: "aitools", Hidden: true, Short: "Databricks AI Tools for coding agents", - Long: `Manage Databricks AI Tools. + Long: `Experimental coding-agent helpers. Skills management is at "databricks aitools".`, + } -Provides commands to: -- Install the AI tools in coding agents (install) -- Manage skills (skills) -- Access tools directly (tools)`, + // Backward-compat aliases for the skills-management commands. They live + // at top-level `databricks aitools ` now; the old paths still work + // but print a deprecation notice that points to the new path. + aliases := []struct { + name string + mk func() *cobra.Command + }{ + {"install", aitoolscmd.NewInstallCmd}, + {"update", aitoolscmd.NewUpdateCmd}, + {"uninstall", aitoolscmd.NewUninstallCmd}, + {"list", aitoolscmd.NewListCmd}, + {"version", aitoolscmd.NewVersionCmd}, + } + for _, a := range aliases { + sub := a.mk() + sub.Hidden = true + sub.Deprecated = fmt.Sprintf(`use "databricks aitools %s" instead.`, a.name) + cmd.AddCommand(sub) } - cmd.AddCommand(newInstallCmd()) - cmd.AddCommand(newUpdateCmd()) - cmd.AddCommand(newUninstallCmd()) - cmd.AddCommand(newListCmd()) - cmd.AddCommand(newVersionCmd()) - cmd.AddCommand(newSkillsCmd()) + cmd.AddCommand(aitoolscmd.NewLegacySkillsCmd()) cmd.AddCommand(newToolsCmd()) return cmd diff --git a/experimental/aitools/cmd/discover_schema.go b/experimental/aitools/cmd/discover_schema.go index 091222368d9..418ab78e257 100644 --- a/experimental/aitools/cmd/discover_schema.go +++ b/experimental/aitools/cmd/discover_schema.go @@ -10,6 +10,7 @@ import ( "slices" "strings" "sync" + "sync/atomic" "syscall" "github.com/databricks/cli/cmd/root" @@ -161,47 +162,20 @@ CancelExecution before the command exits.`, gate := newSQLGate(concurrency) - results := make([]string, len(args)) - g := new(errgroup.Group) - for i, table := range args { - g.Go(func() error { - result, err := discoverTable(pollCtx, gate, w, warehouseID, table) - if err != nil { - results[i] = fmt.Sprintf("Error discovering %s: %v", table, err) - } else { - results[i] = result - } - // A failure on one table shouldn't abort the others. - return nil - }) - } - _ = g.Wait() + output, anyFailed := runDiscoverSchemas(pollCtx, gate, w, warehouseID, args) if pollCtx.Err() != nil { cancelDiscoverInFlight(ctx, w.StatementExecution, gate.trackedIDs()) return root.ErrAlreadyPrinted } - // format output with dividers for multiple tables - var output string - if len(results) == 1 { - output = results[0] - } else { - divider := strings.Repeat("-", 70) - var sb strings.Builder - for i, result := range results { - if i > 0 { - sb.WriteByte('\n') - sb.WriteString(divider) - sb.WriteByte('\n') - } - fmt.Fprintf(&sb, "TABLE: %s\n%s\n", args[i], divider) - sb.WriteString(result) - } - output = sb.String() - } - cmdio.LogString(ctx, output) + if anyFailed { + // Per-table errors are already in `output`; ErrAlreadyPrinted + // gives a non-zero exit without re-printing them so scripts + // and CI can detect failure via the exit code. + return root.ErrAlreadyPrinted + } return nil }, } @@ -211,6 +185,46 @@ CancelExecution before the command exits.`, return cmd } +// runDiscoverSchemas discovers schemas for tables concurrently and returns the +// rendered output. The bool is true if any table failed; per-table errors are +// inlined into the output so one bad table doesn't abort the others. +func runDiscoverSchemas(ctx context.Context, gate *sqlGate, w *databricks.WorkspaceClient, warehouseID string, tables []string) (string, bool) { + results := make([]string, len(tables)) + var anyFailed atomic.Bool + g := new(errgroup.Group) + for i, table := range tables { + g.Go(func() error { + result, err := discoverTable(ctx, gate, w, warehouseID, table) + if err != nil { + results[i] = fmt.Sprintf("Error discovering %s: %v", table, err) + anyFailed.Store(true) + } else { + results[i] = result + } + // A failure on one table shouldn't abort the others. + return nil + }) + } + _ = g.Wait() + + if len(tables) == 1 { + return results[0], anyFailed.Load() + } + + divider := strings.Repeat("-", 70) + var sb strings.Builder + for i, result := range results { + if i > 0 { + sb.WriteByte('\n') + sb.WriteString(divider) + sb.WriteByte('\n') + } + fmt.Fprintf(&sb, "TABLE: %s\n%s\n", tables[i], divider) + sb.WriteString(result) + } + return sb.String(), anyFailed.Load() +} + // cancelDiscoverInFlight sends CancelExecution for every recorded statement_id. // Best effort: errors are logged but don't fail the user-visible exit. // Statements that already finished server-side return an error which we just diff --git a/experimental/aitools/cmd/discover_schema_test.go b/experimental/aitools/cmd/discover_schema_test.go index fe6d86e799f..b76004367c9 100644 --- a/experimental/aitools/cmd/discover_schema_test.go +++ b/experimental/aitools/cmd/discover_schema_test.go @@ -324,3 +324,62 @@ func TestDiscoverSchemaInvalidTableNameRejectedBeforeWorkspaceClient(t *testing. require.Error(t, err) assert.Contains(t, err.Error(), "expected CATALOG.SCHEMA.TABLE") } + +func TestRunDiscoverSchemasFlagsTableFailureForExitCode(t *testing.T) { + // runDiscoverSchemas must report any per-table failure via the bool + // return so the caller can produce a non-zero exit. Without this signal + // scripts and CI parse stdout to detect failure, which is brittle. + ctx := cmdio.MockDiscard(t.Context()) + mockAPI := mocksql.NewMockStatementExecutionInterface(t) + + mockAPI.EXPECT().ExecuteStatement(mock.Anything, mock.Anything).Return(&dbsql.StatementResponse{ + StatementId: "stmt-bad", + Status: &dbsql.StatementStatus{ + State: dbsql.StatementStateFailed, + Error: &dbsql.ServiceError{ErrorCode: "TABLE_OR_VIEW_NOT_FOUND", Message: "no such table"}, + }, + }, nil).Once() + + w := &databricks.WorkspaceClient{StatementExecution: mockAPI} + output, anyFailed := runDiscoverSchemas(ctx, newSQLGate(8), w, "wh-1", []string{"main.public.missing"}) + + assert.True(t, anyFailed) + assert.Contains(t, output, "Error discovering main.public.missing") + assert.Contains(t, output, "TABLE_OR_VIEW_NOT_FOUND") +} + +func TestRunDiscoverSchemasAllSucceedReturnsFalse(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + mockAPI := mocksql.NewMockStatementExecutionInterface(t) + + mockAPI.EXPECT().ExecuteStatement(mock.Anything, mock.MatchedBy(func(req dbsql.ExecuteStatementRequest) bool { + return strings.HasPrefix(req.Statement, "DESCRIBE TABLE") + })).Return(&dbsql.StatementResponse{ + StatementId: "stmt-desc", + Status: &dbsql.StatementStatus{State: dbsql.StatementStateSucceeded}, + Result: &dbsql.ResultData{DataArray: [][]string{{"id", "BIGINT", ""}}}, + }, nil).Once() + + mockAPI.EXPECT().ExecuteStatement(mock.Anything, mock.MatchedBy(func(req dbsql.ExecuteStatementRequest) bool { + return strings.HasPrefix(req.Statement, "SELECT *") + })).Return(&dbsql.StatementResponse{ + StatementId: "stmt-sample", + Status: &dbsql.StatementStatus{State: dbsql.StatementStateSucceeded}, + }, nil).Once() + + mockAPI.EXPECT().ExecuteStatement(mock.Anything, mock.MatchedBy(func(req dbsql.ExecuteStatementRequest) bool { + return strings.Contains(req.Statement, "SUM(CASE WHEN") + })).Return(&dbsql.StatementResponse{ + StatementId: "stmt-null", + Status: &dbsql.StatementStatus{State: dbsql.StatementStateSucceeded}, + Manifest: &dbsql.ResultManifest{Schema: &dbsql.ResultSchema{Columns: []dbsql.ColumnInfo{{Name: "total_rows"}, {Name: "id_nulls"}}}}, + Result: &dbsql.ResultData{DataArray: [][]string{{"7", "0"}}}, + }, nil).Once() + + w := &databricks.WorkspaceClient{StatementExecution: mockAPI} + output, anyFailed := runDiscoverSchemas(ctx, newSQLGate(8), w, "wh-1", []string{"main.public.orders"}) + + assert.False(t, anyFailed) + assert.Contains(t, output, "COLUMNS:") + assert.NotContains(t, output, "Error discovering") +} diff --git a/experimental/aitools/cmd/list.go b/experimental/aitools/cmd/list.go deleted file mode 100644 index 1be1538c9a0..00000000000 --- a/experimental/aitools/cmd/list.go +++ /dev/null @@ -1,179 +0,0 @@ -package aitools - -import ( - "errors" - "fmt" - "maps" - "slices" - "strings" - "text/tabwriter" - - "github.com/databricks/cli/experimental/aitools/lib/installer" - "github.com/databricks/cli/libs/cmdio" - "github.com/databricks/cli/libs/log" - "github.com/spf13/cobra" -) - -// listSkillsFn is the function used to render the skills list. -// It is a package-level var so tests can replace the data-fetching layer. -var listSkillsFn = defaultListSkills - -func newListCmd() *cobra.Command { - var projectFlag, globalFlag bool - - cmd := &cobra.Command{ - Use: "list", - Short: "List installed AI tools components", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - if projectFlag && globalFlag { - return errors.New("cannot use --global and --project together") - } - // For list: no flag = show both scopes (empty string). - var scope string - if projectFlag { - scope = installer.ScopeProject - } else if globalFlag { - scope = installer.ScopeGlobal - } - return listSkillsFn(cmd, scope) - }, - } - - cmd.Flags().BoolVar(&projectFlag, "project", false, "Show only project-scoped skills") - cmd.Flags().BoolVar(&globalFlag, "global", false, "Show only globally-scoped skills") - return cmd -} - -func defaultListSkills(cmd *cobra.Command, scope string) error { - ctx := cmd.Context() - - ref := installer.GetSkillsRef(ctx) - - src := &installer.GitHubManifestSource{} - manifest, err := src.FetchManifest(ctx, ref) - if err != nil { - return fmt.Errorf("failed to fetch manifest: %w", err) - } - - // Load global state. - var globalState *installer.InstallState - if scope != installer.ScopeProject { - globalDir, gErr := installer.GlobalSkillsDir(ctx) - if gErr == nil { - globalState, err = installer.LoadState(globalDir) - if err != nil { - log.Debugf(ctx, "Could not load global install state: %v", err) - } - } - } - - // Load project state. - var projectState *installer.InstallState - if scope != installer.ScopeGlobal { - projectDir, pErr := installer.ProjectSkillsDir(ctx) - if pErr == nil { - projectState, err = installer.LoadState(projectDir) - if err != nil { - log.Debugf(ctx, "Could not load project install state: %v", err) - } - } - } - - // Build sorted list of skill names. - names := slices.Sorted(maps.Keys(manifest.Skills)) - - version := strings.TrimPrefix(ref, "v") - cmdio.LogString(ctx, "Available skills (v"+version+"):") - cmdio.LogString(ctx, "") - - var buf strings.Builder - tw := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0) - fmt.Fprintln(tw, " NAME\tVERSION\tINSTALLED") - - bothScopes := globalState != nil && projectState != nil - - globalCount := 0 - projectCount := 0 - for _, name := range names { - meta := manifest.Skills[name] - - tag := "" - if meta.Experimental { - tag = " [experimental]" - } - - installedStr := installedStatus(name, meta.Version, globalState, projectState, bothScopes) - if globalState != nil { - if _, ok := globalState.Skills[name]; ok { - globalCount++ - } - } - if projectState != nil { - if _, ok := projectState.Skills[name]; ok { - projectCount++ - } - } - - fmt.Fprintf(tw, " %s%s\tv%s\t%s\n", name, tag, meta.Version, installedStr) - } - tw.Flush() - cmdio.LogString(ctx, buf.String()) - - // Summary line. - switch { - case bothScopes: - cmdio.LogString(ctx, fmt.Sprintf("%d/%d skills installed (global), %d/%d (project)", globalCount, len(names), projectCount, len(names))) - case projectState != nil: - cmdio.LogString(ctx, fmt.Sprintf("%d/%d skills installed (project)", projectCount, len(names))) - case scope == installer.ScopeProject: - cmdio.LogString(ctx, fmt.Sprintf("%d/%d skills installed (project)", 0, len(names))) - default: - cmdio.LogString(ctx, fmt.Sprintf("%d/%d skills installed (global)", globalCount, len(names))) - } - return nil -} - -// installedStatus returns the display string for a skill's installation status. -func installedStatus(name, latestVersion string, globalState, projectState *installer.InstallState, bothScopes bool) string { - globalVer := "" - projectVer := "" - - if globalState != nil { - globalVer = globalState.Skills[name] - } - if projectState != nil { - projectVer = projectState.Skills[name] - } - - if globalVer == "" && projectVer == "" { - return "not installed" - } - - // If both scopes have the skill, show the project version (takes precedence). - if bothScopes && globalVer != "" && projectVer != "" { - return versionLabel(projectVer, latestVersion) + " (project, global)" - } - - if projectVer != "" { - label := versionLabel(projectVer, latestVersion) - if bothScopes { - return label + " (project)" - } - return label - } - - label := versionLabel(globalVer, latestVersion) - if bothScopes { - return label + " (global)" - } - return label -} - -// versionLabel formats version with update status. -func versionLabel(installed, latest string) string { - if installed == latest { - return "v" + installed + " (up to date)" - } - return "v" + installed + " (update available)" -} diff --git a/experimental/aitools/cmd/list_test.go b/experimental/aitools/cmd/list_test.go deleted file mode 100644 index 31390110c01..00000000000 --- a/experimental/aitools/cmd/list_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package aitools - -import ( - "testing" - - "github.com/databricks/cli/libs/cmdio" - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestListCommandExists(t *testing.T) { - cmd := newListCmd() - assert.Equal(t, "list", cmd.Use) -} - -func TestListCommandCallsListFn(t *testing.T) { - orig := listSkillsFn - t.Cleanup(func() { listSkillsFn = orig }) - - called := false - listSkillsFn = func(cmd *cobra.Command, scope string) error { - called = true - return nil - } - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newListCmd() - cmd.SetContext(ctx) - - err := cmd.RunE(cmd, nil) - require.NoError(t, err) - assert.True(t, called) -} - -func TestListCommandHasScopeFlags(t *testing.T) { - cmd := newListCmd() - f := cmd.Flags().Lookup("project") - require.NotNil(t, f, "--project flag should exist") - f = cmd.Flags().Lookup("global") - require.NotNil(t, f, "--global flag should exist") -} - -func TestSkillsListDelegatesToListFn(t *testing.T) { - orig := listSkillsFn - t.Cleanup(func() { listSkillsFn = orig }) - - called := false - listSkillsFn = func(cmd *cobra.Command, scope string) error { - called = true - return nil - } - - ctx := cmdio.MockDiscard(t.Context()) - cmd := newSkillsListCmd() - cmd.SetContext(ctx) - - err := cmd.RunE(cmd, nil) - require.NoError(t, err) - assert.True(t, called) -} diff --git a/experimental/aitools/cmd/query.go b/experimental/aitools/cmd/query.go index 7e9ae1d030d..c6291733edb 100644 --- a/experimental/aitools/cmd/query.go +++ b/experimental/aitools/cmd/query.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "io" "os" "os/signal" "strings" @@ -14,19 +13,15 @@ import ( "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/experimental/aitools/lib/middlewares" "github.com/databricks/cli/experimental/aitools/lib/session" + "github.com/databricks/cli/experimental/libs/sqlcli" "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/service/sql" "github.com/spf13/cobra" ) const ( - // sqlFileExtension is the file extension used to auto-detect SQL files. - sqlFileExtension = ".sql" - // pollIntervalInitial is the starting interval between status polls. pollIntervalInitial = 1 * time.Second @@ -35,16 +30,6 @@ const ( // cancelTimeout is how long to wait for server-side cancellation. cancelTimeout = 10 * time.Second - - // staticTableThreshold is the maximum number of rows rendered as a static table. - // Beyond this, an interactive scrollable table is used. - staticTableThreshold = 30 - - // outputCSV is the csv output format, supported only by the query command. - outputCSV = "csv" - - // envOutputFormat matches the env var name in cmd/root/io.go. - envOutputFormat = "DATABRICKS_OUTPUT_FORMAT" ) type queryOutputMode int @@ -55,8 +40,13 @@ const ( queryOutputModeInteractiveTable ) -func selectQueryOutputMode(outputType flags.Output, stdoutInteractive, promptSupported bool, rowCount int) queryOutputMode { - if outputType == flags.OutputJSON { +// selectQueryOutputMode picks the rendering mode for a single-query result. +// JSON is the only machine-readable option; static and interactive are +// table variants chosen by row count and TTY capabilities. Sharing only +// the threshold with sqlcli; the three-way decision is aitools-specific +// because the postgres command's renderers have a different shape. +func selectQueryOutputMode(format sqlcli.Format, stdoutInteractive, promptSupported bool, rowCount int) queryOutputMode { + if format == sqlcli.OutputJSON { return queryOutputModeJSON } if !stdoutInteractive { @@ -67,7 +57,7 @@ func selectQueryOutputMode(outputType flags.Output, stdoutInteractive, promptSup if !promptSupported { return queryOutputModeStaticTable } - if rowCount <= staticTableThreshold { + if rowCount <= sqlcli.StaticTableThreshold { return queryOutputModeStaticTable } return queryOutputModeInteractiveTable @@ -119,24 +109,15 @@ interactive table browser. Use --output csv to export results as CSV.`, RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - // Normalize case to match root --output behavior (flags.Output.Set lowercases). - outputFormat = strings.ToLower(outputFormat) - - // If --output wasn't explicitly passed, check the env var. - // Invalid env values are silently ignored, matching cmd/root/io.go. - if !cmd.Flag("output").Changed { - if v, ok := env.Lookup(ctx, envOutputFormat); ok { - switch flags.Output(strings.ToLower(v)) { - case flags.OutputText, flags.OutputJSON, outputCSV: - outputFormat = strings.ToLower(v) - } - } - } - - switch flags.Output(outputFormat) { - case flags.OutputText, flags.OutputJSON, outputCSV: - default: - return fmt.Errorf("unsupported output format %q, accepted values: text, json, csv", outputFormat) + // Resolve the effective format via sqlcli so the env-var + // precedence and explicit-text-on-pipe handling stays in sync + // across commands. We pass stdoutTTY=true to keep the original + // aitools behavior of not auto-falling-back to JSON here; the + // per-result render mode further down already handles the pipe + // case via selectQueryOutputMode. + format, err := sqlcli.ResolveFormat(ctx, outputFormat, cmd.Flag("output").Changed, true) + if err != nil { + return err } sqls, err := resolveSQLs(ctx, cmd, args, filePaths) @@ -146,7 +127,7 @@ interactive table browser. Use --output csv to export results as CSV.`, // Reject incompatible flag combinations before any API call so the // user sees the real error instead of an auth/warehouse failure. - if len(sqls) > 1 && flags.Output(outputFormat) != flags.OutputJSON { + if len(sqls) > 1 && format != sqlcli.OutputJSON { return fmt.Errorf("multiple queries require --output json (got %q); pass --output json to receive a JSON array of per-statement results", outputFormat) } @@ -173,7 +154,7 @@ interactive table browser. Use --output csv to export results as CSV.`, } // CSV bypasses the normal output mode selection. - if flags.Output(outputFormat) == outputCSV { + if format == sqlcli.OutputCSV { if len(columns) == 0 && len(rows) == 0 { return nil } @@ -190,7 +171,7 @@ interactive table browser. Use --output csv to export results as CSV.`, stdoutInteractive := cmdio.SupportsColor(ctx, cmd.OutOrStdout()) promptSupported := cmdio.IsPromptSupported(ctx) - switch selectQueryOutputMode(flags.Output(outputFormat), stdoutInteractive, promptSupported, len(rows)) { + switch selectQueryOutputMode(format, stdoutInteractive, promptSupported, len(rows)) { case queryOutputModeJSON: return renderJSON(cmd.OutOrStdout(), columns, rows) case queryOutputModeStaticTable: @@ -206,74 +187,34 @@ interactive table browser. Use --output csv to export results as CSV.`, cmd.Flags().IntVar(&concurrency, "concurrency", defaultBatchConcurrency, "Maximum in-flight statements when running a batch of queries") // Local --output flag shadows the root command's persistent --output flag, // adding csv support for this command only. - cmd.Flags().StringVarP(&outputFormat, "output", "o", string(flags.OutputText), "Output format: text, json, or csv") + cmd.Flags().StringVarP(&outputFormat, "output", "o", string(sqlcli.OutputText), "Output format: text, json, or csv") cmd.RegisterFlagCompletionFunc("output", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { - return []string{string(flags.OutputText), string(flags.OutputJSON), string(outputCSV)}, cobra.ShellCompDirectiveNoFileComp + out := make([]string, len(sqlcli.AllFormats)) + for i, f := range sqlcli.AllFormats { + out[i] = string(f) + } + return out, cobra.ShellCompDirectiveNoFileComp }) return cmd } // resolveSQLs collects SQL statements from --file paths, positional args, and -// stdin. The returned slice preserves source order: --file paths first (in flag -// order), then positional args (in arg order), then stdin (only if no other -// source produced anything). Each SQL is run through cleanSQL. +// stdin via sqlcli.Collect, then runs each through cleanSQL (the warehouse +// statement API doesn't care about line comments, so we strip them up front +// to normalise the wire payload). Returns just the SQL strings so the rest of +// this command's flow stays unchanged; the Source labels sqlcli adds are +// dropped on the floor (this command surfaces statement_id, not source). func resolveSQLs(ctx context.Context, cmd *cobra.Command, args, filePaths []string) ([]string, error) { - var raws []string - - for _, path := range filePaths { - data, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("read SQL file %s: %w", path, err) - } - raws = append(raws, string(data)) - } - - for _, arg := range args { - // If the argument looks like a .sql file, try to read it. - // Only fall through to literal SQL if the file doesn't exist. - // Surface other errors (permission denied, etc.) directly. - if strings.HasSuffix(arg, sqlFileExtension) { - data, err := os.ReadFile(arg) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("read SQL file: %w", err) - } - if err == nil { - raws = append(raws, string(data)) - continue - } - } - raws = append(raws, arg) - } - - if len(raws) == 0 { - // No --file and no positional args: try reading from stdin if it's piped. - // If stdin was overridden (e.g. cmd.SetIn in tests), always read from it. - // Otherwise, only read if stdin is not a TTY (i.e. piped input). - in := cmd.InOrStdin() - _, isOsFile := in.(*os.File) - if isOsFile && cmdio.IsPromptSupported(ctx) { - return nil, errors.New("no SQL provided; pass a SQL string, use --file, or pipe via stdin") - } - data, err := io.ReadAll(in) - if err != nil { - return nil, fmt.Errorf("read stdin: %w", err) - } - raws = append(raws, string(data)) + inputs, err := sqlcli.Collect(ctx, cmd.InOrStdin(), args, filePaths, sqlcli.CollectOptions{Cleaner: cleanSQL}) + if err != nil { + return nil, err } - - cleaned := make([]string, 0, len(raws)) - for i, raw := range raws { - c := cleanSQL(raw) - if c == "" { - if len(raws) == 1 { - return nil, errors.New("SQL statement is empty after removing comments and blank lines") - } - return nil, fmt.Errorf("SQL statement #%d is empty after removing comments and blank lines", i+1) - } - cleaned = append(cleaned, c) + out := make([]string, len(inputs)) + for i, in := range inputs { + out[i] = in.SQL } - return cleaned, nil + return out, nil } // runBatch executes multiple SQL statements in parallel and renders the result diff --git a/experimental/aitools/cmd/query_test.go b/experimental/aitools/cmd/query_test.go index 59de11d578a..8458629c8e7 100644 --- a/experimental/aitools/cmd/query_test.go +++ b/experimental/aitools/cmd/query_test.go @@ -10,9 +10,9 @@ import ( "time" "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/experimental/libs/sqlcli" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" - "github.com/databricks/cli/libs/flags" mocksql "github.com/databricks/databricks-sdk-go/experimental/mocks/service/sql" "github.com/databricks/databricks-sdk-go/service/sql" "github.com/spf13/cobra" @@ -271,7 +271,7 @@ func TestResolveWarehouseIDWithFlag(t *testing.T) { func TestSelectQueryOutputMode(t *testing.T) { tests := []struct { name string - outputType flags.Output + format sqlcli.Format stdoutInteractive bool promptSupported bool rowCount int @@ -279,7 +279,7 @@ func TestSelectQueryOutputMode(t *testing.T) { }{ { name: "json flag always returns json", - outputType: flags.OutputJSON, + format: sqlcli.OutputJSON, stdoutInteractive: true, promptSupported: true, rowCount: 999, @@ -287,7 +287,7 @@ func TestSelectQueryOutputMode(t *testing.T) { }, { name: "non interactive stdout returns json", - outputType: flags.OutputText, + format: sqlcli.OutputText, stdoutInteractive: false, promptSupported: true, rowCount: 5, @@ -295,33 +295,33 @@ func TestSelectQueryOutputMode(t *testing.T) { }, { name: "missing stdin interactivity falls back to static table", - outputType: flags.OutputText, + format: sqlcli.OutputText, stdoutInteractive: true, promptSupported: false, - rowCount: staticTableThreshold + 10, + rowCount: sqlcli.StaticTableThreshold + 10, want: queryOutputModeStaticTable, }, { name: "small results use static table", - outputType: flags.OutputText, + format: sqlcli.OutputText, stdoutInteractive: true, promptSupported: true, - rowCount: staticTableThreshold, + rowCount: sqlcli.StaticTableThreshold, want: queryOutputModeStaticTable, }, { name: "large results use interactive table", - outputType: flags.OutputText, + format: sqlcli.OutputText, stdoutInteractive: true, promptSupported: true, - rowCount: staticTableThreshold + 1, + rowCount: sqlcli.StaticTableThreshold + 1, want: queryOutputModeInteractiveTable, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - got := selectQueryOutputMode(tc.outputType, tc.stdoutInteractive, tc.promptSupported, tc.rowCount) + got := selectQueryOutputMode(tc.format, tc.stdoutInteractive, tc.promptSupported, tc.rowCount) assert.Equal(t, tc.want, got) }) } @@ -539,7 +539,7 @@ func TestResolveSQLsUnreadableSQLFileReturnsError(t *testing.T) { cmd := newTestCmd() _, err = resolveSQLs(cmdio.MockDiscard(t.Context()), cmd, []string{path}, nil) require.Error(t, err) - assert.Contains(t, err.Error(), "read SQL file") + assert.Contains(t, err.Error(), "permission denied") } func TestResolveSQLsFromStdin(t *testing.T) { @@ -579,14 +579,14 @@ func TestResolveSQLsBatchEmptyAtIndexReturnsIndexedError(t *testing.T) { cmd := newTestCmd() _, err := resolveSQLs(cmdio.MockDiscard(t.Context()), cmd, []string{"SELECT 1", "-- comment only", "SELECT 3"}, nil) require.Error(t, err) - assert.Contains(t, err.Error(), "SQL statement #2 is empty") + assert.Contains(t, err.Error(), "argv[2] is empty") } func TestResolveSQLsMissingFileReturnsError(t *testing.T) { cmd := newTestCmd() _, err := resolveSQLs(cmdio.MockDiscard(t.Context()), cmd, nil, []string{"/nonexistent/path/query.sql"}) require.Error(t, err) - assert.Contains(t, err.Error(), "read SQL file") + assert.Contains(t, err.Error(), "no such file") } func TestQueryCommandUnsupportedOutputReturnsError(t *testing.T) { diff --git a/experimental/aitools/cmd/skills.go b/experimental/aitools/cmd/skills.go deleted file mode 100644 index 9995ff72a07..00000000000 --- a/experimental/aitools/cmd/skills.go +++ /dev/null @@ -1,101 +0,0 @@ -package aitools - -import ( - "context" - "errors" - - "github.com/charmbracelet/huh" - "github.com/databricks/cli/experimental/aitools/lib/agents" - "github.com/databricks/cli/experimental/aitools/lib/installer" - "github.com/spf13/cobra" -) - -// Package-level vars for testability. -var ( - promptAgentSelection = defaultPromptAgentSelection - installSkillsForAgentsFn = installer.InstallSkillsForAgents -) - -func defaultPromptAgentSelection(ctx context.Context, detected []*agents.Agent) ([]*agents.Agent, error) { - options := make([]huh.Option[string], 0, len(detected)) - agentsByName := make(map[string]*agents.Agent, len(detected)) - for _, a := range detected { - options = append(options, huh.NewOption(a.DisplayName, a.Name).Selected(true)) - agentsByName[a.Name] = a - } - - var selected []string - err := huh.NewMultiSelect[string](). - Title("Select coding agents to install skills for"). - Description("space to toggle, enter to confirm"). - Options(options...). - Value(&selected). - Run() - if err != nil { - return nil, err - } - - if len(selected) == 0 { - return nil, errors.New("at least one agent must be selected") - } - - result := make([]*agents.Agent, 0, len(selected)) - for _, name := range selected { - result = append(result, agentsByName[name]) - } - return result, nil -} - -func newSkillsCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "skills", - Hidden: true, - Short: "Manage Databricks skills for coding agents", - Long: `Manage Databricks skills that extend coding agents with Databricks-specific capabilities.`, - } - - // Subcommands delegate to the flat top-level commands. - cmd.AddCommand(newSkillsListCmd()) - cmd.AddCommand(newSkillsInstallCmd()) - - return cmd -} - -func newSkillsListCmd() *cobra.Command { - return &cobra.Command{ - Use: "list", - Short: "List available skills", - RunE: func(cmd *cobra.Command, args []string) error { - // Default to showing all scopes (empty scope = both). - return listSkillsFn(cmd, "") - }, - } -} - -func newSkillsInstallCmd() *cobra.Command { - var includeExperimental bool - - cmd := &cobra.Command{ - Use: "install [skill-name]", - Short: "Install Databricks skills for detected coding agents", - Args: cobra.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - // Delegate to the flat install command's logic. - installCmd := newInstallCmd() - installCmd.SetContext(cmd.Context()) - - var delegateArgs []string - if len(args) > 0 { - delegateArgs = append(delegateArgs, "--skills", args[0]) - } - if includeExperimental { - delegateArgs = append(delegateArgs, "--experimental") - } - installCmd.SetArgs(delegateArgs) - return installCmd.Execute() - }, - } - - cmd.Flags().BoolVar(&includeExperimental, "experimental", false, "Include experimental skills") - return cmd -} diff --git a/experimental/aitools/lib/installer/SKILLS_VERSION b/experimental/aitools/lib/installer/SKILLS_VERSION deleted file mode 100644 index 027a383a35e..00000000000 --- a/experimental/aitools/lib/installer/SKILLS_VERSION +++ /dev/null @@ -1 +0,0 @@ -v0.1.5 diff --git a/experimental/aitools/lib/installer/version.go b/experimental/aitools/lib/installer/version.go deleted file mode 100644 index 0e942ca0a95..00000000000 --- a/experimental/aitools/lib/installer/version.go +++ /dev/null @@ -1,14 +0,0 @@ -package installer - -import ( - _ "embed" - "strings" -) - -//go:embed SKILLS_VERSION -var skillsVersionFile string - -// defaultSkillsRepoRef is the pinned tag of databricks/databricks-agent-skills. -// It is sourced from the SKILLS_VERSION file so automation can bump the pin -// with a single-line file edit instead of patching Go source. -var defaultSkillsRepoRef = strings.TrimSpace(skillsVersionFile) diff --git a/experimental/libs/sqlcli/input.go b/experimental/libs/sqlcli/input.go new file mode 100644 index 00000000000..68c3fe1bf77 --- /dev/null +++ b/experimental/libs/sqlcli/input.go @@ -0,0 +1,123 @@ +package sqlcli + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/databricks/cli/libs/cmdio" +) + +// SQLFileExtension is the file suffix that triggers the .sql autodetect on a +// positional argument: if `databricks ... query foo.sql` exists on disk, the +// argument is read as a SQL file; otherwise it's treated as literal SQL. +const SQLFileExtension = ".sql" + +// Input is one SQL statement to execute, paired with a label identifying its +// origin so multi-input renderers and error messages can refer back to "which +// of the N inputs failed". +type Input struct { + // SQL is the cleaned statement text. Always non-empty (Collect rejects + // inputs that clean to empty). + SQL string + // Source is a human-readable label: "--file PATH", "argv[N]", or "stdin". + Source string +} + +// CollectOptions controls per-command behavior. The zero value is fine for +// commands that just want plain trimmed input. +type CollectOptions struct { + // Cleaner is applied to each raw SQL after read (and before the empty + // check). The default is strings.TrimSpace; aitools passes a richer + // cleaner that strips SQL comments and surrounding quotes. Postgres + // passes the default because its multi-statement scanner needs comments + // preserved. + Cleaner func(string) string +} + +// Collect assembles the ordered list of inputs from --file paths, positional +// arguments, and stdin. +// +// Order is files-first, then positionals. Cobra/pflag does not preserve the +// user's interleaved CLI spelling: it collects all --file flags into one +// slice and all positionals into another, so callers cannot honour +// `--file q1.sql "SELECT 1" --file q2.sql` as written. +// +// Stdin is read only when neither --file nor positional input was provided, +// and only when stdin is not a prompt-capable TTY (otherwise we'd block +// waiting for input the user did not realise they had to type). +// +// Errors when: +// - A --file path can't be read or cleans to empty. +// - A positional that looks like a .sql file but read fails with a non- +// "does not exist" error (e.g. permission denied). +// - A positional cleans to empty. +// - Stdin is the only source and it's empty / blocked on a TTY. +func Collect(ctx context.Context, in io.Reader, args, files []string, opts CollectOptions) ([]Input, error) { + cleaner := opts.Cleaner + if cleaner == nil { + cleaner = strings.TrimSpace + } + + var inputs []Input + + for _, path := range files { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read --file %q: %w", path, err) + } + sql := cleaner(string(data)) + if sql == "" { + return nil, fmt.Errorf("--file %q is empty", path) + } + inputs = append(inputs, Input{SQL: sql, Source: "--file " + path}) + } + + for i, arg := range args { + // .sql autodetect: if the positional ends in .sql AND the file + // exists, read it as a SQL file. Other read errors (permission + // denied) surface directly. If the file does not exist, fall + // through and treat the positional as literal SQL — useful when + // the user passes a string that happens to end with ".sql". + if strings.HasSuffix(arg, SQLFileExtension) { + data, err := os.ReadFile(arg) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("read positional %q: %w", arg, err) + } + if err == nil { + sql := cleaner(string(data)) + if sql == "" { + return nil, fmt.Errorf("positional %q is empty", arg) + } + inputs = append(inputs, Input{SQL: sql, Source: arg}) + continue + } + } + sql := cleaner(arg) + if sql == "" { + return nil, fmt.Errorf("argv[%d] is empty", i+1) + } + inputs = append(inputs, Input{SQL: sql, Source: fmt.Sprintf("argv[%d]", i+1)}) + } + + if len(inputs) == 0 { + _, isOsFile := in.(*os.File) + if isOsFile && cmdio.IsPromptSupported(ctx) { + return nil, errors.New("no SQL provided; pass a SQL string, use --file, or pipe via stdin") + } + data, err := io.ReadAll(in) + if err != nil { + return nil, fmt.Errorf("read stdin: %w", err) + } + sql := cleaner(string(data)) + if sql == "" { + return nil, errors.New("no SQL provided") + } + inputs = append(inputs, Input{SQL: sql, Source: "stdin"}) + } + + return inputs, nil +} diff --git a/experimental/libs/sqlcli/input_test.go b/experimental/libs/sqlcli/input_test.go new file mode 100644 index 00000000000..3c2dd44fa8e --- /dev/null +++ b/experimental/libs/sqlcli/input_test.go @@ -0,0 +1,124 @@ +package sqlcli + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func writeTemp(t *testing.T, name, contents string) string { + t.Helper() + dir := t.TempDir() + p := filepath.Join(dir, name) + require.NoError(t, os.WriteFile(p, []byte(contents), 0o644)) + return p +} + +func TestCollect_PositionalOnly(t *testing.T) { + got, err := Collect(t.Context(), strings.NewReader(""), []string{"SELECT 1"}, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT 1", got[0].SQL) + assert.Equal(t, "argv[1]", got[0].Source) +} + +func TestCollect_MultiplePositionals(t *testing.T) { + got, err := Collect(t.Context(), strings.NewReader(""), []string{"SELECT 1", "SELECT 2"}, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 2) + assert.Equal(t, "SELECT 1", got[0].SQL) + assert.Equal(t, "SELECT 2", got[1].SQL) +} + +func TestCollect_FileOnly(t *testing.T) { + p := writeTemp(t, "q.sql", "SELECT * FROM t") + got, err := Collect(t.Context(), strings.NewReader(""), nil, []string{p}, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT * FROM t", got[0].SQL) + assert.Contains(t, got[0].Source, "--file") +} + +func TestCollect_FilesFirstThenPositionals(t *testing.T) { + p1 := writeTemp(t, "a.sql", "SELECT 1") + p2 := writeTemp(t, "b.sql", "SELECT 2") + got, err := Collect(t.Context(), strings.NewReader(""), []string{"SELECT 3"}, []string{p1, p2}, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 3) + assert.Equal(t, "SELECT 1", got[0].SQL) + assert.Equal(t, "SELECT 2", got[1].SQL) + assert.Equal(t, "SELECT 3", got[2].SQL) +} + +func TestCollect_DotSQLAutoDetect(t *testing.T) { + p := writeTemp(t, "data.sql", "SELECT 42") + got, err := Collect(t.Context(), strings.NewReader(""), []string{p}, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT 42", got[0].SQL) +} + +func TestCollect_DotSQLNotExistingFallsThroughToLiteral(t *testing.T) { + got, err := Collect(t.Context(), strings.NewReader(""), []string{"/nonexistent/path.sql"}, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "/nonexistent/path.sql", got[0].SQL) +} + +func TestCollect_StdinOnly(t *testing.T) { + got, err := Collect(t.Context(), strings.NewReader("SELECT 1\n"), nil, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT 1", got[0].SQL) + assert.Equal(t, "stdin", got[0].Source) +} + +func TestCollect_StdinIgnoredWhenPositionalsPresent(t *testing.T) { + got, err := Collect(t.Context(), strings.NewReader("FROM STDIN"), []string{"SELECT 1"}, nil, CollectOptions{}) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT 1", got[0].SQL) +} + +func TestCollect_EmptyStdinErrors(t *testing.T) { + _, err := Collect(t.Context(), strings.NewReader(""), nil, nil, CollectOptions{}) + assert.ErrorContains(t, err, "no SQL provided") +} + +func TestCollect_EmptyFileErrors(t *testing.T) { + p := writeTemp(t, "empty.sql", "") + _, err := Collect(t.Context(), strings.NewReader(""), nil, []string{p}, CollectOptions{}) + assert.ErrorContains(t, err, "is empty") +} + +func TestCollect_EmptyPositional(t *testing.T) { + _, err := Collect(t.Context(), strings.NewReader(""), []string{" "}, nil, CollectOptions{}) + assert.ErrorContains(t, err, "is empty") +} + +func TestCollect_CustomCleanerStripsComments(t *testing.T) { + cleaner := func(s string) string { + // Naive comment stripper: drop lines starting with -- + var lines []string + for line := range strings.SplitSeq(s, "\n") { + line = strings.TrimSpace(line) + if line != "" && !strings.HasPrefix(line, "--") { + lines = append(lines, line) + } + } + return strings.Join(lines, "\n") + } + got, err := Collect( + t.Context(), strings.NewReader(""), + []string{"-- ignored\nSELECT 1\n-- also ignored"}, + nil, + CollectOptions{Cleaner: cleaner}, + ) + require.NoError(t, err) + require.Len(t, got, 1) + assert.Equal(t, "SELECT 1", got[0].SQL) +} diff --git a/experimental/libs/sqlcli/output.go b/experimental/libs/sqlcli/output.go new file mode 100644 index 00000000000..4643303cd23 --- /dev/null +++ b/experimental/libs/sqlcli/output.go @@ -0,0 +1,93 @@ +// Package sqlcli holds patterns shared by experimental SQL-running commands +// (currently `experimental aitools tools query` and `experimental postgres +// query`). The package lives under experimental/libs/ rather than libs/ so +// the commands depending on it inherit experimental-stability guarantees: +// when both consumers graduate, this package can be promoted alongside +// (or its API stabilised first). +package sqlcli + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/databricks/cli/libs/env" +) + +// EnvOutputFormat matches the env var name in cmd/root/io.go. +// Reading it lets pipelines set DATABRICKS_OUTPUT_FORMAT once for all +// commands. +const EnvOutputFormat = "DATABRICKS_OUTPUT_FORMAT" + +// StaticTableThreshold is the row count above which interactive callers may +// hand off to libs/tableview's scrollable viewer. Smaller results stay in a +// static tabwriter table so they pipe to scripts unchanged. +const StaticTableThreshold = 30 + +// Format is the user-selectable output shape. Using a string typedef instead +// of an int enum keeps the help text and DATABRICKS_OUTPUT_FORMAT env var +// values self-describing. +type Format string + +const ( + OutputText Format = "text" + OutputJSON Format = "json" + OutputCSV Format = "csv" +) + +// AllFormats is the canonical order shown in completions / help. Sharing +// the slice avoids drift between consumers when a new format is added. +var AllFormats = []Format{OutputText, OutputJSON, OutputCSV} + +// ResolveFormat picks the effective output format. Precedence: +// +// 1. The local --output flag if it was explicitly set. +// 2. DATABRICKS_OUTPUT_FORMAT env var if set to a known value (invalid +// values are silently ignored, matching cmd/root/io.go and aitools). +// 3. The flag default (whatever the caller passes as flagValue). +// +// Then the auto-selection rule applies: a *defaulted* text mode on a non-TTY +// stdout falls back to JSON, so scripts piping the output get machine- +// readable output by default. An *explicit* --output text (flag or env) is +// honoured even on a pipe; per AGENTS.md we don't silently override flags +// the user set. +// +// flagSet is true if the user explicitly passed --output on the CLI. +// stdoutTTY is true if stdout is a terminal. +func ResolveFormat(ctx context.Context, flagValue string, flagSet, stdoutTTY bool) (Format, error) { + chosen := Format(strings.ToLower(flagValue)) + chosenExplicit := flagSet + + if !flagSet { + if v, ok := env.Lookup(ctx, EnvOutputFormat); ok { + candidate := Format(strings.ToLower(v)) + if IsKnown(candidate) { + chosen = candidate + chosenExplicit = true + } + } + } + + if !IsKnown(chosen) { + return "", fmt.Errorf("unsupported output format %q; expected one of: %s", flagValue, joinFormats(AllFormats)) + } + + if chosen == OutputText && !stdoutTTY && !chosenExplicit { + return OutputJSON, nil + } + return chosen, nil +} + +// IsKnown reports whether f is one of the formats in AllFormats. +func IsKnown(f Format) bool { + return slices.Contains(AllFormats, f) +} + +func joinFormats(formats []Format) string { + parts := make([]string, len(formats)) + for i, f := range formats { + parts[i] = string(f) + } + return strings.Join(parts, ", ") +} diff --git a/experimental/libs/sqlcli/output_test.go b/experimental/libs/sqlcli/output_test.go new file mode 100644 index 00000000000..1e91bd9cf3d --- /dev/null +++ b/experimental/libs/sqlcli/output_test.go @@ -0,0 +1,100 @@ +package sqlcli + +import ( + "testing" + + "github.com/databricks/cli/libs/env" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolveFormat_Defaults(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "text", false, true) + require.NoError(t, err) + assert.Equal(t, OutputText, got) +} + +func TestResolveFormat_TextOnPipeFallsBackToJSON(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "text", false, false) + require.NoError(t, err) + assert.Equal(t, OutputJSON, got) +} + +func TestResolveFormat_ExplicitTextOnPipeIsHonoured(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "text", true, false) + require.NoError(t, err) + assert.Equal(t, OutputText, got) +} + +func TestResolveFormat_EnvVarTextOnPipeIsHonoured(t *testing.T) { + ctx := env.Set(t.Context(), EnvOutputFormat, "text") + got, err := ResolveFormat(ctx, "text", false, false) + require.NoError(t, err) + assert.Equal(t, OutputText, got) +} + +func TestResolveFormat_EnvVarCSVOnPipe(t *testing.T) { + ctx := env.Set(t.Context(), EnvOutputFormat, "csv") + got, err := ResolveFormat(ctx, "text", false, false) + require.NoError(t, err) + assert.Equal(t, OutputCSV, got) +} + +func TestResolveFormat_ExplicitJSON(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "json", true, true) + require.NoError(t, err) + assert.Equal(t, OutputJSON, got) +} + +func TestResolveFormat_ExplicitCSV(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "csv", true, true) + require.NoError(t, err) + assert.Equal(t, OutputCSV, got) +} + +func TestResolveFormat_EnvVarHonoredWhenFlagNotSet(t *testing.T) { + ctx := env.Set(t.Context(), EnvOutputFormat, "csv") + got, err := ResolveFormat(ctx, "text", false, true) + require.NoError(t, err) + assert.Equal(t, OutputCSV, got) +} + +func TestResolveFormat_FlagOverridesEnvVar(t *testing.T) { + ctx := env.Set(t.Context(), EnvOutputFormat, "csv") + got, err := ResolveFormat(ctx, "json", true, true) + require.NoError(t, err) + assert.Equal(t, OutputJSON, got) +} + +func TestResolveFormat_InvalidEnvVarIgnored(t *testing.T) { + ctx := env.Set(t.Context(), EnvOutputFormat, "yaml") + got, err := ResolveFormat(ctx, "text", false, true) + require.NoError(t, err) + assert.Equal(t, OutputText, got) +} + +func TestResolveFormat_InvalidFlagErrors(t *testing.T) { + ctx := t.Context() + _, err := ResolveFormat(ctx, "yaml", true, true) + assert.ErrorContains(t, err, "unsupported output format") +} + +func TestResolveFormat_CaseInsensitive(t *testing.T) { + ctx := t.Context() + got, err := ResolveFormat(ctx, "JSON", true, true) + require.NoError(t, err) + assert.Equal(t, OutputJSON, got) +} + +func TestIsKnown(t *testing.T) { + assert.True(t, IsKnown(OutputText)) + assert.True(t, IsKnown(OutputJSON)) + assert.True(t, IsKnown(OutputCSV)) + assert.False(t, IsKnown(Format("yaml"))) + assert.False(t, IsKnown(Format(""))) +} diff --git a/experimental/postgres/cmd/cancel_test.go b/experimental/postgres/cmd/cancel_test.go new file mode 100644 index 00000000000..4245b905efc --- /dev/null +++ b/experimental/postgres/cmd/cancel_test.go @@ -0,0 +1,89 @@ +package postgrescmd + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestWithStatementTimeout_ZeroIsPassthrough(t *testing.T) { + parent := t.Context() + got, cancel := withStatementTimeout(parent, 0) + defer cancel() + // Parent and got should compare equal: zero timeout returns the parent + // unchanged (and a no-op cancel). + deadline, ok := got.Deadline() + assert.False(t, ok, "deadline should not be set when timeout is 0") + assert.True(t, deadline.IsZero()) +} + +func TestWithStatementTimeout_AppliesDeadline(t *testing.T) { + parent := t.Context() + got, cancel := withStatementTimeout(parent, time.Second) + defer cancel() + deadline, ok := got.Deadline() + assert.True(t, ok) + assert.False(t, deadline.IsZero()) +} + +func TestReportCancellation_SignalCanceled(t *testing.T) { + signalCtx, signalCancel := context.WithCancel(t.Context()) + signalCancel() + stmtCtx := signalCtx + msg, invocationScoped := reportCancellation(signalCtx, stmtCtx, errors.New("anything"), 0) + assert.Equal(t, "Query cancelled.", msg) + assert.True(t, invocationScoped) +} + +func TestReportCancellation_TimeoutFired(t *testing.T) { + signalCtx := t.Context() + stmtCtx, stmtCancel := context.WithDeadline(signalCtx, time.Now().Add(-time.Second)) + defer stmtCancel() + <-stmtCtx.Done() + msg, invocationScoped := reportCancellation(signalCtx, stmtCtx, errors.New("query failed"), 5*time.Second) + assert.Equal(t, "Query timed out after 5s.", msg) + assert.True(t, invocationScoped) +} + +func TestReportCancellation_GenericError(t *testing.T) { + signalCtx := t.Context() + stmtCtx := signalCtx + msg, invocationScoped := reportCancellation(signalCtx, stmtCtx, errors.New("syntax error"), 0) + assert.Equal(t, "syntax error", msg) + assert.False(t, invocationScoped) +} + +func TestReportCancellation_BothFire_CancelWinsRace(t *testing.T) { + // User cancel and deadline both already done. Precedence: cancel wins + // (the user's intent dominates a coincidental deadline). A future + // reordering of the switch would silently flip this; the test pins it. + signalCtx, signalCancel := context.WithCancel(t.Context()) + signalCancel() + stmtCtx, stmtCancel := context.WithDeadline(signalCtx, time.Now().Add(-time.Second)) + defer stmtCancel() + <-stmtCtx.Done() + msg, invocationScoped := reportCancellation(signalCtx, stmtCtx, errors.New("anything"), time.Second) + assert.Equal(t, "Query cancelled.", msg) + assert.True(t, invocationScoped) +} + +func TestWatchInterruptSignals_CancelOnStop(t *testing.T) { + // stop should cancel the parent context as a side-effect so the goroutine + // terminates promptly. We don't actually send a SIGINT here (it would + // also kill the test runner); we just verify stop cleans up. + parent, parentCancel := context.WithCancel(t.Context()) + defer parentCancel() + + cancelled := false + cancel := func() { + cancelled = true + parentCancel() + } + + stop := watchInterruptSignals(parent, cancel) + stop() + assert.True(t, cancelled, "stop should call cancel to wake the goroutine") +} diff --git a/experimental/postgres/cmd/cmd.go b/experimental/postgres/cmd/cmd.go new file mode 100644 index 00000000000..8db7b46be86 --- /dev/null +++ b/experimental/postgres/cmd/cmd.go @@ -0,0 +1,25 @@ +// Package postgrescmd registers the `databricks experimental postgres ...` +// command tree. The current sub-tree provides `query`, a scriptable SQL +// runner against any Lakebase Postgres endpoint that does not require a +// system `psql` binary. +package postgrescmd + +import ( + "github.com/spf13/cobra" +) + +// New returns the root `postgres` experimental command. It is hidden by its +// experimental parent; the command itself is always visible once one of its +// subcommands is reached. +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "postgres", + Short: "Experimental Lakebase Postgres commands", + Long: `Experimental commands for interacting with Lakebase Postgres endpoints. + +These commands are still under development and may change without notice.`, + } + + cmd.AddCommand(newQueryCmd()) + return cmd +} diff --git a/experimental/postgres/cmd/connect.go b/experimental/postgres/cmd/connect.go new file mode 100644 index 00000000000..0cc47c998a0 --- /dev/null +++ b/experimental/postgres/cmd/connect.go @@ -0,0 +1,183 @@ +package postgrescmd + +import ( + "context" + "errors" + "fmt" + "net" + "net/url" + "strconv" + "time" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/log" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgconn/ctxwatch" +) + +// defaultConnectTimeout is the dial timeout for a single connect attempt. +// Lakebase autoscaling endpoints can be cold-starting; Postgres' own dial +// keeps trying within this window before giving up. +const defaultConnectTimeout = 120 * time.Second + +// connectConfig collects everything pgx needs to dial Postgres. Kept as a +// struct rather than passed through positional args because the pgx config +// has many fields and the call sites differ between code paths (production +// vs unit tests stubbing connectFunc). +type connectConfig struct { + Host string + Port int + Username string + Password string + Database string + ConnectTimeout time.Duration +} + +// retryConfig controls connect retry on idle/waking endpoints. MaxAttempts is +// the total number of attempts: 1 means no retry, 3 means up to two retries +// with backoff between. We use the count-of-attempts reading rather than +// count-of-retries to match libs/psql.RetryConfig.MaxRetries semantics, so +// behavior stays consistent across the two commands sharing a flag name. +type retryConfig struct { + MaxAttempts int + InitialDelay time.Duration + MaxDelay time.Duration +} + +// connectFunc is a seam for unit tests: production wires pgx.ConnectConfig, +// tests inject failures (DNS, auth, ctx-cancel mid-connect). We deliberately +// do not wrap *pgx.Conn behind an interface for query execution; that surface +// is exercised by integration tests against real Lakebase endpoints. +type connectFunc func(ctx context.Context, cfg *pgx.ConnConfig) (*pgx.Conn, error) + +// buildPgxConfig parses a DSN that includes the real host so pgx derives the +// right TLSConfig and Fallbacks for sslmode=require. An empty host in the DSN +// makes pgx fall back to defaultHost(), which resolves to a unix-socket path. +// pgconn classifies that as a unix socket and assigns TLSConfig=nil; patching +// cfg.Host after the parse does not re-derive TLSConfig, so the connection +// goes out in plaintext and Lakebase rejects the pgwire startup with +// "Invalid protocol version: 196608". User, password, and connect timeout are +// patched as fields because tokens can contain characters that would need +// URL-escaping in userinfo. +// +// The context-watcher handler is overridden so context cancellation issues +// a Postgres CancelRequest on the side-channel rather than only closing the +// underlying TCP connection. Without this override, a Ctrl+C during a long +// SELECT would tear down the TCP socket but leave the server-side query +// running until it noticed the broken connection on its next write. +// +// CancelRequestDelay = 0: send the cancel-request immediately on ctx cancel. +// The user just hit Ctrl+C; we want the server to learn now. +// DeadlineDelay = 5s: if the cancel-request has not gotten the server to +// terminate the query within 5s, fall back to deadlining the connection. +// Zero DeadlineDelay would race the cancel-request and could leave the +// connection unusable. +func buildPgxConfig(c connectConfig) (*pgx.ConnConfig, error) { + dsn := fmt.Sprintf("postgresql://%s/%s?sslmode=require", + net.JoinHostPort(c.Host, strconv.Itoa(c.Port)), + url.PathEscape(c.Database)) + cfg, err := pgx.ParseConfig(dsn) + if err != nil { + return nil, fmt.Errorf("parse pgx config: %w", err) + } + cfg.User = c.Username + cfg.Password = c.Password + cfg.ConnectTimeout = c.ConnectTimeout + + cfg.BuildContextWatcherHandler = func(pgc *pgconn.PgConn) ctxwatch.Handler { + return &pgconn.CancelRequestContextWatcherHandler{ + Conn: pgc, + CancelRequestDelay: 0, + DeadlineDelay: 5 * time.Second, + } + } + return cfg, nil +} + +// connectWithRetry dials Postgres, retrying on connect-time errors that +// indicate the endpoint is asleep or in the middle of a wake-up. Errors that +// cannot be improved by retrying (auth failures, permission errors, +// post-query errors) are returned immediately. +// +// MaxAttempts must be >= 1 (caller validates). 1 means a single attempt +// with no retries. +func connectWithRetry(ctx context.Context, cfg *pgx.ConnConfig, rc retryConfig, dial connectFunc) (*pgx.Conn, error) { + delay := rc.InitialDelay + var lastErr error + + for attempt := 1; attempt <= rc.MaxAttempts; attempt++ { + if attempt > 1 { + cmdio.LogString(ctx, fmt.Sprintf("Connection attempt %d/%d failed, retrying in %v...", attempt-1, rc.MaxAttempts, delay)) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(delay): + } + if rc.MaxDelay > 0 { + delay = min(delay*2, rc.MaxDelay) + } + } + + conn, err := dial(ctx, cfg) + if err == nil { + return conn, nil + } + lastErr = err + + if !isRetryableConnectError(err) { + return nil, err + } + log.Debugf(ctx, "retryable connect error on attempt %d: %v", attempt, err) + } + + return nil, fmt.Errorf("failed to connect after %d attempts: %w", rc.MaxAttempts, lastErr) +} + +// isRetryableConnectError classifies whether an error from the connect path +// is a transient "endpoint asleep / cold-starting" failure. +// +// Retryable: +// - net.OpError with Op == "dial" (DNS resolution, TCP connect refused, +// host unreachable). The "endpoint asleep" cases. +// - pgconn.ConnectError that wraps a retryable network error. +// - Postgres connection-establishment SQLSTATE codes (08xxx). Lakebase +// emits these during cold-start. +// - Postgres "cannot_connect_now" (57P03), which Postgres returns during +// server startup ("the database system is starting up"). Plausibly emitted +// during the wake-up handshake window. We do NOT broaden to all of class 57: +// 57P01/57P02 are admin shutdowns (debatable) and 57014 is query_canceled. +// +// Not retryable: auth errors (28xxx), permission errors (42501), +// context cancellation/deadlines, anything after Query has been issued +// (caller never passes that to us; we only run before Query). +func isRetryableConnectError(err error) bool { + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return false + } + + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + switch { + // 08xxx is the connection_exception class. + case len(pgErr.Code) == 5 && pgErr.Code[:2] == "08": + return true + case pgErr.Code == "57P03": + return true + default: + return false + } + } + + var connectErr *pgconn.ConnectError + if errors.As(err, &connectErr) { + return isRetryableConnectError(connectErr.Unwrap()) + } + + var opErr *net.OpError + if errors.As(err, &opErr) { + return opErr.Op == "dial" + } + + return false +} diff --git a/experimental/postgres/cmd/connect_test.go b/experimental/postgres/cmd/connect_test.go new file mode 100644 index 00000000000..fd294ef2765 --- /dev/null +++ b/experimental/postgres/cmd/connect_test.go @@ -0,0 +1,155 @@ +package postgrescmd + +import ( + "context" + "errors" + "net" + "testing" + "time" + + "github.com/databricks/cli/libs/cmdio" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testCtx(t *testing.T) context.Context { + return cmdio.MockDiscard(t.Context()) +} + +func TestIsRetryableConnectError(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + { + name: "dial error", + err: &net.OpError{Op: "dial", Err: errors.New("connection refused")}, + want: true, + }, + { + name: "non-dial net.OpError", + err: &net.OpError{Op: "read", Err: errors.New("oops")}, + want: false, + }, + { + name: "08006 connection failure", + err: &pgconn.PgError{Code: "08006", Message: "connection failure"}, + want: true, + }, + { + name: "08001 cannot establish", + err: &pgconn.PgError{Code: "08001", Message: "sqlclient unable to establish sqlconnection"}, + want: true, + }, + { + name: "57P03 cannot_connect_now", + err: &pgconn.PgError{Code: "57P03", Message: "the database system is starting up"}, + want: true, + }, + { + name: "57P01 admin shutdown not retryable", + err: &pgconn.PgError{Code: "57P01"}, + want: false, + }, + { + name: "57014 query_canceled not retryable", + err: &pgconn.PgError{Code: "57014"}, + want: false, + }, + { + name: "28000 invalid auth", + err: &pgconn.PgError{Code: "28000", Message: "invalid authorization specification"}, + want: false, + }, + { + name: "28P01 invalid password", + err: &pgconn.PgError{Code: "28P01", Message: "invalid password"}, + want: false, + }, + { + name: "42501 insufficient privilege", + err: &pgconn.PgError{Code: "42501", Message: "permission denied"}, + want: false, + }, + { + name: "context cancelled", + err: context.Canceled, + want: false, + }, + { + name: "context deadline exceeded", + err: context.DeadlineExceeded, + want: false, + }, + { + name: "nil error never retryable", + err: nil, + want: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, isRetryableConnectError(tc.err)) + }) + } +} + +func TestConnectWithRetry_RespectsMaxAttempts(t *testing.T) { + ctx := testCtx(t) + calls := 0 + dialErr := &pgconn.PgError{Code: "08006"} + dial := func(ctx context.Context, cfg *pgx.ConnConfig) (*pgx.Conn, error) { + calls++ + return nil, dialErr + } + cfg := &pgx.ConnConfig{} + rc := retryConfig{MaxAttempts: 3, InitialDelay: 0, MaxDelay: 0} + + _, err := connectWithRetry(ctx, cfg, rc, dial) + require.Error(t, err) + assert.Equal(t, 3, calls, "expected 3 attempts (1 initial + 2 retries)") +} + +func TestConnectWithRetry_StopsOnNonRetryable(t *testing.T) { + ctx := testCtx(t) + calls := 0 + authErr := &pgconn.PgError{Code: "28P01"} + dial := func(ctx context.Context, cfg *pgx.ConnConfig) (*pgx.Conn, error) { + calls++ + return nil, authErr + } + cfg := &pgx.ConnConfig{} + rc := retryConfig{MaxAttempts: 3, InitialDelay: 0} + + _, err := connectWithRetry(ctx, cfg, rc, dial) + require.Error(t, err) + assert.Equal(t, 1, calls, "auth errors should not retry") +} + +func TestBuildPgxConfig(t *testing.T) { + cfg, err := buildPgxConfig(connectConfig{ + Host: "host.example.com", + Port: 5432, + Username: "user", + Password: "secret", + Database: "db", + ConnectTimeout: 30 * time.Second, + }) + require.NoError(t, err) + assert.Equal(t, "host.example.com", cfg.Host) + assert.Equal(t, uint16(5432), cfg.Port) + assert.Equal(t, "user", cfg.User) + assert.Equal(t, "secret", cfg.Password) + assert.Equal(t, "db", cfg.Database) + assert.Equal(t, 30*time.Second, cfg.ConnectTimeout) + + // sslmode=require must produce a non-nil TLSConfig for the real host. + // Connecting in plaintext makes Lakebase reject the pgwire startup with + // "Invalid protocol version: 196608". + require.NotNil(t, cfg.TLSConfig, "TLSConfig must be set for sslmode=require") + assert.Equal(t, "host.example.com", cfg.TLSConfig.ServerName) +} diff --git a/experimental/postgres/cmd/error.go b/experimental/postgres/cmd/error.go new file mode 100644 index 00000000000..02278a6c58b --- /dev/null +++ b/experimental/postgres/cmd/error.go @@ -0,0 +1,42 @@ +package postgrescmd + +import ( + "errors" + "fmt" + "strings" + + "github.com/jackc/pgx/v5/pgconn" +) + +// formatPgError renders an error in a friendlier form when it's a Postgres +// server-side error. *pgconn.PgError exposes Code, Severity, Message, Detail, +// Hint, and Position; the plain text form attaches what's set so users see +// SQLSTATE plus any hint upstream included. +// +// For non-PgError values, returns err.Error() unchanged so the caller can +// surface it directly. The richer LINE+caret rendering is out of scope for +// this PR; we stick with the plain shape for now. +func formatPgError(err error) string { + var pgErr *pgconn.PgError + if !errors.As(err, &pgErr) { + return err.Error() + } + + var sb strings.Builder + if pgErr.Severity != "" { + fmt.Fprintf(&sb, "%s: ", pgErr.Severity) + } else { + sb.WriteString("ERROR: ") + } + sb.WriteString(pgErr.Message) + if pgErr.Code != "" { + fmt.Fprintf(&sb, " (SQLSTATE %s)", pgErr.Code) + } + if pgErr.Detail != "" { + fmt.Fprintf(&sb, "\nDETAIL: %s", pgErr.Detail) + } + if pgErr.Hint != "" { + fmt.Fprintf(&sb, "\nHINT: %s", pgErr.Hint) + } + return sb.String() +} diff --git a/experimental/postgres/cmd/error_test.go b/experimental/postgres/cmd/error_test.go new file mode 100644 index 00000000000..f4d709468d1 --- /dev/null +++ b/experimental/postgres/cmd/error_test.go @@ -0,0 +1,48 @@ +package postgrescmd + +import ( + "errors" + "testing" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" +) + +func TestFormatPgError_NonPgError(t *testing.T) { + err := errors.New("plain error") + assert.Equal(t, "plain error", formatPgError(err)) +} + +func TestFormatPgError_BasicPgError(t *testing.T) { + err := &pgconn.PgError{ + Severity: "ERROR", + Code: "42601", + Message: `syntax error at or near "FRO"`, + } + assert.Equal(t, + `ERROR: syntax error at or near "FRO" (SQLSTATE 42601)`, + formatPgError(err), + ) +} + +func TestFormatPgError_WithDetailAndHint(t *testing.T) { + err := &pgconn.PgError{ + Severity: "ERROR", + Code: "42601", + Message: `syntax error at or near "FRO"`, + Hint: `Did you mean "FROM"?`, + Detail: "more context", + } + got := formatPgError(err) + assert.Contains(t, got, "ERROR:") + assert.Contains(t, got, "(SQLSTATE 42601)") + assert.Contains(t, got, "DETAIL: more context") + assert.Contains(t, got, `HINT: Did you mean "FROM"?`) +} + +func TestFormatPgError_WrappedPgError(t *testing.T) { + pg := &pgconn.PgError{Code: "42501", Message: "permission denied"} + wrapped := errors.New("query failed: " + pg.Error()) + // Plain error doesn't unwrap; falls through to err.Error. + assert.Contains(t, formatPgError(wrapped), "permission denied") +} diff --git a/experimental/postgres/cmd/execute.go b/experimental/postgres/cmd/execute.go new file mode 100644 index 00000000000..8d0b896031c --- /dev/null +++ b/experimental/postgres/cmd/execute.go @@ -0,0 +1,78 @@ +package postgrescmd + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +// rowSink consumes a query result one row at a time. Sinks that maintain open +// output structures (e.g. a streaming JSON array) implement OnError so they +// can close cleanly when the iteration terminates with a partial result. +type rowSink interface { + // Begin is called once with the column descriptions before any Row. + // For command-only statements (no rows), Begin is still called with an + // empty slice so the sink can lock in its rows-vs-command shape. + Begin(fields []pgconn.FieldDescription) error + // Row delivers one decoded row. Values aligns with the fields passed to + // Begin and uses pgx's Go type mapping (int64, float64, time.Time, ...). + Row(values []any) error + // End is called once on successful completion. + End(commandTag string) error + // OnError is called if iteration errors after Begin returned successfully. + // The sink is expected to flush any in-progress output structures so + // stdout remains well-formed. The caller still surfaces err to its caller. + // If Begin itself errors, OnError is NOT called: sinks must not write any + // framing before Begin succeeds. + OnError(err error) +} + +// executeOne runs a single SQL statement and streams the result through sink. +// +// We pass QueryExecModeExec explicitly (not the pgx default +// QueryExecModeCacheStatement) for two reasons: +// +// 1. Statement caching has no benefit for a one-shot CLI: the connection is +// closed at the end of the command, so the cached prepared statement +// never gets reused. +// 2. Exec mode uses Postgres' extended-protocol "exec" path with text-format +// result columns. We still call rows.Values() (not RawValues) so all +// three sinks see uniform Go-typed input; jsonValue/textValue then map +// those types back to canonical strings for text/CSV and to JSON-typed +// values for JSON. The wire format being text means pgx's decode is +// cheap (text -> Go) rather than binary -> Go. +// +// QueryExecModeExec still uses extended protocol with a single statement and +// no implicit transaction wrap, so transaction-disallowed DDL like +// CREATE DATABASE works. +func executeOne(ctx context.Context, conn *pgx.Conn, sql string, sink rowSink) error { + rows, err := conn.Query(ctx, sql, pgx.QueryExecModeExec) + if err != nil { + return fmt.Errorf("query failed: %w", err) + } + defer rows.Close() + + if err := sink.Begin(rows.FieldDescriptions()); err != nil { + return err + } + + for rows.Next() { + values, err := rows.Values() + if err != nil { + sink.OnError(err) + return fmt.Errorf("decode row: %w", err) + } + if err := sink.Row(values); err != nil { + sink.OnError(err) + return err + } + } + if err := rows.Err(); err != nil { + sink.OnError(err) + return fmt.Errorf("query failed: %w", err) + } + + return sink.End(rows.CommandTag().String()) +} diff --git a/experimental/postgres/cmd/internal/target/autoscaling.go b/experimental/postgres/cmd/internal/target/autoscaling.go new file mode 100644 index 00000000000..3e496611d6b --- /dev/null +++ b/experimental/postgres/cmd/internal/target/autoscaling.go @@ -0,0 +1,127 @@ +package target + +import ( + "context" + "errors" + "fmt" + + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +// ListProjects returns all autoscaling projects in the workspace. +func ListProjects(ctx context.Context, w *databricks.WorkspaceClient) ([]postgres.Project, error) { + return w.Postgres.ListProjectsAll(ctx, postgres.ListProjectsRequest{}) +} + +// ListBranches returns all branches under the given project. +// projectName is the SDK resource name like "projects/foo". +func ListBranches(ctx context.Context, w *databricks.WorkspaceClient, projectName string) ([]postgres.Branch, error) { + return w.Postgres.ListBranchesAll(ctx, postgres.ListBranchesRequest{Parent: projectName}) +} + +// ListEndpoints returns all endpoints under the given branch. +// branchName is the SDK resource name like "projects/foo/branches/bar". +func ListEndpoints(ctx context.Context, w *databricks.WorkspaceClient, branchName string) ([]postgres.Endpoint, error) { + return w.Postgres.ListEndpointsAll(ctx, postgres.ListEndpointsRequest{Parent: branchName}) +} + +// GetProject fetches a single project by ID. Unlike GetProvisioned, the +// Postgres autoscaling API populates the Name field on the response so we do +// not need to patch it. +func GetProject(ctx context.Context, w *databricks.WorkspaceClient, projectID string) (*postgres.Project, error) { + return w.Postgres.GetProject(ctx, postgres.GetProjectRequest{Name: pathSegmentProjects + "/" + projectID}) +} + +// GetEndpoint fetches a single endpoint by ID, given its parent IDs. Unlike +// GetProvisioned, the Postgres autoscaling API populates the Name field. +func GetEndpoint(ctx context.Context, w *databricks.WorkspaceClient, projectID, branchID, endpointID string) (*postgres.Endpoint, error) { + name := fmt.Sprintf("projects/%s/branches/%s/endpoints/%s", projectID, branchID, endpointID) + return w.Postgres.GetEndpoint(ctx, postgres.GetEndpointRequest{Name: name}) +} + +// AutoSelectProject returns the trailing project ID (e.g. "foo", not +// "projects/foo") if exactly one project exists. Returns an *AmbiguousError +// carrying the choices if there are multiple, or a plain error if there are none. +func AutoSelectProject(ctx context.Context, w *databricks.WorkspaceClient) (string, error) { + projects, err := ListProjects(ctx, w) + if err != nil { + return "", err + } + if len(projects) == 0 { + return "", errors.New("no Lakebase Autoscaling projects found in workspace") + } + if len(projects) == 1 { + return extractID(projects[0].Name, pathSegmentProjects), nil + } + + choices := make([]Choice, 0, len(projects)) + for _, p := range projects { + id := extractID(p.Name, pathSegmentProjects) + var display string + if p.Status != nil && p.Status.DisplayName != "" && p.Status.DisplayName != id { + display = p.Status.DisplayName + } + choices = append(choices, Choice{ID: id, DisplayName: display}) + } + return "", &AmbiguousError{Kind: KindProject, FlagHint: "--project", Choices: choices} +} + +// AutoSelectBranch returns the trailing branch ID under projectName if +// exactly one branch exists. Returns an *AmbiguousError if there are multiple. +// projectName is the SDK resource name (e.g. "projects/foo"). +func AutoSelectBranch(ctx context.Context, w *databricks.WorkspaceClient, projectName string) (string, error) { + branches, err := ListBranches(ctx, w, projectName) + if err != nil { + return "", err + } + if len(branches) == 0 { + return "", errors.New("no branches found in project") + } + if len(branches) == 1 { + return extractID(branches[0].Name, pathSegmentBranches), nil + } + + choices := make([]Choice, 0, len(branches)) + for _, b := range branches { + id := extractID(b.Name, pathSegmentBranches) + choices = append(choices, Choice{ID: id}) + } + return "", &AmbiguousError{Kind: KindBranch, Parent: projectName, FlagHint: "--branch", Choices: choices} +} + +// AutoSelectEndpoint returns the trailing endpoint ID under branchName if +// exactly one endpoint exists. Returns an *AmbiguousError if there are multiple. +// branchName is the SDK resource name (e.g. "projects/foo/branches/bar"). +func AutoSelectEndpoint(ctx context.Context, w *databricks.WorkspaceClient, branchName string) (string, error) { + endpoints, err := ListEndpoints(ctx, w, branchName) + if err != nil { + return "", err + } + if len(endpoints) == 0 { + return "", errors.New("no endpoints found in branch") + } + if len(endpoints) == 1 { + return extractID(endpoints[0].Name, pathSegmentEndpoints), nil + } + + choices := make([]Choice, 0, len(endpoints)) + for _, e := range endpoints { + id := extractID(e.Name, pathSegmentEndpoints) + choices = append(choices, Choice{ID: id}) + } + return "", &AmbiguousError{Kind: KindEndpoint, Parent: branchName, FlagHint: "--endpoint", Choices: choices} +} + +// AutoscalingCredential issues a short-lived OAuth token that can be used to +// authenticate to the given autoscaling endpoint. endpointName is the SDK +// resource name (e.g. "projects/foo/branches/bar/endpoints/baz"). +func AutoscalingCredential(ctx context.Context, w *databricks.WorkspaceClient, endpointName string) (string, error) { + cred, err := w.Postgres.GenerateDatabaseCredential(ctx, postgres.GenerateDatabaseCredentialRequest{ + Endpoint: endpointName, + }) + if err != nil { + return "", fmt.Errorf("failed to get database credentials: %w", err) + } + return cred.Token, nil +} diff --git a/experimental/postgres/cmd/internal/target/provisioned.go b/experimental/postgres/cmd/internal/target/provisioned.go new file mode 100644 index 00000000000..786e86d2886 --- /dev/null +++ b/experimental/postgres/cmd/internal/target/provisioned.go @@ -0,0 +1,37 @@ +package target + +import ( + "context" + "fmt" + + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/database" + "github.com/google/uuid" +) + +// GetProvisioned fetches a single provisioned instance by name. +// The Name field on the response can be empty; this function ensures it is +// populated from the input so downstream callers do not have to re-set it. +func GetProvisioned(ctx context.Context, w *databricks.WorkspaceClient, name string) (*database.DatabaseInstance, error) { + instance, err := w.Database.GetDatabaseInstance(ctx, database.GetDatabaseInstanceRequest{Name: name}) + if err != nil { + return nil, fmt.Errorf("failed to get database instance: %w", err) + } + if instance.Name == "" { + instance.Name = name + } + return instance, nil +} + +// ProvisionedCredential issues a short-lived OAuth token for the provisioned +// instance with the given name. +func ProvisionedCredential(ctx context.Context, w *databricks.WorkspaceClient, instanceName string) (string, error) { + cred, err := w.Database.GenerateDatabaseCredential(ctx, database.GenerateDatabaseCredentialRequest{ + InstanceNames: []string{instanceName}, + RequestId: uuid.NewString(), + }) + if err != nil { + return "", fmt.Errorf("failed to get database credentials: %w", err) + } + return cred.Token, nil +} diff --git a/experimental/postgres/cmd/internal/target/target.go b/experimental/postgres/cmd/internal/target/target.go new file mode 100644 index 00000000000..1874829acce --- /dev/null +++ b/experimental/postgres/cmd/internal/target/target.go @@ -0,0 +1,171 @@ +// Package target resolves Lakebase Postgres targets (provisioned instances and +// autoscaling endpoints) into the host, credential, and SDK metadata that +// callers need to open a connection. It is shared by `cmd/psql` and the +// `experimental postgres query` command so that both speak the same SDK. +package target + +import ( + "errors" + "fmt" + "strings" +) + +const ( + // pathSegmentProjects is the leading path segment that identifies an + // autoscaling resource path. Provisioned instance names never start with + // it. Use IsAutoscalingPath / ProjectIDFromName from outside this package + // instead of comparing the literal. + pathSegmentProjects = "projects" + pathSegmentBranches = "branches" + pathSegmentEndpoints = "endpoints" +) + +// AutoscalingSpec is a partial or full specification for an autoscaling endpoint. +// Empty fields signal "auto-select if exactly one exists, otherwise error". +type AutoscalingSpec struct { + ProjectID string + BranchID string + EndpointID string +} + +// Choice is a single candidate returned alongside an AmbiguousError so callers +// can either render the list to the user or prompt interactively. +// +// DisplayName is the optional friendlier label for the choice. Producers +// should leave it empty when no friendlier label exists; callers that prompt +// interactively can fall back to the ID. +type Choice struct { + ID string + DisplayName string +} + +// AmbiguousKind is the typed enum for what an AmbiguousError refers to. A +// typed enum (vs raw string) keeps producers and the pluralisation switch in +// AmbiguousError.Error in sync. +type AmbiguousKind string + +const ( + KindProject AmbiguousKind = "project" + KindBranch AmbiguousKind = "branch" + KindEndpoint AmbiguousKind = "endpoint" + KindInstance AmbiguousKind = "instance" +) + +// AmbiguousError is returned by AutoSelect* helpers when the SDK returns more +// than one candidate and the caller did not specify which one to pick. +// +// Callers that have a TTY (e.g. `databricks psql`) can use errors.As to detect +// this and prompt interactively. Callers that are non-interactive (e.g. the +// scriptable `postgres query` command) propagate it as a plain error: the +// formatted message already enumerates the choices. +type AmbiguousError struct { + Kind AmbiguousKind + // Parent is the SDK resource name that contained the ambiguity (e.g. + // "projects/foo" when listing branches), or empty when listing projects. + Parent string + // FlagHint is the flag a user would set to disambiguate (e.g. "--branch"). + FlagHint string + // Choices enumerates the candidates returned by the SDK. DisplayName is + // only set when it carries information beyond ID; an empty DisplayName + // suppresses the parenthetical suffix in Error(). + Choices []Choice +} + +func (e *AmbiguousError) Error() string { + plural := map[AmbiguousKind]string{ + KindProject: "projects", + KindBranch: "branches", + KindEndpoint: "endpoints", + KindInstance: "instances", + }[e.Kind] + if plural == "" { + plural = string(e.Kind) + } + + var sb strings.Builder + if e.Parent == "" { + fmt.Fprintf(&sb, "multiple %s found; specify %s:", plural, e.FlagHint) + } else { + fmt.Fprintf(&sb, "multiple %s found in %s; specify %s:", plural, e.Parent, e.FlagHint) + } + for _, c := range e.Choices { + sb.WriteString("\n - ") + sb.WriteString(c.ID) + if c.DisplayName != "" { + fmt.Fprintf(&sb, " (%s)", c.DisplayName) + } + } + return sb.String() +} + +// ParseAutoscalingPath extracts project, branch, and endpoint IDs from a +// resource path. Accepts partial paths: +// +// projects/foo +// projects/foo/branches/bar +// projects/foo/branches/bar/endpoints/baz +// +// Returns an error if the path is malformed or does not start with "projects/". +func ParseAutoscalingPath(input string) (AutoscalingSpec, error) { + parts := strings.Split(input, "/") + + if len(parts) < 2 || parts[0] != pathSegmentProjects { + return AutoscalingSpec{}, fmt.Errorf("invalid resource path: %s", input) + } + if parts[1] == "" { + return AutoscalingSpec{}, errors.New("invalid resource path: missing project ID") + } + spec := AutoscalingSpec{ProjectID: parts[1]} + + if len(parts) > 2 { + if len(parts) < 4 || parts[2] != pathSegmentBranches { + return AutoscalingSpec{}, errors.New("invalid resource path: expected 'branches' after project") + } + if parts[3] == "" { + return AutoscalingSpec{}, errors.New("invalid resource path: missing branch ID") + } + spec.BranchID = parts[3] + } + + if len(parts) > 4 { + if len(parts) < 6 || parts[4] != pathSegmentEndpoints { + return AutoscalingSpec{}, errors.New("invalid resource path: expected 'endpoints' after branch") + } + if parts[5] == "" { + return AutoscalingSpec{}, errors.New("invalid resource path: missing endpoint ID") + } + spec.EndpointID = parts[5] + } + + if len(parts) > 6 { + return AutoscalingSpec{}, fmt.Errorf("invalid resource path: trailing components after endpoint: %s", input) + } + + return spec, nil +} + +// extractID returns the value following component in a resource name. +// extractID("projects/foo/branches/bar", "branches") returns "bar". +// Returns the original name unchanged if component is not found. +func extractID(name, component string) string { + parts := strings.Split(name, "/") + for i := range len(parts) - 1 { + if parts[i] == component { + return parts[i+1] + } + } + return name +} + +// ProjectIDFromName extracts the project ID from a fully-qualified +// SDK resource name like "projects/foo" or "projects/foo/branches/bar". +// Returns the input unchanged if the name does not contain a "projects/" segment. +func ProjectIDFromName(name string) string { + return extractID(name, pathSegmentProjects) +} + +// IsAutoscalingPath reports whether s is an autoscaling resource path +// (i.e. starts with "projects/"). Provisioned instance names never do. +func IsAutoscalingPath(s string) bool { + return strings.HasPrefix(s, pathSegmentProjects+"/") +} diff --git a/experimental/postgres/cmd/internal/target/target_test.go b/experimental/postgres/cmd/internal/target/target_test.go new file mode 100644 index 00000000000..f1726890330 --- /dev/null +++ b/experimental/postgres/cmd/internal/target/target_test.go @@ -0,0 +1,145 @@ +package target + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseAutoscalingPath(t *testing.T) { + tests := []struct { + name string + input string + want AutoscalingSpec + wantErr string + }{ + { + name: "project only", + input: "projects/my-project", + want: AutoscalingSpec{ProjectID: "my-project"}, + }, + { + name: "project and branch", + input: "projects/my-project/branches/main", + want: AutoscalingSpec{ProjectID: "my-project", BranchID: "main"}, + }, + { + name: "full path", + input: "projects/my-project/branches/main/endpoints/primary", + want: AutoscalingSpec{ProjectID: "my-project", BranchID: "main", EndpointID: "primary"}, + }, + { + name: "missing project ID", + input: "projects/", + wantErr: "missing project ID", + }, + { + name: "missing branch ID", + input: "projects/my-project/branches/", + wantErr: "missing branch ID", + }, + { + name: "missing endpoint ID", + input: "projects/my-project/branches/main/endpoints/", + wantErr: "missing endpoint ID", + }, + { + name: "invalid segment after project", + input: "projects/my-project/invalid/foo", + wantErr: "expected 'branches' after project", + }, + { + name: "invalid segment after branch", + input: "projects/my-project/branches/main/invalid/foo", + wantErr: "expected 'endpoints' after branch", + }, + { + name: "not a projects path", + input: "something/else", + wantErr: "invalid resource path", + }, + { + name: "trailing components after endpoint", + input: "projects/foo/branches/bar/endpoints/baz/extra", + wantErr: "trailing components after endpoint", + }, + { + name: "empty input", + input: "", + wantErr: "invalid resource path", + }, + { + name: "single slash", + input: "/", + wantErr: "invalid resource path", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := ParseAutoscalingPath(tc.input) + if tc.wantErr != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} + +func TestProjectIDFromName(t *testing.T) { + assert.Equal(t, "foo", ProjectIDFromName("projects/foo")) + assert.Equal(t, "foo", ProjectIDFromName("projects/foo/branches/bar")) + assert.Equal(t, "no-projects", ProjectIDFromName("no-projects")) +} + +func TestIsAutoscalingPath(t *testing.T) { + assert.True(t, IsAutoscalingPath("projects/foo")) + assert.True(t, IsAutoscalingPath("projects/foo/branches/bar")) + assert.False(t, IsAutoscalingPath("my-instance")) + assert.False(t, IsAutoscalingPath("")) + assert.False(t, IsAutoscalingPath("projects")) +} + +func TestAmbiguousErrorMessage(t *testing.T) { + t.Run("with parent, no display names", func(t *testing.T) { + err := &AmbiguousError{ + Kind: KindBranch, + Parent: "projects/foo", + FlagHint: "--branch", + Choices: []Choice{ + {ID: "main"}, + {ID: "feature-x"}, + }, + } + assert.Equal(t, + "multiple branches found in projects/foo; specify --branch:\n - main\n - feature-x", + err.Error(), + ) + }) + + t.Run("without parent, mixed display names", func(t *testing.T) { + err := &AmbiguousError{ + Kind: KindProject, + FlagHint: "--project", + Choices: []Choice{ + {ID: "alpha", DisplayName: "Alpha Project"}, + {ID: "beta"}, + }, + } + assert.Equal(t, + "multiple projects found; specify --project:\n - alpha (Alpha Project)\n - beta", + err.Error(), + ) + }) + + t.Run("errors.As", func(t *testing.T) { + var amb *AmbiguousError + err := error(&AmbiguousError{Kind: KindEndpoint, FlagHint: "--endpoint"}) + assert.ErrorAs(t, err, &amb) + assert.Equal(t, KindEndpoint, amb.Kind) + }) +} diff --git a/experimental/postgres/cmd/multistatement.go b/experimental/postgres/cmd/multistatement.go new file mode 100644 index 00000000000..12032d1608b --- /dev/null +++ b/experimental/postgres/cmd/multistatement.go @@ -0,0 +1,179 @@ +package postgrescmd + +import ( + "errors" + "strings" +) + +// errMultipleStatements is the typed error returned by checkSingleStatement +// when the input contains more than one ';'-separated statement. The runQuery +// path catches this with errors.Is to attach the multi-input workaround +// pointer in the user-visible message. +var errMultipleStatements = errors.New("input contains multiple statements (a ';' separates two or more statements)") + +// checkSingleStatement walks sql and returns errMultipleStatements if a +// statement-terminating ';' is found anywhere except trailing whitespace. +// +// The scanner ignores ';' inside: +// - single-quoted strings ('a;b', SQL standard doubled-quote escape) +// - double-quoted identifiers ("col;name") +// - line comments (-- ... \n) +// - block comments (/* ... */, non-nesting) +// - dollar-quoted bodies ($tag$ ... $tag$, optional tag) +// +// Over-rejection on weird syntactic edge cases is acceptable: users get a +// clear error and can split into multiple input units. v2 may swap this for +// a real Postgres tokenizer. +func checkSingleStatement(sql string) error { + s := sql + // Trim trailing whitespace once so a single trailing ';' is allowed. + end := len(strings.TrimRight(s, " \t\r\n")) + + i := 0 + for i < end { + c := s[i] + + switch c { + case ';': + // A ';' that's not at end-of-trimmed-input is a separator. + if i < end-1 { + return errMultipleStatements + } + // Trailing ';' is fine. + i++ + + case '\'': + // Single-quoted string. SQL standard escape is '' (doubled). + i = scanQuoted(s, i, end, '\'') + + case '"': + // Double-quoted identifier. Same '"' doubling escape rule. + i = scanQuoted(s, i, end, '"') + + case '-': + // Line comment "--" runs to next newline. + if i+1 < end && s[i+1] == '-' { + i = scanLineComment(s, i, end) + } else { + i++ + } + + case '/': + // Block comment "/* ... */". + if i+1 < end && s[i+1] == '*' { + i = scanBlockComment(s, i, end) + } else { + i++ + } + + case '$': + // Dollar-quoted body: $tag$ ... $tag$ (tag may be empty). + tag, end2 := readDollarTag(s, i, end) + if tag != "" || end2 > i { + i = scanDollarBody(s, end2, end, tag) + } else { + i++ + } + + default: + i++ + } + } + + return nil +} + +// scanQuoted advances past a quoted string or identifier opened at s[start] +// with the given quote character. SQL standard doubles the quote to escape +// (e.g. doubling the quote inside the string). Returns the index of the byte AFTER the closing quote, or +// end if the string is unterminated (over-permissive: an unterminated string +// at EOF means there's no ';' inside it anyway). +func scanQuoted(s string, start, end int, quote byte) int { + i := start + 1 + for i < end { + if s[i] == quote { + if i+1 < end && s[i+1] == quote { + i += 2 // doubled-quote escape + continue + } + return i + 1 + } + i++ + } + return end +} + +func scanLineComment(s string, start, end int) int { + i := start + 2 + for i < end && s[i] != '\n' { + i++ + } + return i +} + +func scanBlockComment(s string, start, end int) int { + i := start + 2 + for i+1 < end { + if s[i] == '*' && s[i+1] == '/' { + return i + 2 + } + i++ + } + return end +} + +// readDollarTag inspects s[start] (which must be '$') and returns the tag +// between the two dollar signs and the index right after the closing first +// '$' of $tag$. If the construct doesn't look like a valid dollar-quote +// opener, returns ("", start) so the caller can fall through. +// +// Tag rule: a Postgres dollar-quote tag is an unquoted identifier — first +// char is ASCII letter or underscore, subsequent chars are letter/digit/ +// underscore. We reject anything outside that grammar so e.g. `$1` +// (parameter placeholder) and `$foo-bar$ ... $foo-bar$` (PG parses as two +// statements because `-` is not a tag char) are NOT treated as dollar +// quotes by this scanner. Empty tag is valid: `$$` is a marker, `$$body$$` +// is the body. +func readDollarTag(s string, start, end int) (string, int) { + i := start + 1 + for i < end { + c := s[i] + if c == '$' { + return s[start+1 : i], i + 1 + } + // First tag char must be a letter or underscore. + if i == start+1 { + if !isTagStart(c) { + return "", start + } + i++ + continue + } + // Subsequent tag chars: letter, digit, or underscore. + if !isTagCont(c) { + return "", start + } + i++ + } + return "", start +} + +func isTagStart(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' +} + +func isTagCont(c byte) bool { + return isTagStart(c) || (c >= '0' && c <= '9') +} + +// scanDollarBody advances past a $tag$...$tag$ body starting at start (the +// byte right after the opening tag's closing '$'). Returns the index of the +// byte AFTER the closing tag, or end if unterminated. +func scanDollarBody(s string, start, end int, tag string) int { + close := "$" + tag + "$" + idx := strings.Index(s[start:end], close) + if idx < 0 { + return end + } + return start + idx + len(close) +} diff --git a/experimental/postgres/cmd/multistatement_test.go b/experimental/postgres/cmd/multistatement_test.go new file mode 100644 index 00000000000..baeec020dd9 --- /dev/null +++ b/experimental/postgres/cmd/multistatement_test.go @@ -0,0 +1,73 @@ +package postgrescmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckSingleStatement(t *testing.T) { + tests := []struct { + name string + input string + wantErr bool + }{ + {name: "single statement", input: "SELECT 1", wantErr: false}, + {name: "trailing semicolon allowed", input: "SELECT 1;", wantErr: false}, + {name: "trailing semicolon plus whitespace", input: "SELECT 1;\n ", wantErr: false}, + {name: "two statements rejected", input: "SELECT 1; SELECT 2", wantErr: true}, + {name: "two statements with trailing semi", input: "SELECT 1; SELECT 2;", wantErr: true}, + + {name: "semicolon in single-quoted string", input: "SELECT 'a;b'", wantErr: false}, + {name: "semicolon in double-quoted ident", input: `SELECT "col;name" FROM t`, wantErr: false}, + {name: "doubled quote escape", input: "SELECT 'it''s;ok'", wantErr: false}, + {name: "doubled identifier quote", input: `SELECT "x""y;z" FROM t`, wantErr: false}, + + {name: "semicolon in line comment", input: "SELECT 1 -- x;y\n", wantErr: false}, + {name: "semicolon in block comment", input: "SELECT 1 /* x;y */", wantErr: false}, + {name: "block comment unterminated", input: "SELECT 1 /* unterminated", wantErr: false}, + + {name: "semicolon in dollar body untagged", input: "SELECT $$a;b$$", wantErr: false}, + {name: "semicolon in dollar body tagged", input: "SELECT $tag$a;b$tag$", wantErr: false}, + {name: "create function with body", input: "CREATE FUNCTION f() RETURNS int AS $$ BEGIN; END $$ LANGUAGE plpgsql", wantErr: false}, + + {name: "semi inside string then real semi", input: "SELECT 'a;b'; SELECT 2", wantErr: true}, + {name: "semi inside line comment then real semi", input: "SELECT 1 -- ; \n; SELECT 2", wantErr: true}, + {name: "semi inside dollar then real semi", input: "SELECT $$a;b$$; SELECT 2", wantErr: true}, + + {name: "leading whitespace", input: " ;", wantErr: false}, + {name: "empty input", input: "", wantErr: false}, + {name: "only whitespace", input: " \n\t ", wantErr: false}, + {name: "only semicolon", input: ";", wantErr: false}, + + // $1 / $2 placeholder syntax must not be confused with a dollar-quote + // tag (tags can't start with a digit per PG docs). + {name: "dollar-digit placeholders", input: "SELECT $1, $2 FROM t", wantErr: false}, + {name: "dollar-digit then real semi", input: "SELECT $1 FROM t; SELECT 2", wantErr: true}, + + // Tag must be an unquoted identifier. Punctuation rejects the + // candidate so the embedded ';' is NOT hidden inside a fake body. + {name: "dollar-tag with hyphen rejected", input: "SELECT $foo-bar$a;b$foo-bar$", wantErr: true}, + {name: "dollar-tag with dot rejected", input: "SELECT $foo.bar$a;b$foo.bar$", wantErr: true}, + {name: "dollar-tag with underscore", input: "SELECT $body_v2$a;b$body_v2$", wantErr: false}, + {name: "dollar-tag mixed letters digits", input: "SELECT $tag1$a;b$tag1$", wantErr: false}, + + // E-string escape syntax: scanner doesn't honour \' escape, so a + // backslash-escaped apostrophe terminates the literal early. We + // document the over-rejection rather than fix it (acceptable v1 + // stance per the plan); pin the behaviour here so the next person + // touching the scanner has to update the test. + {name: "E-string with backslash-escape over-rejects", input: `SELECT E'foo\';bar'`, wantErr: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := checkSingleStatement(tc.input) + if tc.wantErr { + assert.ErrorIs(t, err, errMultipleStatements) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/experimental/postgres/cmd/query.go b/experimental/postgres/cmd/query.go new file mode 100644 index 00000000000..ca28d6e84ac --- /dev/null +++ b/experimental/postgres/cmd/query.go @@ -0,0 +1,330 @@ +package postgrescmd + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/experimental/libs/sqlcli" + "github.com/databricks/cli/libs/cmdio" + "github.com/jackc/pgx/v5" + "github.com/spf13/cobra" +) + +// defaultDatabase is the database name used when --database is not set. +// Lakebase Autoscaling and Provisioned both use this name as their default. +const defaultDatabase = "databricks_postgres" + +// queryFlags is the union of every flag the query command exposes. Lifted +// out of newQueryCmd so unit-tested helpers (resolveTarget, etc.) can take +// it directly without poking at cobra internals. +type queryFlags struct { + targetingFlags + database string + connectTimeout time.Duration + maxRetries int + files []string + timeout time.Duration + + // outputFormat is the raw flag value. resolveOutputFormat turns it into + // the effective format (which may differ when stdout is piped). + outputFormat string + outputFormatSet bool +} + +func newQueryCmd() *cobra.Command { + var f queryFlags + + cmd := &cobra.Command{ + Use: "query [SQL | file.sql]...", + Short: "Run SQL statements against a Lakebase Postgres endpoint", + Long: `Execute one or more SQL statements against a Lakebase Postgres endpoint. + +Targeting (exactly one form required): + --target STRING Provisioned instance name OR autoscaling resource path + (e.g. my-instance, projects/foo/branches/main/endpoints/primary) + --project ID Autoscaling project ID + --branch ID Autoscaling branch ID (default: auto-select if exactly one) + --endpoint ID Autoscaling endpoint ID + +Inputs (positionals and --file may be combined; execution order is files-first +then positionals; stdin is used only when neither is present): + -f, --file PATH SQL file path (repeatable). Each file must contain + exactly one statement. + positional SQL string OR path ending in '.sql' that exists on disk. + +Output: + --output text Aligned table for rows-producing statements (default). + Falls back to JSON when stdout is not a terminal so + scripts piping the output get machine-readable results. + --output json For a single input: top-level array of row objects, + streamed. For multiple inputs: top-level array of + per-unit result objects ({"sql","kind","elapsed_ms",...}), + with each object buffered to completion. + --output csv Header row + one CSV row per result row, streamed. + Single-input only; multi-input + csv is rejected + pre-flight. Use --output json for multi-input. + +DATABRICKS_OUTPUT_FORMAT is honoured when --output is not explicitly set. + +Limitations (this release): + + - Single statement per input unit. Multi-statement strings (e.g. + "SELECT 1; SELECT 2") are rejected; pass each as a separate positional + or --file. + - No interactive REPL. 'databricks psql' continues to own that surface. + - Inputs run sequentially on one connection; session state (SET, temp + tables, prepared statement names) carries across them. + - The OAuth token is generated once per invocation and is valid for 1h. + Queries longer than that fail with an auth error. + - --output csv is rejected when more than one input unit is present; + use --output json or split into separate invocations. +`, + PreRunE: root.MustWorkspaceClient, + RunE: func(cmd *cobra.Command, args []string) error { + f.outputFormatSet = cmd.Flag("output").Changed + return runQuery(cmd.Context(), cmd, args, f) + }, + } + + cmd.Flags().StringVar(&f.target, "target", "", "Provisioned instance name OR autoscaling resource path") + cmd.Flags().StringVar(&f.project, "project", "", "Autoscaling project ID") + cmd.Flags().StringVar(&f.branch, "branch", "", "Autoscaling branch ID (default: auto-select if exactly one)") + cmd.Flags().StringVar(&f.endpoint, "endpoint", "", "Autoscaling endpoint ID (default: auto-select if exactly one)") + cmd.Flags().StringVarP(&f.database, "database", "d", defaultDatabase, "Database name") + cmd.Flags().DurationVar(&f.connectTimeout, "connect-timeout", defaultConnectTimeout, "Connect timeout") + cmd.Flags().IntVar(&f.maxRetries, "max-retries", 3, "Total connect attempts on idle/waking endpoint (must be >= 1; 1 disables retry)") + cmd.Flags().DurationVar(&f.timeout, "timeout", 0, "Per-statement timeout (0 disables)") + cmd.Flags().StringArrayVarP(&f.files, "file", "f", nil, "SQL file path (repeatable)") + cmd.Flags().StringVarP(&f.outputFormat, "output", "o", string(sqlcli.OutputText), "Output format: text, json, or csv") + cmd.RegisterFlagCompletionFunc("output", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + out := make([]string, len(sqlcli.AllFormats)) + for i, f := range sqlcli.AllFormats { + out[i] = string(f) + } + return out, cobra.ShellCompDirectiveNoFileComp + }) + + cmd.MarkFlagsMutuallyExclusive("target", "project") + cmd.MarkFlagsMutuallyExclusive("target", "branch") + cmd.MarkFlagsMutuallyExclusive("target", "endpoint") + + return cmd +} + +// runQuery is the production entry point. It is split out from RunE so unit +// tests can call it directly with a stubbed connectFunc once we add seam-based +// tests in a later PR. +func runQuery(ctx context.Context, cmd *cobra.Command, args []string, f queryFlags) error { + if f.maxRetries < 1 { + return fmt.Errorf("--max-retries must be at least 1; got %d", f.maxRetries) + } + if err := validateTargeting(f.targetingFlags); err != nil { + return err + } + + units, err := sqlcli.Collect(ctx, cmd.InOrStdin(), args, f.files, sqlcli.CollectOptions{}) + if err != nil { + return err + } + for _, u := range units { + if err := checkSingleStatement(u.SQL); err != nil { + return fmt.Errorf("%s: %w%s", u.Source, err, multiStatementHint) + } + } + + // IsOutputTTY checks the file-descriptor only. SupportsColor would also + // AND in NO_COLOR / TERM=dumb, which are colour preferences and have + // nothing to do with whether stdout is a pipe; folding them in here + // would silently demote interactive text output to JSON for users who + // have NO_COLOR set on a real terminal. + stdoutTTY := cmdio.IsOutputTTY(cmd.OutOrStdout()) + format, err := sqlcli.ResolveFormat(ctx, f.outputFormat, f.outputFormatSet, stdoutTTY) + if err != nil { + return err + } + + // CSV multi-input is rejected pre-flight: there is no sensible shape for + // a CSV that has to merge schemas across statements. The error names the + // flag pair and tells the user how to recover, per the repo rule about + // rejecting incompatible inputs early. + if format == sqlcli.OutputCSV && len(units) > 1 { + return fmt.Errorf("--output csv requires a single input unit; got %d (use --output json for multi-input invocations)", len(units)) + } + + resolved, err := resolveTarget(ctx, f.targetingFlags) + if err != nil { + return err + } + + pgxCfg, err := buildPgxConfig(connectConfig{ + Host: resolved.Host, + Port: 5432, + Username: resolved.Username, + Password: resolved.Token, + Database: f.database, + ConnectTimeout: f.connectTimeout, + }) + if err != nil { + return err + } + + rc := retryConfig{ + MaxAttempts: f.maxRetries, + InitialDelay: time.Second, + MaxDelay: 10 * time.Second, + } + + // Invocation-scoped context: cancelled by Ctrl+C/SIGTERM. Owns the + // connection lifecycle. Per-statement timeouts are children of this so + // a cancelled invocation also cancels the in-flight statement. + signalCtx, signalCancel := context.WithCancel(ctx) + defer signalCancel() + + stopSignals := watchInterruptSignals(signalCtx, signalCancel) + defer stopSignals() + + // Spinner clears its line on Close, so the "Connecting to ..." status + // disappears once the connection is up. cmdio.NewSpinner already writes + // to stderr and degrades to a no-op in non-interactive terminals. + sp := cmdio.NewSpinner(signalCtx) + sp.Update("Connecting to " + resolved.DisplayName) + conn, err := connectWithRetry(signalCtx, pgxCfg, rc, pgx.ConnectConfig) + sp.Close() + if err != nil { + return err + } + // Close on a background ctx so a cancelled signalCtx does not abort a + // clean teardown handshake. + defer conn.Close(context.WithoutCancel(ctx)) + + out := cmd.OutOrStdout() + stderr := cmd.ErrOrStderr() + + if len(units) == 1 { + // Single-input path: stream directly through the per-format sink. + // Avoids buffering rows for large exports and matches the v1 single- + // input behaviour PR 2 shipped. Wrap the error so DETAIL / HINT + // from a *pgconn.PgError surface even on the single-input path. + // Promote-to-interactive only when stdout is a prompt-capable TTY so + // a pipe falls back to the static table rather than launching a TUI + // into a dead writer. + sink := newSinkInteractive(format, out, stderr, stdoutTTY && cmdio.IsPromptSupported(ctx)) + stmtCtx, stmtCancel := withStatementTimeout(signalCtx, f.timeout) + err := executeOne(stmtCtx, conn, units[0].SQL, sink) + stmtCancel() + if err != nil { + msg, _ := reportCancellation(signalCtx, stmtCtx, err, f.timeout) + return errors.New(msg) + } + return nil + } + + // Multi-input path: per-unit buffering. The plan accepts this trade-off + // (multi-input invocations with huge SELECTs should use single-input + // invocations with --output csv for streaming). Session state (SET, + // temp tables) carries across units because we hold the same connection. + results := make([]*unitResult, 0, len(units)) + for _, u := range units { + stmtCtx, stmtCancel := withStatementTimeout(signalCtx, f.timeout) + r, err := runUnitBuffered(stmtCtx, conn, u) + stmtCancel() + if err != nil { + // Render the successful prefix, then surface the error with + // rich pgError formatting if applicable. + if rerr := renderPartial(out, stderr, format, results, r, err); rerr != nil { + // Best-effort partial render failed; surface the original + // error to the user, the renderer error to debug logs. + fmt.Fprintln(stderr, "warning: failed to render partial result:", rerr) + } + msg, invocationScoped := reportCancellation(signalCtx, stmtCtx, err, f.timeout) + if invocationScoped { + // User cancel / timeout is invocation-scoped; the source + // prefix is redundant ("--file foo.sql: Query cancelled." + // reads worse than just "Query cancelled."). + return errors.New(msg) + } + return errors.New(u.Source + ": " + msg) + } + results = append(results, r) + } + + switch format { + case sqlcli.OutputJSON: + return renderJSONMulti(out, stderr, results, nil, "") + default: + return renderTextMulti(out, results) + } +} + +// withStatementTimeout returns ctx unchanged (and a no-op cancel) when +// timeout is zero, otherwise a child context with the timeout applied. Each +// statement gets its own deadline so cancellation is scoped to one +// statement at a time. +func withStatementTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) { + if timeout <= 0 { + return parent, func() {} + } + return context.WithTimeout(parent, timeout) +} + +// reportCancellation distinguishes the three error cases that look the same +// from `executeOne`'s POV (a wrapped pgconn / network error): user cancelled +// via Ctrl+C, --timeout fired, or the statement just plain errored. Returns +// the human-readable message and whether the cause is invocation-scoped +// (cancel/timeout) rather than statement-scoped. +// +// Precedence: user cancel beats deadline. If both contexts fire near- +// simultaneously (race), we report "cancelled" because the user's intent +// dominates a coincidental timeout. +func reportCancellation(signalCtx, stmtCtx context.Context, err error, timeout time.Duration) (msg string, invocationScoped bool) { + switch { + case errors.Is(signalCtx.Err(), context.Canceled): + return "Query cancelled.", true + case timeout > 0 && errors.Is(stmtCtx.Err(), context.DeadlineExceeded): + return fmt.Sprintf("Query timed out after %s.", timeout), true + default: + return formatPgError(err), false + } +} + +// newSinkInteractive returns the rowSink for the chosen output format. When +// interactive is true the text sink may launch the libs/tableview viewer for +// results larger than staticTableThreshold; when false it uses the static +// tabwriter table. +func newSinkInteractive(format sqlcli.Format, out, stderr io.Writer, interactive bool) rowSink { + switch format { + case sqlcli.OutputJSON: + return newJSONSink(out, stderr) + case sqlcli.OutputCSV: + return newCSVSink(out, stderr) + default: + if interactive { + return newInteractiveTextSink(out) + } + return newTextSink(out) + } +} + +// renderPartial emits the rendered output for the prefix of units that ran +// successfully before a unit errored. For multi-input json this also writes +// the error envelope as the last array element. +func renderPartial(out, stderr io.Writer, format sqlcli.Format, results []*unitResult, errored *unitResult, err error) error { + switch format { + case sqlcli.OutputJSON: + return renderJSONMulti(out, stderr, results, errored, formatPgError(err)) + default: + // Text: render whatever ran cleanly. The error message goes through + // cobra's default error path on stderr after we return. + return renderTextMulti(out, results) + } +} + +// multiStatementHint is appended to errMultipleStatements so users see the +// recovery path inline. +const multiStatementHint = "\nThis command runs one statement per input. To run multiple statements:\n" + + ` - Pass each as a separate positional: query "SELECT 1" "SELECT 2"` + "\n" + + ` - Pass each in its own --file: query --file q1.sql --file q2.sql` diff --git a/experimental/postgres/cmd/render.go b/experimental/postgres/cmd/render.go new file mode 100644 index 00000000000..0d09556ece7 --- /dev/null +++ b/experimental/postgres/cmd/render.go @@ -0,0 +1,126 @@ +package postgrescmd + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + + "github.com/databricks/cli/libs/tableview" + "github.com/jackc/pgx/v5/pgconn" +) + +// staticTableThreshold is the row count above which we hand off to +// libs/tableview's interactive viewer (when stdout is interactive). Smaller +// results stay in the static tabwriter path so they stream to a pipe +// unchanged. Matches the threshold aitools query uses. +const staticTableThreshold = 30 + +// textSink renders results as plain text: a tabwriter-aligned table for +// rows-producing statements, the command tag for command-only ones. +// +// Text output buffers all rows because tabwriter needs the widest cell in each +// column before it can align. Streaming output is provided by the JSON and CSV +// sinks; users with huge result sets should pick those. +// +// When interactive is true and the result has more than staticTableThreshold +// rows, End hands off to libs/tableview's scrollable viewer instead of +// emitting the static table. The interactive path requires a real TTY and a +// prompt-capable terminal; the caller decides. +type textSink struct { + out io.Writer + interactive bool + columns []string + rows [][]string +} + +func newTextSink(out io.Writer) *textSink { + return &textSink{out: out} +} + +// newInteractiveTextSink returns a text sink that uses the interactive table +// viewer for results larger than staticTableThreshold. +func newInteractiveTextSink(out io.Writer) *textSink { + return &textSink{out: out, interactive: true} +} + +func (s *textSink) Begin(fields []pgconn.FieldDescription) error { + s.columns = make([]string, len(fields)) + for i, f := range fields { + s.columns[i] = f.Name + } + return nil +} + +func (s *textSink) Row(values []any) error { + row := make([]string, len(values)) + for i, v := range values { + row[i] = escapeControlForTabwriter(textValue(v)) + } + s.rows = append(s.rows, row) + return nil +} + +// escapeControlForTabwriter replaces tabs, newlines, and carriage returns in +// a cell value with the two-character backslash-letter sequence. tabwriter +// uses '\t' as a column boundary and '\n' as a row boundary, so an embedded +// tab silently shifts subsequent columns and an embedded newline splits one +// logical row into two. psql's text mode applies the same escapes. +func escapeControlForTabwriter(s string) string { + if !strings.ContainsAny(s, "\t\n\r") { + return s + } + r := strings.NewReplacer("\t", `\t`, "\n", `\n`, "\r", `\r`) + return r.Replace(s) +} + +func (s *textSink) End(commandTag string) error { + if len(s.columns) == 0 { + _, err := fmt.Fprintln(s.out, commandTag) + return err + } + + if s.interactive && len(s.rows) > staticTableThreshold { + // Try the interactive viewer; on failure (TUI startup, terminal + // resize race, etc.) fall through to the static path so the user + // still sees the rows their query returned. Without this fallback + // a successful query would surface as "viewer failed" with no data. + if err := tableview.Run(s.out, s.columns, s.rows); err == nil { + return nil + } + } + + tw := tabwriter.NewWriter(s.out, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, strings.Join(s.columns, "\t")) + fmt.Fprintln(tw, strings.Join(headerSeparator(s.columns), "\t")) + for _, row := range s.rows { + fmt.Fprintln(tw, strings.Join(row, "\t")) + } + if err := tw.Flush(); err != nil { + return err + } + + _, err := fmt.Fprintf(s.out, "(%d %s)\n", len(s.rows), pluralize(len(s.rows), "row", "rows")) + return err +} + +// OnError for text sinks is a no-op. Text mode buffers all rows for +// tabwriter alignment, so a partial result is discarded on iteration error; +// only cobra's error message reaches the user. The streaming sinks (json, +// csv) handle the partial-result case themselves. +func (s *textSink) OnError(err error) {} + +func headerSeparator(cols []string) []string { + out := make([]string, len(cols)) + for i, c := range cols { + out[i] = strings.Repeat("-", max(len(c), 3)) + } + return out +} + +func pluralize(n int, singular, plural string) string { + if n == 1 { + return singular + } + return plural +} diff --git a/experimental/postgres/cmd/render_csv.go b/experimental/postgres/cmd/render_csv.go new file mode 100644 index 00000000000..940e11324f5 --- /dev/null +++ b/experimental/postgres/cmd/render_csv.go @@ -0,0 +1,80 @@ +package postgrescmd + +import ( + "encoding/csv" + "fmt" + "io" + + "github.com/jackc/pgx/v5/pgconn" +) + +// csvSink streams query results as CSV. Header row is written on Begin, each +// data row is written and flushed individually so large exports do not buffer +// in memory. +// +// For command-only statements CSV has nothing meaningful to emit (no header, +// no rows): we write the command tag to stderr so machine consumers reading +// stdout still receive an empty document, while humans get a confirmation. +type csvSink struct { + out io.Writer + stderr io.Writer + w *csv.Writer + + // rowsProducing is true once Begin saw a non-empty fields slice. End + // uses it to decide whether to write the command-tag stderr line. + rowsProducing bool +} + +func newCSVSink(out, stderr io.Writer) *csvSink { + return &csvSink{out: out, stderr: stderr, w: csv.NewWriter(out)} +} + +func (s *csvSink) Begin(fields []pgconn.FieldDescription) error { + if len(fields) == 0 { + return nil + } + s.rowsProducing = true + + header := make([]string, len(fields)) + for i, f := range fields { + header[i] = f.Name + } + if err := s.w.Write(header); err != nil { + return fmt.Errorf("write CSV header: %w", err) + } + s.w.Flush() + return s.w.Error() +} + +func (s *csvSink) Row(values []any) error { + row := make([]string, len(values)) + for i, v := range values { + // CSV represents NULL as an empty field, matching `psql --csv`. + if v == nil { + row[i] = "" + continue + } + row[i] = textValue(v) + } + if err := s.w.Write(row); err != nil { + return fmt.Errorf("write CSV row: %w", err) + } + s.w.Flush() + return s.w.Error() +} + +func (s *csvSink) End(commandTag string) error { + if !s.rowsProducing { + _, err := fmt.Fprintln(s.stderr, commandTag) + return err + } + s.w.Flush() + return s.w.Error() +} + +// OnError flushes whatever is buffered in the csv.Writer so the partial result +// is visible to the consumer. csv.Writer has no concept of "open structure", +// so there is nothing more to do. +func (s *csvSink) OnError(err error) { + s.w.Flush() +} diff --git a/experimental/postgres/cmd/render_csv_test.go b/experimental/postgres/cmd/render_csv_test.go new file mode 100644 index 00000000000..5a3ee277e2c --- /dev/null +++ b/experimental/postgres/cmd/render_csv_test.go @@ -0,0 +1,69 @@ +package postgrescmd + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCSVSink_TwoRows(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(fields("id", "name"))) + require.NoError(t, s.Row([]any{int64(1), "alice"})) + require.NoError(t, s.Row([]any{int64(2), "bob"})) + require.NoError(t, s.End("SELECT 2")) + + assert.Equal(t, "id,name\n1,alice\n2,bob\n", stdout.String()) + assert.Empty(t, stderr.String()) +} + +func TestCSVSink_NULLEmptyField(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(fields("id", "note"))) + require.NoError(t, s.Row([]any{int64(1), nil})) + require.NoError(t, s.End("SELECT 1")) + + assert.Equal(t, "id,note\n1,\n", stdout.String()) +} + +func TestCSVSink_CommandOnly(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(nil)) + require.NoError(t, s.End("CREATE DATABASE")) + assert.Empty(t, stdout.String()) + assert.Equal(t, "CREATE DATABASE\n", stderr.String()) +} + +func TestCSVSink_QuotesFieldsWithCommas(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(fields("note"))) + require.NoError(t, s.Row([]any{"a,b"})) + require.NoError(t, s.End("SELECT 1")) + assert.Contains(t, stdout.String(), `"a,b"`) +} + +func TestCSVSink_EmbeddedNewlineAndQuote(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(fields("note"))) + require.NoError(t, s.Row([]any{"line1\nline2 \"quoted\""})) + require.NoError(t, s.End("SELECT 1")) + assert.Contains(t, stdout.String(), "\"line1\nline2 \"\"quoted\"\"\"") +} + +func TestCSVSink_OnError_NoOp(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newCSVSink(&stdout, &stderr) + require.NoError(t, s.Begin(fields("id"))) + require.NoError(t, s.Row([]any{int64(1)})) + s.OnError(assert.AnError) + // CSV has no open structure to close; partial row count plus header is + // what the consumer sees. The sink must not panic on OnError. + assert.Contains(t, stdout.String(), "id\n1\n") +} diff --git a/experimental/postgres/cmd/render_json.go b/experimental/postgres/cmd/render_json.go new file mode 100644 index 00000000000..c50739e2d1f --- /dev/null +++ b/experimental/postgres/cmd/render_json.go @@ -0,0 +1,220 @@ +package postgrescmd + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strconv" + + "github.com/jackc/pgx/v5/pgconn" +) + +// jsonSink streams query results as JSON. +// +// For rows-producing statements the output is a top-level array of row +// objects. We use the separator-before-element pattern to avoid the +// "rewrite the trailing comma" trick and keep the JSON parseable even when +// iteration ends with a partial result (caller closes the array on OnError). +// +// For command-only statements the output is a single object describing the +// command tag. +type jsonSink struct { + out io.Writer + stderr io.Writer + + // columns are the disambiguated column names: duplicates beyond the first + // occurrence are renamed to "__2", "__3", etc. Postgres + // allows duplicate output names (`SELECT 1, 1`, joins with two unaliased + // `id` columns) but JSON consumers usually want unique keys; we rename + // deterministically and warn once on stderr. + columns []string + oids []uint32 + + // hasOpenedArray is true once the leading `[\n` has been written. Used + // by OnError to decide whether to emit the closing `]\n` to keep stdout + // well-formed. + hasOpenedArray bool + // rowsWritten counts emitted rows so the separator decision is trivial: + // 0 means "first row, no separator", anything else means "separator first". + rowsWritten int +} + +func newJSONSink(out, stderr io.Writer) *jsonSink { + return &jsonSink{out: out, stderr: stderr} +} + +func (s *jsonSink) Begin(fields []pgconn.FieldDescription) error { + if len(fields) == 0 { + // Command-only; we wait until End to emit the {"command": ...} object. + return nil + } + + s.columns = make([]string, len(fields)) + s.oids = make([]uint32, len(fields)) + // assigned tracks every name we have committed to s.columns so far. This + // must include both first-occurrence names and __N suffixed renames, so a + // query whose original column list contains the same suffix we'd generate + // (e.g. ["id", "id__2", "id"]) does not produce two id__2 keys. + assigned := make(map[string]struct{}, len(fields)) + dupes := false + for i, f := range fields { + s.oids[i] = f.DataTypeOID + name := f.Name + if _, taken := assigned[name]; taken { + dupes = true + suffix := 2 + for { + candidate := fmt.Sprintf("%s__%d", f.Name, suffix) + if _, taken := assigned[candidate]; !taken { + name = candidate + break + } + suffix++ + } + } + assigned[name] = struct{}{} + s.columns[i] = name + } + if dupes { + fmt.Fprintln(s.stderr, "Warning: query returned duplicate column names; renamed duplicates to __N. Use AS aliases for stable names.") + } + + if _, err := io.WriteString(s.out, "[\n"); err != nil { + return err + } + s.hasOpenedArray = true + return nil +} + +func (s *jsonSink) Row(values []any) error { + if s.rowsWritten > 0 { + if _, err := io.WriteString(s.out, ",\n"); err != nil { + return err + } + } + + // Emit keys in column order. json.Marshal on a map sorts keys + // alphabetically; SELECT order is what consumers expect, so we write + // `{`, walk columns, encode key:value pairs ourselves, then `}`. + if _, err := io.WriteString(s.out, "{"); err != nil { + return err + } + for i, name := range s.columns { + if i > 0 { + if _, err := io.WriteString(s.out, ","); err != nil { + return err + } + } + key, err := marshalJSON(name) + if err != nil { + return fmt.Errorf("encode column name %q: %w", name, err) + } + if _, err := s.out.Write(key); err != nil { + return err + } + if _, err := io.WriteString(s.out, ":"); err != nil { + return err + } + val, err := marshalJSON(jsonValueWithOID(values[i], s.oids[i])) + if err != nil { + return fmt.Errorf("encode value for %q: %w", name, err) + } + if _, err := s.out.Write(val); err != nil { + return err + } + } + if _, err := io.WriteString(s.out, "}"); err != nil { + return err + } + s.rowsWritten++ + return nil +} + +// marshalJSON encodes v with HTML escaping disabled (so jsonb values like +// {"url":""} round-trip without `<` rewrites). encoding/json's Encoder +// is the only path that exposes SetEscapeHTML, so we route through it and +// strip the trailing newline it always appends. +func marshalJSON(v any) ([]byte, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.SetEscapeHTML(false) + if err := enc.Encode(v); err != nil { + return nil, err + } + return bytes.TrimRight(buf.Bytes(), "\n"), nil +} + +func (s *jsonSink) End(commandTag string) error { + if s.hasOpenedArray { + if s.rowsWritten == 0 { + // Empty result: collapse to "[]\n" rather than "[\n\n]\n". + _, err := io.WriteString(s.out, "]\n") + return err + } + _, err := io.WriteString(s.out, "\n]\n") + return err + } + // Command-only path: emit a single ordered object. + if _, err := io.WriteString(s.out, `{"command":`); err != nil { + return err + } + verbBytes, err := marshalJSON(commandTagVerb(commandTag)) + if err != nil { + return fmt.Errorf("encode command tag verb: %w", err) + } + if _, err := s.out.Write(verbBytes); err != nil { + return err + } + if rows, ok := commandTagRowCount(commandTag); ok { + if _, err := fmt.Fprintf(s.out, `,"rows_affected":%d`, rows); err != nil { + return err + } + } + _, err = io.WriteString(s.out, "}\n") + return err +} + +// OnError closes the array cleanly so stdout remains parseable JSON. The +// caller still propagates the original error, which the command writes to +// stderr. +func (s *jsonSink) OnError(err error) { + if !s.hasOpenedArray { + return + } + // Best-effort; if this Write fails the stream is already corrupted + // and there is nothing more we can do. + if s.rowsWritten == 0 { + _, _ = io.WriteString(s.out, "]\n") + return + } + _, _ = io.WriteString(s.out, "\n]\n") +} + +// commandTagVerb extracts the leading verb from a Postgres command tag (e.g. +// "INSERT 0 5" -> "INSERT"). Returns the input unchanged if there is no space. +func commandTagVerb(tag string) string { + for i, r := range tag { + if r == ' ' { + return tag[:i] + } + } + return tag +} + +// commandTagRowCount extracts the trailing row count from a Postgres command +// tag. INSERT tags have the shape "INSERT "; UPDATE/DELETE/SELECT +// have "VERB ". Returns ok=false for tags without a trailing integer +// (e.g. "CREATE DATABASE", "SET"). +func commandTagRowCount(tag string) (int64, bool) { + for i := len(tag) - 1; i >= 0; i-- { + if tag[i] == ' ' { + n, err := strconv.ParseInt(tag[i+1:], 10, 64) + if err != nil { + return 0, false + } + return n, true + } + } + return 0, false +} diff --git a/experimental/postgres/cmd/render_json_test.go b/experimental/postgres/cmd/render_json_test.go new file mode 100644 index 00000000000..9cf386cb14d --- /dev/null +++ b/experimental/postgres/cmd/render_json_test.go @@ -0,0 +1,161 @@ +package postgrescmd + +import ( + "bytes" + "strings" + "testing" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func fieldsWithOIDs(names []string, oids []uint32) []pgconn.FieldDescription { + out := make([]pgconn.FieldDescription, len(names)) + for i, n := range names { + out[i] = pgconn.FieldDescription{Name: n, DataTypeOID: oids[i]} + } + return out +} + +func TestJSONSink_TwoRows(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"id", "name"}, []uint32{pgtype.Int8OID, pgtype.TextOID}))) + require.NoError(t, s.Row([]any{int64(1), "alice"})) + require.NoError(t, s.Row([]any{int64(2), "bob"})) + require.NoError(t, s.End("SELECT 2")) + + assert.Equal(t, + "[\n"+ + `{"id":1,"name":"alice"}`+",\n"+ + `{"id":2,"name":"bob"}`+ + "\n]\n", + stdout.String(), + ) + assert.Empty(t, stderr.String()) +} + +func TestJSONSink_EmptyRowsProducing(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"id"}, []uint32{pgtype.Int8OID}))) + require.NoError(t, s.End("SELECT 0")) + assert.Equal(t, "[\n]\n", stdout.String()) +} + +func TestJSONSink_KeysInColumnOrder(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"b", "a"}, []uint32{pgtype.Int8OID, pgtype.Int8OID}))) + require.NoError(t, s.Row([]any{int64(1), int64(2)})) + require.NoError(t, s.End("SELECT 1")) + assert.Equal(t, "[\n"+`{"b":1,"a":2}`+"\n]\n", stdout.String()) +} + +func TestJSONSink_CommandOnly_WithRowCount(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(nil)) + require.NoError(t, s.End("INSERT 0 5")) + // Byte-equal: pins the field order so adding a future field (e.g. last_oid) + // must update the test rather than silently drift. + assert.Equal(t, `{"command":"INSERT","rows_affected":5}`+"\n", stdout.String()) +} + +func TestJSONSink_CommandOnly_NoRowCount(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(nil)) + require.NoError(t, s.End("CREATE DATABASE")) + assert.Equal(t, `{"command":"CREATE"}`+"\n", stdout.String()) +} + +func TestJSONSink_DuplicateColumns(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"id", "id", "id"}, []uint32{pgtype.Int8OID, pgtype.Int8OID, pgtype.Int8OID}))) + require.NoError(t, s.Row([]any{int64(1), int64(2), int64(3)})) + require.NoError(t, s.End("SELECT 1")) + + assert.Contains(t, stdout.String(), `"id":1`) + assert.Contains(t, stdout.String(), `"id__2":2`) + assert.Contains(t, stdout.String(), `"id__3":3`) + assert.Contains(t, stderr.String(), "duplicate column names") +} + +func TestJSONSink_OnError_AfterRows(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"id"}, []uint32{pgtype.Int8OID}))) + require.NoError(t, s.Row([]any{int64(1)})) + s.OnError(assert.AnError) + assert.Equal(t, "[\n"+`{"id":1}`+"\n]\n", stdout.String()) +} + +func TestJSONSink_OnError_AfterBeginNoRows(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs([]string{"id"}, []uint32{pgtype.Int8OID}))) + s.OnError(assert.AnError) + assert.Equal(t, "[\n]\n", stdout.String()) +} + +func TestJSONSink_OnError_BeforeBegin(t *testing.T) { + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + s.OnError(assert.AnError) + assert.Empty(t, stdout.String()) +} + +func TestCommandTagParse(t *testing.T) { + tests := []struct { + tag string + verb string + rows int64 + hasCount bool + }{ + {"INSERT 0 5", "INSERT", 5, true}, + {"UPDATE 3", "UPDATE", 3, true}, + {"DELETE 0", "DELETE", 0, true}, + {"SELECT 100", "SELECT", 100, true}, + {"MERGE 5", "MERGE", 5, true}, + {"COPY 1000", "COPY", 1000, true}, + {"FETCH 7", "FETCH", 7, true}, + {"MOVE 3", "MOVE", 3, true}, + {"CREATE DATABASE", "CREATE", 0, false}, + {"SET", "SET", 0, false}, + } + for _, tc := range tests { + assert.Equal(t, tc.verb, commandTagVerb(tc.tag), "verb for %q", tc.tag) + count, ok := commandTagRowCount(tc.tag) + assert.Equal(t, tc.hasCount, ok, "hasCount for %q", tc.tag) + if tc.hasCount { + assert.Equal(t, tc.rows, count, "rows for %q", tc.tag) + } + } +} + +func TestJSONSink_DuplicateColumns_DoesNotCollideWithExistingSuffix(t *testing.T) { + // Source columns ["id", "id__2", "id"]: the second `id` would naively + // rename to id__2, colliding with the existing id__2 from the source. + // Verify the dedup logic bumps the suffix until unique. + var stdout, stderr bytes.Buffer + s := newJSONSink(&stdout, &stderr) + require.NoError(t, s.Begin(fieldsWithOIDs( + []string{"id", "id__2", "id"}, + []uint32{pgtype.Int8OID, pgtype.Int8OID, pgtype.Int8OID}, + ))) + require.NoError(t, s.Row([]any{int64(1), int64(2), int64(3)})) + require.NoError(t, s.End("SELECT 1")) + + // All three keys present with no duplicates. + out := stdout.String() + assert.Contains(t, out, `"id":1`) + assert.Contains(t, out, `"id__2":2`) + assert.Contains(t, out, `"id__3":3`) + // And NOT two id__2 keys. + assert.Equal(t, 1, strings.Count(out, `"id__2"`)) +} diff --git a/experimental/postgres/cmd/render_multi.go b/experimental/postgres/cmd/render_multi.go new file mode 100644 index 00000000000..2a2d7938163 --- /dev/null +++ b/experimental/postgres/cmd/render_multi.go @@ -0,0 +1,210 @@ +package postgrescmd + +import ( + "bytes" + "fmt" + "io" +) + +// renderTextMulti renders a sequence of unit results as plain text. Each +// per-unit block follows the single-input layout (table for rows-producing, +// command tag for command-only); successive blocks are separated by a blank +// line, mirroring `psql -c "...; ..."` shape. +// +// errIndex/errResult identifies the unit that errored (-1 if none); we still +// render any successful prefix. The error itself is surfaced by the caller +// via cobra's default error rendering. +func renderTextMulti(out io.Writer, results []*unitResult) error { + for i, r := range results { + if i > 0 { + if _, err := io.WriteString(out, "\n"); err != nil { + return err + } + } + if err := renderTextResult(out, r); err != nil { + return err + } + } + return nil +} + +// renderTextResult renders a single buffered unitResult in the same shape as +// textSink would for a streamed result. +func renderTextResult(out io.Writer, r *unitResult) error { + if !r.IsRowsProducing() { + _, err := fmt.Fprintln(out, r.CommandTag) + return err + } + + // Reuse textSink for the table layout so single-input and multi-input + // share the same alignment and footer logic. + sink := newTextSink(out) + if err := sink.Begin(r.Fields); err != nil { + return err + } + for _, row := range r.Rows { + if err := sink.Row(row); err != nil { + return err + } + } + return sink.End(r.CommandTag) +} + +// renderJSONMulti emits the wrapped multi-input JSON shape: a top-level +// array of result objects, one per input unit. Per-unit objects are buffered +// to completion before write; the outer array uses separator-before-element +// streaming. CSV multi-input is rejected pre-flight, so this function is +// only used for json. +// +// Every per-unit object shares the same canonical key order: +// +// {"source", "sql", "kind", "elapsed_ms", payload...} +// +// where payload depends on kind: +// +// "rows": {..., "rows": [...]} +// "command": {..., "command": "...", "rows_affected": N} +// "error": {..., "error": {"message": "..."}} +// +// elapsed_ms is present on errors too: it captures how long the failing +// statement ran before the error fired. +func renderJSONMulti(out, stderr io.Writer, results []*unitResult, errored *unitResult, errMessage string) error { + if _, err := io.WriteString(out, "[\n"); err != nil { + return err + } + + for i, r := range results { + if i > 0 { + if _, err := io.WriteString(out, ",\n"); err != nil { + return err + } + } + var unitBuf bytes.Buffer + if err := renderJSONUnit(&unitBuf, stderr, r); err != nil { + return err + } + if _, err := out.Write(unitBuf.Bytes()); err != nil { + return err + } + } + + if errored != nil { + if len(results) > 0 { + if _, err := io.WriteString(out, ",\n"); err != nil { + return err + } + } + obj := jsonErrorObject(errored, errMessage) + if _, err := out.Write(obj); err != nil { + return err + } + } + + _, err := io.WriteString(out, "\n]\n") + return err +} + +// renderJSONUnit writes one buffered result object to buf, using the +// existing single-input json rendering for the rows array so the value +// mapping stays consistent across single- and multi-input shapes. +func renderJSONUnit(buf *bytes.Buffer, stderr io.Writer, r *unitResult) error { + if err := writeJSONUnitHeader(buf, r); err != nil { + return err + } + + if !r.IsRowsProducing() { + buf.WriteString(`,"kind":"command"`) + fmt.Fprintf(buf, `,"elapsed_ms":%d`, r.Elapsed.Milliseconds()) + verbBytes, err := marshalJSON(commandTagVerb(r.CommandTag)) + if err != nil { + return err + } + buf.WriteString(`,"command":`) + buf.Write(verbBytes) + if rows, ok := commandTagRowCount(r.CommandTag); ok { + fmt.Fprintf(buf, `,"rows_affected":%d`, rows) + } + buf.WriteString(`}`) + return nil + } + + // Rows-producing unit. Reuse jsonSink for the rows array body so the + // per-row encoding (column order, type mapping) stays in one place. + buf.WriteString(`,"kind":"rows"`) + fmt.Fprintf(buf, `,"elapsed_ms":%d,"rows":`, r.Elapsed.Milliseconds()) + + rowsBuf := &bytes.Buffer{} + sink := newJSONSink(rowsBuf, stderr) + if err := sink.Begin(r.Fields); err != nil { + return err + } + for _, row := range r.Rows { + if err := sink.Row(row); err != nil { + return err + } + } + if err := sink.End(""); err != nil { + return err + } + rowsTrimmed := bytes.TrimRight(rowsBuf.Bytes(), "\n") + buf.Write(rowsTrimmed) + buf.WriteString(`}`) + return nil +} + +// writeJSONUnitHeader writes the canonical {source, sql, ...} prefix used +// by every per-unit object. The closing brace and the kind-specific payload +// are appended by the caller. +func writeJSONUnitHeader(buf *bytes.Buffer, r *unitResult) error { + sourceBytes, err := marshalJSON(r.Source) + if err != nil { + return err + } + sqlBytes, err := marshalJSON(r.SQL) + if err != nil { + return err + } + buf.WriteString(`{"source":`) + buf.Write(sourceBytes) + buf.WriteString(`,"sql":`) + buf.Write(sqlBytes) + return nil +} + +// jsonErrorObject builds the per-unit error envelope used in the multi-input +// JSON shape. The buffered unitResult provides source, SQL, and the elapsed +// time captured by runUnitBuffered's error path. message is the +// already-formatted error wording (includes SQLSTATE / hint / detail for +// PgErrors). +// +// marshalJSON of a string never errors (encoding/json replaces invalid UTF-8 +// with U+FFFD), so the inner errors are unreachable and we treat them as +// programming errors via panic. +func jsonErrorObject(r *unitResult, message string) []byte { + var buf bytes.Buffer + mustWriteJSONHeader(&buf, r) + buf.WriteString(`,"kind":"error"`) + fmt.Fprintf(&buf, `,"elapsed_ms":%d`, r.Elapsed.Milliseconds()) + buf.WriteString(`,"error":{"message":`) + buf.Write(mustMarshalJSON(message)) + buf.WriteString(`}}`) + return buf.Bytes() +} + +// mustWriteJSONHeader is writeJSONUnitHeader with a panic instead of an +// error return. The only failure mode is an unreachable encoding/json error. +func mustWriteJSONHeader(buf *bytes.Buffer, r *unitResult) { + if err := writeJSONUnitHeader(buf, r); err != nil { + panic(fmt.Errorf("encoding json header: %w", err)) + } +} + +// mustMarshalJSON is marshalJSON with a panic instead of an error return, +// for the same reason. +func mustMarshalJSON(v any) []byte { + b, err := marshalJSON(v) + if err != nil { + panic(fmt.Errorf("encoding json value: %w", err)) + } + return b +} diff --git a/experimental/postgres/cmd/render_multi_test.go b/experimental/postgres/cmd/render_multi_test.go new file mode 100644 index 00000000000..b4e96f73eb8 --- /dev/null +++ b/experimental/postgres/cmd/render_multi_test.go @@ -0,0 +1,105 @@ +package postgrescmd + +import ( + "bytes" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenderTextMulti_TwoResults(t *testing.T) { + r1 := &unitResult{ + Source: "argv[1]", + SQL: "INSERT INTO t VALUES (1)", + CommandTag: "INSERT 0 1", + Elapsed: 5 * time.Millisecond, + } + r2 := &unitResult{ + Source: "argv[2]", + SQL: "SELECT id FROM t", + Fields: fieldsWithOIDs([]string{"id"}, []uint32{pgtype.Int8OID}), + Rows: [][]any{{int64(1)}}, + CommandTag: "SELECT 1", + Elapsed: 3 * time.Millisecond, + } + + var buf bytes.Buffer + require.NoError(t, renderTextMulti(&buf, []*unitResult{r1, r2})) + out := buf.String() + assert.Contains(t, out, "INSERT 0 1\n") + assert.Contains(t, out, "id") + assert.Contains(t, out, "(1 row)") + // Blank-line separator between blocks. + assert.Contains(t, out, "INSERT 0 1\n\n") +} + +func TestRenderJSONMulti_TwoResults(t *testing.T) { + r1 := &unitResult{ + Source: "argv[1]", + SQL: "INSERT INTO t VALUES (1)", + CommandTag: "INSERT 0 1", + Elapsed: 5 * time.Millisecond, + } + r2 := &unitResult{ + Source: "argv[2]", + SQL: "SELECT id FROM t", + Fields: fieldsWithOIDs([]string{"id"}, []uint32{pgtype.Int8OID}), + Rows: [][]any{{int64(1)}, {int64(2)}}, + CommandTag: "SELECT 2", + Elapsed: 3 * time.Millisecond, + } + + var stdout, stderr bytes.Buffer + require.NoError(t, renderJSONMulti(&stdout, &stderr, []*unitResult{r1, r2}, nil, "")) + + out := stdout.String() + // Canonical key order: source, sql, kind, elapsed_ms, payload. + assert.Contains(t, out, `"source":"argv[1]","sql":"INSERT INTO t VALUES (1)","kind":"command","elapsed_ms":5,"command":"INSERT","rows_affected":1`) + assert.Contains(t, out, `"source":"argv[2]","sql":"SELECT id FROM t","kind":"rows","elapsed_ms":3,"rows":`) + // Outer array framing. + assert.Greater(t, len(out), 4) + assert.Equal(t, byte('['), out[0]) + assert.Equal(t, byte('\n'), out[len(out)-1]) +} + +func TestRenderJSONMulti_WithErrorAtEnd(t *testing.T) { + r1 := &unitResult{ + Source: "argv[1]", + SQL: "SELECT 1", + Fields: fieldsWithOIDs([]string{"?column?"}, []uint32{pgtype.Int8OID}), + Rows: [][]any{{int64(1)}}, + CommandTag: "SELECT 1", + Elapsed: 1 * time.Millisecond, + } + errored := &unitResult{ + Source: "argv[2]", + SQL: "BROKEN SQL", + Elapsed: 2 * time.Millisecond, + } + + var stdout, stderr bytes.Buffer + require.NoError(t, renderJSONMulti(&stdout, &stderr, []*unitResult{r1}, errored, "ERROR: syntax error (SQLSTATE 42601)")) + + out := stdout.String() + assert.Contains(t, out, `"kind":"rows"`) + // Error envelope: same key order, includes elapsed_ms + source + sql. + assert.Contains(t, out, `"source":"argv[2]","sql":"BROKEN SQL","kind":"error","elapsed_ms":2,"error":{"message":"ERROR: syntax error (SQLSTATE 42601)"}`) +} + +func TestRenderJSONMulti_FirstUnitFails(t *testing.T) { + errored := &unitResult{ + Source: "argv[1]", + SQL: "BROKEN", + Elapsed: 7 * time.Millisecond, + } + var stdout, stderr bytes.Buffer + require.NoError(t, renderJSONMulti(&stdout, &stderr, nil, errored, "ERROR: bad")) + + out := stdout.String() + // No leading separator before the single error envelope. + assert.Contains(t, out, "[\n"+`{"source":"argv[1]","sql":"BROKEN","kind":"error","elapsed_ms":7,"error":{"message":"ERROR: bad"}}`) + assert.Contains(t, out, "\n]\n") +} diff --git a/experimental/postgres/cmd/render_test.go b/experimental/postgres/cmd/render_test.go new file mode 100644 index 00000000000..bdd2bddd4f6 --- /dev/null +++ b/experimental/postgres/cmd/render_test.go @@ -0,0 +1,98 @@ +package postgrescmd + +import ( + "bytes" + "testing" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// fields is a small helper to build []pgconn.FieldDescription with just names +// (no OIDs), so renderer tests don't need to know about Postgres OIDs. +func fields(names ...string) []pgconn.FieldDescription { + out := make([]pgconn.FieldDescription, len(names)) + for i, n := range names { + out[i] = pgconn.FieldDescription{Name: n} + } + return out +} + +func TestTextSink_RowsProducing(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + + require.NoError(t, s.Begin(fields("id", "name"))) + require.NoError(t, s.Row([]any{int64(1), "alice"})) + require.NoError(t, s.Row([]any{int64(2), "bob"})) + require.NoError(t, s.End("SELECT 2")) + + assert.Equal(t, + "id name\n"+ + "--- ----\n"+ + "1 alice\n"+ + "2 bob\n"+ + "(2 rows)\n", + buf.String(), + ) +} + +func TestTextSink_SingleRow(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(fields("id"))) + require.NoError(t, s.Row([]any{int64(42)})) + require.NoError(t, s.End("SELECT 1")) + assert.Contains(t, buf.String(), "(1 row)\n") +} + +func TestTextSink_Empty(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(fields("id", "name"))) + require.NoError(t, s.End("SELECT 0")) + assert.Contains(t, buf.String(), "(0 rows)\n") +} + +func TestTextSink_CommandOnly(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(nil)) + require.NoError(t, s.End("INSERT 0 5")) + assert.Equal(t, "INSERT 0 5\n", buf.String()) +} + +func TestTextSink_NULLRendersAsNULL(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(fields("id"))) + require.NoError(t, s.Row([]any{nil})) + require.NoError(t, s.End("SELECT 1")) + assert.Contains(t, buf.String(), "NULL") +} + +func TestTextSink_OnError_NoOp(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(fields("id"))) + require.NoError(t, s.Row([]any{int64(1)})) + s.OnError(assert.AnError) + // Text sink has no open structure to close. OnError must not panic and + // must not emit a partial table; the partial result lives in s.rows but + // is never flushed. + assert.Empty(t, buf.String()) +} + +func TestTextSink_EscapesTabAndNewlineInCells(t *testing.T) { + var buf bytes.Buffer + s := newTextSink(&buf) + require.NoError(t, s.Begin(fields("note"))) + require.NoError(t, s.Row([]any{"a\tb\nc\rd"})) + require.NoError(t, s.End("SELECT 1")) + // The escape replaces tabs/newlines/CR with their backslash-letter forms + // so the tabwriter doesn't treat them as column or row boundaries. + assert.Contains(t, buf.String(), `a\tb\nc\rd`) + assert.NotContains(t, buf.String(), "a\tb") + assert.NotContains(t, buf.String(), "c\rd") +} diff --git a/experimental/postgres/cmd/result.go b/experimental/postgres/cmd/result.go new file mode 100644 index 00000000000..40267260af1 --- /dev/null +++ b/experimental/postgres/cmd/result.go @@ -0,0 +1,76 @@ +package postgrescmd + +import ( + "context" + "slices" + "time" + + "github.com/databricks/cli/experimental/libs/sqlcli" + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +// unitResult is the buffered result of running one input unit. The +// multi-input renderers (text, json) need rows buffered before they can +// emit a per-unit block; for the single-input path we still stream +// directly through a rowSink and never produce a unitResult. +type unitResult struct { + Source string + SQL string + Fields []pgconn.FieldDescription + Rows [][]any + CommandTag string + Elapsed time.Duration +} + +// IsRowsProducing returns whether the unit returned a row description. +func (r *unitResult) IsRowsProducing() bool { + return len(r.Fields) > 0 +} + +// runUnitBuffered runs sql and collects every row into memory. Implemented +// as a thin wrapper that hands a bufferSink to executeOne, so error wrapping +// and the rowSink contract stay in one place rather than parallel-evolving +// across two query loops. +func runUnitBuffered(ctx context.Context, conn *pgx.Conn, unit sqlcli.Input) (*unitResult, error) { + start := time.Now() + r := &unitResult{Source: unit.Source, SQL: unit.SQL} + sink := &bufferSink{result: r} + if err := executeOne(ctx, conn, unit.SQL, sink); err != nil { + // Capture timing on the error path too so the JSON error envelope + // can surface "this query ran for X seconds before failing". + r.Elapsed = time.Since(start) + return r, err + } + r.Elapsed = time.Since(start) + return r, nil +} + +// bufferSink is a rowSink that copies fields, rows, and the command tag into +// a unitResult instead of writing anywhere. Used by the multi-input path so +// successive units can be rendered together once they're all available. +type bufferSink struct { + result *unitResult +} + +func (s *bufferSink) Begin(fields []pgconn.FieldDescription) error { + // pgx reuses the FieldDescription backing array across queries on the same + // connection (pgConn.fieldDescriptions is a fixed-size buffer that's + // re-sliced per statement). Clone here so a buffered unit holds onto its + // own column descriptions; otherwise the multi-input renderers see every + // unit's Fields aliased to the last query's row description. + s.result.Fields = slices.Clone(fields) + return nil +} + +func (s *bufferSink) Row(values []any) error { + s.result.Rows = append(s.result.Rows, values) + return nil +} + +func (s *bufferSink) End(commandTag string) error { + s.result.CommandTag = commandTag + return nil +} + +func (s *bufferSink) OnError(err error) {} diff --git a/experimental/postgres/cmd/result_test.go b/experimental/postgres/cmd/result_test.go new file mode 100644 index 00000000000..22872d3bbd4 --- /dev/null +++ b/experimental/postgres/cmd/result_test.go @@ -0,0 +1,41 @@ +package postgrescmd + +import ( + "testing" + + "github.com/jackc/pgx/v5/pgconn" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBufferSink_BeginClonesFields(t *testing.T) { + r := &unitResult{} + s := &bufferSink{result: r} + + // pgx hands Begin a slice whose backing array gets reused for the next + // query on the same connection. Mutating the caller's slice after Begin + // must not change what the buffered result holds. + fields := []pgconn.FieldDescription{ + {Name: "first_col", DataTypeOID: 23}, + } + require.NoError(t, s.Begin(fields)) + + fields[0] = pgconn.FieldDescription{Name: "second_col", DataTypeOID: 25} + + require.Len(t, r.Fields, 1) + assert.Equal(t, "first_col", r.Fields[0].Name) + assert.Equal(t, uint32(23), r.Fields[0].DataTypeOID) +} + +func TestBufferSink_RowAndEnd(t *testing.T) { + r := &unitResult{} + s := &bufferSink{result: r} + + require.NoError(t, s.Begin([]pgconn.FieldDescription{{Name: "a"}})) + require.NoError(t, s.Row([]any{int64(1)})) + require.NoError(t, s.Row([]any{int64(2)})) + require.NoError(t, s.End("SELECT 2")) + + assert.Equal(t, [][]any{{int64(1)}, {int64(2)}}, r.Rows) + assert.Equal(t, "SELECT 2", r.CommandTag) +} diff --git a/experimental/postgres/cmd/signals.go b/experimental/postgres/cmd/signals.go new file mode 100644 index 00000000000..5e4c29346f9 --- /dev/null +++ b/experimental/postgres/cmd/signals.go @@ -0,0 +1,40 @@ +package postgrescmd + +import ( + "context" + "os" + "os/signal" + "syscall" +) + +// watchInterruptSignals installs handlers for SIGINT and SIGTERM that call +// cancel when the user hits Ctrl+C or the process gets a SIGTERM. +// +// Returns a stop-and-cancel function that uninstalls the handlers (signal.Stop +// prevents future OS deliveries) and cancels the parent context so the +// goroutine wakes promptly. The caller must defer it. The channel is +// 1-buffered and GC'd on return; no explicit drain is needed. +// +// On Windows, Go maps Ctrl+C to os.Interrupt via the console-control-handler, +// so the same code path covers Windows. +func watchInterruptSignals(ctx context.Context, cancel context.CancelFunc) func() { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + done := make(chan struct{}) + go func() { + select { + case <-sigCh: + cancel() + case <-ctx.Done(): + } + close(done) + }() + + return func() { + signal.Stop(sigCh) + // Wake the goroutine in case neither sigCh nor ctx.Done has fired. + cancel() + <-done + } +} diff --git a/experimental/postgres/cmd/targeting.go b/experimental/postgres/cmd/targeting.go new file mode 100644 index 00000000000..cf1290ad578 --- /dev/null +++ b/experimental/postgres/cmd/targeting.go @@ -0,0 +1,213 @@ +package postgrescmd + +import ( + "context" + "errors" + "fmt" + + "github.com/databricks/cli/experimental/postgres/cmd/internal/target" + "github.com/databricks/cli/libs/cmdctx" + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/database" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/databricks-sdk-go/service/postgres" +) + +// resolvedTarget carries everything the query command needs to dial Postgres: +// the endpoint host (resolved through the SDK) and a short-lived OAuth token. +// `kind` records whether we resolved an autoscaling endpoint or a provisioned +// instance, so the caller can pick the right default database name and emit +// kind-appropriate logging. +type resolvedTarget struct { + Kind targetKind + Host string + Username string + Token string + // Display strings used only for human-readable logs / errors. + DisplayName string +} + +type targetKind int + +const ( + kindAutoscaling targetKind = iota + kindProvisioned +) + +// targetingFlags is the user-supplied targeting input. Exactly one of: +// - target (full path or instance name) +// - project (with optional branch and endpoint) +// +// must be set. Validated by validateTargeting before any SDK call. +type targetingFlags struct { + target string + project string + branch string + endpoint string +} + +func (f targetingFlags) hasGranular() bool { + return f.project != "" || f.branch != "" || f.endpoint != "" +} + +// validateTargeting enforces "exactly one targeting form" before any SDK call. +// Returns a typed error so the JSON envelope renderer (added in a later PR) +// can surface a structured error. +// +// We require --branch when --endpoint is set: this command is non-interactive +// and scriptable, and the alternative (auto-select-then-look-up-endpoint) +// produces confusing errors when the resolved branch does not contain the +// requested endpoint. Asking the user to be explicit is friendlier. +func validateTargeting(f targetingFlags) error { + switch { + case f.target == "" && !f.hasGranular(): + return errors.New("must specify --target or --project") + case f.target != "" && f.hasGranular(): + return errors.New("--target is mutually exclusive with --project, --branch, --endpoint") + case f.target == "" && f.project == "" && (f.branch != "" || f.endpoint != ""): + return errors.New("--project is required when using --branch or --endpoint") + case f.endpoint != "" && f.branch == "": + return errors.New("--branch is required when using --endpoint") + } + return nil +} + +// resolveTarget translates the validated flags into a resolvedTarget. +// +// --target accepts either an autoscaling resource path (starts with "projects/") +// or a provisioned instance name (everything else). Granular flags +// (--project, --branch, --endpoint) target autoscaling only. +func resolveTarget(ctx context.Context, f targetingFlags) (*resolvedTarget, error) { + w := cmdctx.WorkspaceClient(ctx) + + switch { + case f.target != "" && target.IsAutoscalingPath(f.target): + spec, err := target.ParseAutoscalingPath(f.target) + if err != nil { + return nil, err + } + return resolveAutoscaling(ctx, w, spec) + + case f.target != "": + return resolveProvisioned(ctx, w, f.target) + + default: + spec := target.AutoscalingSpec{ + ProjectID: f.project, + BranchID: f.branch, + EndpointID: f.endpoint, + } + return resolveAutoscaling(ctx, w, spec) + } +} + +// resolveProvisioned looks up a provisioned instance and issues a token. The +// instance must be in the AVAILABLE state; transitional states return an +// error pointing the user at the lifecycle they are waiting on. +func resolveProvisioned(ctx context.Context, w *databricks.WorkspaceClient, instanceName string) (*resolvedTarget, error) { + instance, err := target.GetProvisioned(ctx, w, instanceName) + if err != nil { + return nil, err + } + + if instance.State != database.DatabaseInstanceStateAvailable { + return nil, fmt.Errorf("database instance %q is not ready for accepting connections (state: %s)", instance.Name, instance.State) + } + + user, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) + if err != nil { + return nil, fmt.Errorf("failed to get current user: %w", err) + } + + token, err := target.ProvisionedCredential(ctx, w, instance.Name) + if err != nil { + return nil, err + } + + return &resolvedTarget{ + Kind: kindProvisioned, + Host: instance.ReadWriteDns, + Username: user.UserName, + Token: token, + DisplayName: instance.Name, + }, nil +} + +// resolveAutoscaling expands a partial spec into a fully-resolved endpoint and +// issues a short-lived OAuth token. Missing branch/endpoint IDs are +// auto-selected when exactly one candidate exists; ambiguity propagates as an +// AmbiguousError with the list of choices. +func resolveAutoscaling(ctx context.Context, w *databricks.WorkspaceClient, spec target.AutoscalingSpec) (*resolvedTarget, error) { + if spec.ProjectID == "" { + var err error + spec.ProjectID, err = target.AutoSelectProject(ctx, w) + if err != nil { + return nil, err + } + } + + project, err := target.GetProject(ctx, w, spec.ProjectID) + if err != nil { + return nil, fmt.Errorf("failed to get project: %w", err) + } + + if spec.BranchID == "" { + spec.BranchID, err = target.AutoSelectBranch(ctx, w, project.Name) + if err != nil { + return nil, err + } + } + + if spec.EndpointID == "" { + branchName := project.Name + "/branches/" + spec.BranchID + spec.EndpointID, err = target.AutoSelectEndpoint(ctx, w, branchName) + if err != nil { + return nil, err + } + } + + endpoint, err := target.GetEndpoint(ctx, w, spec.ProjectID, spec.BranchID, spec.EndpointID) + if err != nil { + return nil, fmt.Errorf("failed to get endpoint: %w", err) + } + + if err := checkEndpointReady(endpoint); err != nil { + return nil, err + } + + user, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) + if err != nil { + return nil, fmt.Errorf("failed to get current user: %w", err) + } + + token, err := target.AutoscalingCredential(ctx, w, endpoint.Name) + if err != nil { + return nil, err + } + + return &resolvedTarget{ + Kind: kindAutoscaling, + Host: endpoint.Status.Hosts.Host, + Username: user.UserName, + Token: token, + DisplayName: endpoint.Name, + }, nil +} + +// checkEndpointReady returns an error if the endpoint is not in a connectable +// state. Idle endpoints are considered connectable (Lakebase wakes them on +// dial); the connect retry loop handles the wake-up window. +func checkEndpointReady(endpoint *postgres.Endpoint) error { + if endpoint.Status == nil { + return errors.New("endpoint status is not available") + } + if endpoint.Status.Hosts == nil || endpoint.Status.Hosts.Host == "" { + return errors.New("endpoint host information is not available") + } + switch endpoint.Status.CurrentState { + case postgres.EndpointStatusStateActive, postgres.EndpointStatusStateIdle: + return nil + default: + return fmt.Errorf("endpoint is not ready for accepting connections (state: %s)", endpoint.Status.CurrentState) + } +} diff --git a/experimental/postgres/cmd/targeting_test.go b/experimental/postgres/cmd/targeting_test.go new file mode 100644 index 00000000000..62f43d22496 --- /dev/null +++ b/experimental/postgres/cmd/targeting_test.go @@ -0,0 +1,89 @@ +package postgrescmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateTargeting(t *testing.T) { + tests := []struct { + name string + flags targetingFlags + wantErr string + }{ + { + name: "neither form", + flags: targetingFlags{}, + wantErr: "must specify --target or --project", + }, + { + name: "only target", + flags: targetingFlags{ + target: "projects/foo", + }, + }, + { + name: "only project", + flags: targetingFlags{ + project: "foo", + }, + }, + { + name: "project and branch", + flags: targetingFlags{ + project: "foo", + branch: "main", + }, + }, + { + name: "project, branch, endpoint", + flags: targetingFlags{ + project: "foo", + branch: "main", + endpoint: "primary", + }, + }, + { + name: "target and project both set", + flags: targetingFlags{ + target: "projects/foo", + project: "foo", + }, + wantErr: "mutually exclusive", + }, + { + name: "branch without project", + flags: targetingFlags{ + branch: "main", + }, + wantErr: "--project is required when using --branch or --endpoint", + }, + { + name: "endpoint without project", + flags: targetingFlags{ + endpoint: "primary", + }, + wantErr: "--project is required when using --branch or --endpoint", + }, + { + name: "endpoint with project but no branch", + flags: targetingFlags{ + project: "foo", + endpoint: "primary", + }, + wantErr: "--branch is required when using --endpoint", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := validateTargeting(tc.flags) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + assert.NoError(t, err) + }) + } +} diff --git a/experimental/postgres/cmd/value.go b/experimental/postgres/cmd/value.go new file mode 100644 index 00000000000..1578c7efecf --- /dev/null +++ b/experimental/postgres/cmd/value.go @@ -0,0 +1,175 @@ +package postgrescmd + +import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "math" + "math/big" + "strconv" + "time" + + "github.com/jackc/pgx/v5/pgtype" +) + +// safeIntegerBound is the largest absolute integer value that can be +// represented exactly in IEEE 754 double precision. Beyond this, encoding an +// int64 as a JSON number silently loses precision in JavaScript-style +// consumers. We render those as JSON strings to preserve the original digits. +const safeIntegerBound = 1<<53 - 1 + +// textValue renders a Go value (as decoded by pgx) to its canonical Postgres +// text representation. Used by --output text and --output csv. +// +// NULL renders as the literal "NULL" so it lines up with the column rather +// than appearing as an empty cell. CSV converts that back to an empty field +// at write time (matches `psql --csv`). +// +// IEEE special floats use Postgres' canonical wording ("NaN" / "Infinity" +// / "-Infinity"), not Go's `fmt.Sprintf("%v")` defaults (which would emit +// "+Inf"/"-Inf"). Finite floats use Go's shortest-round-trip 'g' format, +// which may differ from psql in exponential vs fixed notation around the +// 'g' boundary (e.g. Go prints `1e+10`; psql prints `10000000000`). Full +// psql parity is not worth a custom formatter. +func textValue(v any) string { + if v == nil { + return "NULL" + } + + switch x := v.(type) { + case string: + return x + case []byte: + return `\x` + hex.EncodeToString(x) + case bool: + if x { + return "t" + } + return "f" + case float64: + return floatTextForm(x) + case float32: + return floatTextForm(float64(x)) + case time.Time: + return x.Format(time.RFC3339Nano) + case fmt.Stringer: + return x.String() + } + + return fmt.Sprintf("%v", v) +} + +// floatTextForm formats a float using Postgres' canonical text wording for +// the IEEE specials and Go's shortest-round-trip 'g' format otherwise. +func floatTextForm(f float64) string { + switch { + case math.IsNaN(f): + return "NaN" + case math.IsInf(f, 1): + return "Infinity" + case math.IsInf(f, -1): + return "-Infinity" + } + return strconv.FormatFloat(f, 'g', -1, 64) +} + +// jsonValue renders a Go value (as decoded by pgx) to a JSON-encodable +// representation. Returns a value the standard json.Marshal can handle +// directly and the JSON shape we want; never returns Go values that would +// silently lose information (e.g. NaN, oversized integers). +// +// The mapping intentionally favours machine-friendly output: +// - jsonb / json bytes round-trip as raw JSON (preserves bigint precision +// inside JSON values, e.g. {"id": 9007199254740993}). +// - bytea encodes as base64. +// - timestamps render in RFC3339 with subsecond precision. +// - Postgres NaN / +Inf / -Inf become JSON strings (JSON has no IEEE-special). +// - Integers outside ±2^53 become JSON strings to preserve precision. +// - Numerics, intervals, geometric types, and unknown types fall back to +// the canonical Postgres text representation as a JSON string. +func jsonValue(v any) any { + if v == nil { + return nil + } + + switch x := v.(type) { + case bool: + return x + case string: + return x + case int16, int32: + return x + case int64: + // pgx decodes Postgres int8 to Go int64. Outside the IEEE-754 safe + // integer range we render as a string so JavaScript-style consumers + // don't silently lose precision. + if x > safeIntegerBound || x < -safeIntegerBound { + return strconv.FormatInt(x, 10) + } + return x + case float32: + return jsonFloat(float64(x)) + case float64: + return jsonFloat(x) + case []byte: + // Postgres jsonb / json arrive as []byte holding raw JSON. Anything + // else we'd like to base64-encode. We can't tell them apart from the + // Go type alone; the sink calls jsonValueWithOID for oid-aware + // disambiguation. This bare path is the conservative fallback and + // treats unknown bytes as base64 (lossless and correct for bytea). + return base64.StdEncoding.EncodeToString(x) + case time.Time: + return x.UTC().Format(time.RFC3339Nano) + case *big.Int: + // numeric without scale; preserve as string to keep precision. + return x.String() + case fmt.Stringer: + return x.String() + } + + return fmt.Sprintf("%v", v) +} + +// jsonFloat handles the IEEE-special cases that JSON cannot represent. +// Finite values pass through unchanged. +func jsonFloat(f float64) any { + switch { + case math.IsNaN(f): + return "NaN" + case math.IsInf(f, 1): + return "Infinity" + case math.IsInf(f, -1): + return "-Infinity" + } + return f +} + +// jsonValueWithOID applies oid-aware overrides on top of jsonValue. The two +// places this matters today are JSON/JSONB and bytea: both arrive from pgx as +// []byte but want different JSON shapes (raw JSON passthrough vs base64). +func jsonValueWithOID(v any, oid uint32) any { + if v == nil { + return nil + } + + switch oid { + case pgtype.JSONOID, pgtype.JSONBOID: + // pgx returns json/jsonb as already-decoded Go values when no codec + // is registered; with the default codec, they decode to map/slice/etc. + // In QueryExecModeExec text-mode, pgx returns the raw JSON bytes as + // string (since the wire is text). We accept both shapes. + switch x := v.(type) { + case []byte: + return json.RawMessage(x) + case string: + return json.RawMessage(x) + } + case pgtype.ByteaOID: + if b, ok := v.([]byte); ok { + return base64.StdEncoding.EncodeToString(b) + } + } + + return jsonValue(v) +} diff --git a/experimental/postgres/cmd/value_test.go b/experimental/postgres/cmd/value_test.go new file mode 100644 index 00000000000..d52edae90bc --- /dev/null +++ b/experimental/postgres/cmd/value_test.go @@ -0,0 +1,95 @@ +package postgrescmd + +import ( + "encoding/json" + "math" + "testing" + "time" + + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/assert" +) + +func TestJSONValue_PrimitiveTypes(t *testing.T) { + assert.Equal(t, true, jsonValue(true)) + assert.Equal(t, "hello", jsonValue("hello")) + assert.Equal(t, int64(42), jsonValue(int64(42))) + assert.InDelta(t, 3.14, jsonValue(float64(3.14)), 1e-9) +} + +func TestJSONValue_NULL(t *testing.T) { + assert.Nil(t, jsonValue(nil)) +} + +func TestJSONValue_FloatSpecials(t *testing.T) { + assert.Equal(t, "NaN", jsonValue(math.NaN())) + assert.Equal(t, "Infinity", jsonValue(math.Inf(1))) + assert.Equal(t, "-Infinity", jsonValue(math.Inf(-1))) +} + +func TestJSONValue_LargeIntPreservedAsString(t *testing.T) { + big := int64(1<<53 + 1) + assert.Equal(t, "9007199254740993", jsonValue(big)) + + negBig := -int64(1<<53 + 1) + assert.Equal(t, "-9007199254740993", jsonValue(negBig)) +} + +func TestJSONValue_SafeIntPreservedAsNumber(t *testing.T) { + safe := int64(1<<53 - 1) + assert.Equal(t, safe, jsonValue(safe)) +} + +func TestJSONValue_TimestampToRFC3339(t *testing.T) { + tm := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC) + v := jsonValue(tm) + assert.Equal(t, "2024-01-15T10:30:00Z", v) +} + +func TestJSONValueWithOID_JSONBPassthrough(t *testing.T) { + raw := []byte(`{"id":9007199254740993,"name":"alice"}`) + v := jsonValueWithOID(raw, pgtype.JSONBOID) + + encoded, err := json.Marshal(v) + assert.NoError(t, err) + assert.JSONEq(t, string(raw), string(encoded)) +} + +func TestJSONValueWithOID_ByteaToBase64(t *testing.T) { + v := jsonValueWithOID([]byte{0xde, 0xad, 0xbe, 0xef}, pgtype.ByteaOID) + assert.Equal(t, "3q2+7w==", v) +} + +func TestJSONValueWithOID_FallsBackToJSONValue(t *testing.T) { + assert.Equal(t, int64(42), jsonValueWithOID(int64(42), pgtype.Int8OID)) + assert.Nil(t, jsonValueWithOID(nil, pgtype.TextOID)) +} + +func TestTextValue_NULL(t *testing.T) { + assert.Equal(t, "NULL", textValue(nil)) +} + +func TestTextValue_Bool(t *testing.T) { + assert.Equal(t, "t", textValue(true)) + assert.Equal(t, "f", textValue(false)) +} + +func TestTextValue_BytesAsHex(t *testing.T) { + assert.Equal(t, `\xdeadbeef`, textValue([]byte{0xde, 0xad, 0xbe, 0xef})) +} + +func TestTextValue_Time(t *testing.T) { + tm := time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC) + assert.Equal(t, "2024-01-15T10:30:00Z", textValue(tm)) +} + +func TestTextValue_FloatSpecials(t *testing.T) { + assert.Equal(t, "NaN", textValue(math.NaN())) + assert.Equal(t, "Infinity", textValue(math.Inf(1))) + assert.Equal(t, "-Infinity", textValue(math.Inf(-1))) +} + +func TestTextValue_FiniteFloat(t *testing.T) { + assert.Equal(t, "3.14", textValue(float64(3.14))) + assert.Equal(t, "0", textValue(float64(0))) +} diff --git a/experimental/ssh/README.md b/experimental/ssh/README.md index 66ed288bdda..40b553d4bff 100644 --- a/experimental/ssh/README.md +++ b/experimental/ssh/README.md @@ -19,7 +19,7 @@ databricks ssh connect --cluster=id ## Development ```shell -make build snapshot-release +./task build snapshot-release ./cli ssh connect --cluster= --releases-dir=./dist --debug # or modify ssh config accordingly ``` diff --git a/experimental/ssh/cmd/connect.go b/experimental/ssh/cmd/connect.go index a02098923e3..c9eeea7c8d6 100644 --- a/experimental/ssh/cmd/connect.go +++ b/experimental/ssh/cmd/connect.go @@ -12,13 +12,17 @@ import ( func newConnectCommand() *cobra.Command { cmd := &cobra.Command{ Use: "connect", - Short: "Connect to Databricks compute via SSH", - Long: `Connect to Databricks compute via SSH. + Short: "[EXPERIMENTAL] Connect to your Databricks compute and workspace via SSH", + Long: `[EXPERIMENTAL] Connect to your Databricks compute and workspace via SSH. -This command establishes an SSH connection to Databricks compute, setting up -the SSH server and handling the connection proxy. +This is an experimental feature and is subject to change. -` + disclaimer, +Connect to serverless: + databricks ssh connect + databricks ssh connect --accelerator= # AI Runtime + +Connect to a dedicated cluster: + databricks ssh connect --cluster=`, } var clusterID string @@ -38,17 +42,14 @@ the SSH server and handling the connection proxy. var environmentVersion int var autoApprove bool - cmd.Flags().StringVar(&clusterID, "cluster", "", "Databricks cluster ID (for dedicated clusters)") + cmd.Flags().StringVar(&clusterID, "cluster", "", "Databricks dedicated cluster ID") cmd.Flags().DurationVar(&shutdownDelay, "shutdown-delay", defaultShutdownDelay, "Delay before shutting down the server after the last client disconnects") cmd.Flags().IntVar(&maxClients, "max-clients", defaultMaxClients, "Maximum number of SSH clients") cmd.Flags().BoolVar(&autoStartCluster, "auto-start-cluster", true, "Automatically start the cluster if it is not running") - cmd.Flags().StringVar(&connectionName, "name", "", "Connection name (for serverless compute)") - cmd.Flags().MarkHidden("name") - cmd.Flags().StringVar(&accelerator, "accelerator", "", "GPU accelerator type (GPU_1xA10 or GPU_8xH100)") - cmd.Flags().MarkHidden("accelerator") - cmd.Flags().StringVar(&ide, "ide", "", "Open remote IDE window (vscode or cursor)") - cmd.Flags().MarkHidden("ide") + cmd.Flags().StringVar(&connectionName, "name", "", "Connection name to reuse across sessions (serverless only)") + cmd.Flags().StringVar(&accelerator, "accelerator", "", "Serverless GPU accelerator type (GPU_1xA10 or GPU_8xH100)") + cmd.Flags().StringVar(&ide, "ide", "", "Open a remote IDE window on connect (vscode or cursor)") cmd.Flags().BoolVar(&proxyMode, "proxy", false, "ProxyCommand mode") cmd.Flags().MarkHidden("proxy") @@ -69,8 +70,7 @@ the SSH server and handling the connection proxy. cmd.Flags().BoolVar(&skipSettingsCheck, "skip-settings-check", false, "Skip checking and updating IDE settings") cmd.Flags().MarkHidden("skip-settings-check") - cmd.Flags().IntVar(&environmentVersion, "environment-version", defaultEnvironmentVersion, "Environment version for serverless compute") - cmd.Flags().MarkHidden("environment-version") + cmd.Flags().IntVar(&environmentVersion, "environment-version", defaultEnvironmentVersion, "Environment version for AI Runtime") cmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "Skip confirmation prompts, installing IDE extensions and applying IDE settings without asking") diff --git a/experimental/ssh/cmd/server.go b/experimental/ssh/cmd/server.go index d73ad2d5c4e..3591a6eb97c 100644 --- a/experimental/ssh/cmd/server.go +++ b/experimental/ssh/cmd/server.go @@ -12,13 +12,13 @@ import ( func newServerCommand() *cobra.Command { cmd := &cobra.Command{ Use: "server", - Short: "Run SSH tunnel server", - Long: `Run SSH tunnel server. + Short: "[EXPERIMENTAL] Run SSH tunnel server", + Long: `[EXPERIMENTAL] Run SSH tunnel server. -This command starts an SSH tunnel server that accepts WebSocket connections -and proxies them to local SSH daemon processes. +This is an experimental feature and is subject to change. -` + disclaimer, +This command starts an SSH tunnel server that accepts WebSocket connections +and proxies them to local SSH daemon processes.`, // This is an internal command spawned by the SSH client running the "ssh-server-bootstrap.py" job Hidden: true, } diff --git a/experimental/ssh/cmd/setup.go b/experimental/ssh/cmd/setup.go index 104d6bc98a5..22df72fe425 100644 --- a/experimental/ssh/cmd/setup.go +++ b/experimental/ssh/cmd/setup.go @@ -1,11 +1,9 @@ package ssh import ( - "fmt" "time" "github.com/databricks/cli/cmd/root" - "github.com/databricks/cli/experimental/ssh/internal/client" "github.com/databricks/cli/experimental/ssh/internal/setup" "github.com/databricks/cli/libs/cmdctx" "github.com/spf13/cobra" @@ -14,13 +12,14 @@ import ( func newSetupCommand() *cobra.Command { cmd := &cobra.Command{ Use: "setup", - Short: "Setup SSH configuration for Databricks compute", - Long: `Setup SSH configuration for Databricks compute. + Short: "[EXPERIMENTAL] Setup SSH configuration for dedicated (single-user) clusters", + Long: `[EXPERIMENTAL] Setup SSH configuration for dedicated (single-user) clusters. -This command configures SSH to connect to Databricks compute by adding -an SSH host configuration to your SSH config file. +This is an experimental feature and is subject to change. -` + disclaimer, +After running setup, you can connect with ` + "`ssh `" + `. + +For serverless connections, use ` + "`databricks ssh connect`" + ` (no setup step needed).`, } var hostName string @@ -57,17 +56,6 @@ an SSH host configuration to your SSH config file. Profile: wsClient.Config.Profile, AutoApprove: autoApprove, } - clientOpts := client.ClientOptions{ - ClusterID: setupOpts.ClusterID, - AutoStartCluster: setupOpts.AutoStartCluster, - ShutdownDelay: setupOpts.ShutdownDelay, - Profile: setupOpts.Profile, - } - proxyCommand, err := clientOpts.ToProxyCommand() - if err != nil { - return fmt.Errorf("failed to generate ProxyCommand: %w", err) - } - setupOpts.ProxyCommand = proxyCommand return setup.Setup(ctx, wsClient, setupOpts) } diff --git a/experimental/ssh/cmd/ssh.go b/experimental/ssh/cmd/ssh.go index b1425054359..3d139ffd8c5 100644 --- a/experimental/ssh/cmd/ssh.go +++ b/experimental/ssh/cmd/ssh.go @@ -4,27 +4,21 @@ import ( "github.com/spf13/cobra" ) -const disclaimer = `WARNING! This is an experimental feature: -- The product is in preview and not intended to be used in production; -- The product may change or may never be released; -- While we will not charge separately for this product right now, we may charge for it in the future. You will still incur charges for DBUs; -- There's no formal support or SLAs for the preview - so please reach out to your account or other contact with any questions or feedback;` - func New() *cobra.Command { cmd := &cobra.Command{ Use: "ssh", - Short: "Connect to Databricks compute with ssh", + Short: "[EXPERIMENTAL] Connect to your Databricks compute and workspace via SSH", Hidden: true, - Long: `Connect to Databricks compute with ssh. + Long: `[EXPERIMENTAL] Connect to your Databricks compute and workspace via SSH. -SSH commands let you setup and establish ssh connections to Databricks compute. +This is an experimental feature and is subject to change. Common workflows: - databricks ssh connect --cluster= --profile= # connect to a cluster without any setup - databricks ssh setup --name=my-compute --cluster= # update local ssh config - ssh my-compute # connect to the compute using ssh client + databricks ssh connect --ide=cursor # connect to serverless through Cursor + databricks ssh setup --name= --cluster= # update ~/.ssh/config so you can reconnect to a dedicated cluster + ssh # connect to dedicated cluster after setup -` + disclaimer, +Use ` + "`databricks ssh connect --help`" + ` to see all available flags.`, } cmd.AddCommand(newSetupCommand()) diff --git a/experimental/ssh/internal/client/client.go b/experimental/ssh/internal/client/client.go index df7a3edd3db..69360b85d12 100644 --- a/experimental/ssh/internal/client/client.go +++ b/experimental/ssh/internal/client/client.go @@ -31,9 +31,9 @@ import ( "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/retries" "github.com/databricks/databricks-sdk-go/service/compute" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/jobs" "github.com/databricks/databricks-sdk-go/service/workspace" - "github.com/fatih/color" "github.com/gorilla/websocket" ) @@ -228,7 +228,7 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt if !opts.ProxyMode { cmdio.LogString(ctx, fmt.Sprintf("Connecting to %s...", sessionID)) if opts.IsServerlessMode() && opts.Accelerator == "" { - cmdio.LogString(ctx, color.YellowString("WARNING: serverless compute without an accelerator is in private preview. If you are not enrolled, this command will likely time out with an error. Contact your Databricks account team to enroll.")) + cmdio.LogString(ctx, cmdio.Yellow(ctx, "WARNING: serverless compute without an accelerator is in private preview. If you are not enrolled, this command will likely time out with an error. Contact your Databricks account team to enroll.")) } } @@ -314,7 +314,7 @@ func Run(ctx context.Context, client *databricks.WorkspaceClient, opts ClientOpt if err != nil { if opts.IsServerlessMode() && opts.Accelerator == "" && errors.Is(err, errServerMetadata) { return fmt.Errorf("failed to ensure that ssh server is running: %w\n\n"+ - color.YellowString("This may be because serverless compute without an accelerator is in private preview.\nContact your Databricks account team to enroll."), err) + cmdio.Yellow(ctx, "This may be because serverless compute without an accelerator is in private preview.\nContact your Databricks account team to enroll."), err) } return fmt.Errorf("failed to ensure that ssh server is running: %w", err) } @@ -371,7 +371,7 @@ func runIDE(ctx context.Context, client *databricks.WorkspaceClient, userName, k } // Get Databricks user name for the workspace path - currentUser, err := client.CurrentUser.Me(ctx) + currentUser, err := client.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return fmt.Errorf("failed to get current user: %w", err) } diff --git a/experimental/ssh/internal/keys/secrets.go b/experimental/ssh/internal/keys/secrets.go index 76d44da5387..6409770a4d7 100644 --- a/experimental/ssh/internal/keys/secrets.go +++ b/experimental/ssh/internal/keys/secrets.go @@ -7,13 +7,14 @@ import ( "fmt" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/workspace" ) // CreateKeysSecretScope creates or retrieves the secret scope for SSH keys. // sessionID is the unique identifier for the session (cluster ID for dedicated clusters, connection name for serverless). func CreateKeysSecretScope(ctx context.Context, client *databricks.WorkspaceClient, sessionID string) (string, error) { - me, err := client.CurrentUser.Me(ctx) + me, err := client.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return "", fmt.Errorf("failed to get current user: %w", err) } diff --git a/experimental/ssh/internal/setup/setup.go b/experimental/ssh/internal/setup/setup.go index c2645a63797..43056c61a7d 100644 --- a/experimental/ssh/internal/setup/setup.go +++ b/experimental/ssh/internal/setup/setup.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + sshclient "github.com/databricks/cli/experimental/ssh/internal/client" "github.com/databricks/cli/experimental/ssh/internal/keys" "github.com/databricks/cli/experimental/ssh/internal/sshconfig" "github.com/databricks/cli/libs/cmdio" @@ -28,8 +29,6 @@ type SetupOptions struct { SSHKeysDir string // Optional auth profile name. If present, will be added as --profile flag to the ProxyCommand Profile string - // Proxy command to use for the SSH connection - ProxyCommand string // Skip confirmation prompts (e.g. recreate existing host config without asking) AutoApprove bool } @@ -45,17 +44,20 @@ func validateClusterAccess(ctx context.Context, client *databricks.WorkspaceClie return nil } -func generateHostConfig(ctx context.Context, opts SetupOptions) (string, error) { +func generateHostConfig(ctx context.Context, opts SetupOptions, proxyCommand string) (string, error) { identityFilePath, err := keys.GetLocalSSHKeyPath(ctx, opts.ClusterID, opts.SSHKeysDir) if err != nil { return "", fmt.Errorf("failed to get local keys folder: %w", err) } - hostConfig := sshconfig.GenerateHostConfig(opts.HostName, "root", identityFilePath, opts.ProxyCommand) + hostConfig := sshconfig.GenerateHostConfig(opts.HostName, "root", identityFilePath, proxyCommand) return hostConfig, nil } -func clusterSelectionPrompt(ctx context.Context, client *databricks.WorkspaceClient) (string, error) { +// clusterSelectionPrompt is a package-level var so tests can replace it with a mock. +var clusterSelectionPrompt = defaultClusterSelectionPrompt + +func defaultClusterSelectionPrompt(ctx context.Context, client *databricks.WorkspaceClient) (string, error) { sp := cmdio.NewSpinner(ctx) sp.Update("Loading clusters.") clusters, err := client.Clusters.ClusterDetailsClusterNameToClusterIdMap(ctx, compute.ListClustersRequest{ @@ -92,6 +94,20 @@ func Setup(ctx context.Context, client *databricks.WorkspaceClient, opts SetupOp return err } + // Build the ProxyCommand after the cluster ID is resolved. When the user + // omits --cluster, the ID is only known after the interactive picker above, + // so building it earlier would serialize an empty --cluster= flag. + clientOpts := sshclient.ClientOptions{ + ClusterID: opts.ClusterID, + AutoStartCluster: opts.AutoStartCluster, + ShutdownDelay: opts.ShutdownDelay, + Profile: opts.Profile, + } + proxyCommand, err := clientOpts.ToProxyCommand() + if err != nil { + return fmt.Errorf("failed to generate ProxyCommand: %w", err) + } + configPath, err := sshconfig.GetMainConfigPathOrDefault(ctx, opts.SSHConfigPath) if err != nil { return err @@ -102,7 +118,7 @@ func Setup(ctx context.Context, client *databricks.WorkspaceClient, opts SetupOp return err } - hostConfig, err := generateHostConfig(ctx, opts) + hostConfig, err := generateHostConfig(ctx, opts, proxyCommand) if err != nil { return err } diff --git a/experimental/ssh/internal/setup/setup_test.go b/experimental/ssh/internal/setup/setup_test.go index 4cd9970fee8..62dde513f2e 100644 --- a/experimental/ssh/internal/setup/setup_test.go +++ b/experimental/ssh/internal/setup/setup_test.go @@ -1,6 +1,7 @@ package setup import ( + "context" "errors" "fmt" "os" @@ -10,6 +11,7 @@ import ( "github.com/databricks/cli/experimental/ssh/internal/client" "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/experimental/mocks" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/stretchr/testify/assert" @@ -134,10 +136,9 @@ func TestGenerateHostConfig_Valid(t *testing.T) { SSHKeysDir: tmpDir, ShutdownDelay: 30 * time.Second, Profile: "test-profile", - ProxyCommand: proxyCommand, } - result, err := generateHostConfig(t.Context(), opts) + result, err := generateHostConfig(t.Context(), opts, proxyCommand) assert.NoError(t, err) assert.Contains(t, result, "Host test-host") @@ -169,10 +170,9 @@ func TestGenerateHostConfig_WithoutProfile(t *testing.T) { SSHKeysDir: tmpDir, ShutdownDelay: 30 * time.Second, Profile: "", - ProxyCommand: proxyCommand, } - result, err := generateHostConfig(t.Context(), opts) + result, err := generateHostConfig(t.Context(), opts, proxyCommand) assert.NoError(t, err) assert.NotContains(t, result, "--profile=") @@ -193,7 +193,7 @@ func TestGenerateHostConfig_PathEscaping(t *testing.T) { ShutdownDelay: 30 * time.Second, } - result, err := generateHostConfig(t.Context(), opts) + result, err := generateHostConfig(t.Context(), opts, "") assert.NoError(t, err) // Check that quotes are properly escaped @@ -225,17 +225,7 @@ func TestSetup_SuccessfulWithNewConfigFile(t *testing.T) { Profile: "test-profile", } - clientOpts := client.ClientOptions{ - ClusterID: opts.ClusterID, - AutoStartCluster: opts.AutoStartCluster, - ShutdownDelay: opts.ShutdownDelay, - Profile: opts.Profile, - } - proxyCommand, err := clientOpts.ToProxyCommand() - require.NoError(t, err) - opts.ProxyCommand = proxyCommand - - err = Setup(ctx, m.WorkspaceClient, opts) + err := Setup(ctx, m.WorkspaceClient, opts) assert.NoError(t, err) // Check that main config has Include directive @@ -285,15 +275,7 @@ func TestSetup_AutoApproveRecreatesExistingHost(t *testing.T) { AutoApprove: true, } - clientOpts := client.ClientOptions{ - ClusterID: opts.ClusterID, - ShutdownDelay: opts.ShutdownDelay, - } - proxyCommand, err := clientOpts.ToProxyCommand() - require.NoError(t, err) - opts.ProxyCommand = proxyCommand - - err = Setup(ctx, m.WorkspaceClient, opts) + err := Setup(ctx, m.WorkspaceClient, opts) assert.NoError(t, err) // Host config should be recreated (no longer contains the stale User). @@ -304,6 +286,50 @@ func TestSetup_AutoApproveRecreatesExistingHost(t *testing.T) { assert.Contains(t, s, "--cluster=cluster-123") } +func TestSetup_PromptsForClusterWhenNotProvided(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + tmpDir := t.TempDir() + t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) + + configPath := filepath.Join(tmpDir, "ssh_config") + + // Replace the cluster picker with a stub returning a fixed ID. This lets the + // test exercise the empty-ClusterID path of Setup without prompting. + origPrompt := clusterSelectionPrompt + t.Cleanup(func() { clusterSelectionPrompt = origPrompt }) + promptCalled := false + clusterSelectionPrompt = func(_ context.Context, _ *databricks.WorkspaceClient) (string, error) { + promptCalled = true + return "picked-cluster", nil + } + + m := mocks.NewMockWorkspaceClient(t) + clustersAPI := m.GetMockClustersAPI() + clustersAPI.EXPECT().Get(ctx, compute.GetClusterRequest{ClusterId: "picked-cluster"}).Return(&compute.ClusterDetails{ + DataSecurityMode: compute.DataSecurityModeSingleUser, + }, nil) + + opts := SetupOptions{ + HostName: "test-host", + SSHConfigPath: configPath, + SSHKeysDir: tmpDir, + ShutdownDelay: 30 * time.Second, + } + + err := Setup(ctx, m.WorkspaceClient, opts) + require.NoError(t, err) + assert.True(t, promptCalled, "cluster picker should run when ClusterID is empty") + + // The picked ID must be serialized into the ProxyCommand's --cluster= flag. + hostConfigPath := filepath.Join(tmpDir, ".databricks", "ssh-tunnel-configs", "test-host") + hostContent, err := os.ReadFile(hostConfigPath) + require.NoError(t, err) + hostConfigStr := string(hostContent) + assert.Contains(t, hostConfigStr, "--cluster=picked-cluster") + assert.NotContains(t, hostConfigStr, "--cluster= ") +} + func TestSetup_SuccessfulWithExistingConfigFile(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) tmpDir := t.TempDir() @@ -332,16 +358,6 @@ func TestSetup_SuccessfulWithExistingConfigFile(t *testing.T) { ShutdownDelay: 60 * time.Second, } - clientOpts := client.ClientOptions{ - ClusterID: opts.ClusterID, - AutoStartCluster: opts.AutoStartCluster, - ShutdownDelay: opts.ShutdownDelay, - Profile: opts.Profile, - } - proxyCommand, err := clientOpts.ToProxyCommand() - require.NoError(t, err) - opts.ProxyCommand = proxyCommand - err = Setup(ctx, m.WorkspaceClient, opts) assert.NoError(t, err) diff --git a/experimental/ssh/internal/workspace/workspace.go b/experimental/ssh/internal/workspace/workspace.go index 2f017cbae12..0a28b684ebc 100644 --- a/experimental/ssh/internal/workspace/workspace.go +++ b/experimental/ssh/internal/workspace/workspace.go @@ -10,6 +10,7 @@ import ( "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" ) const metadataFileName = "metadata.json" @@ -21,7 +22,7 @@ type WorkspaceMetadata struct { } func getWorkspaceRootDir(ctx context.Context, client *databricks.WorkspaceClient) (string, error) { - me, err := client.CurrentUser.Me(ctx) + me, err := client.CurrentUser.Me(ctx, iam.MeRequest{}) if err != nil { return "", fmt.Errorf("failed to get current user: %w", err) } diff --git a/go.mod b/go.mod index f376aa0a98d..02ab5828520 100644 --- a/go.mod +++ b/go.mod @@ -1,31 +1,29 @@ module github.com/databricks/cli -go 1.25.0 +go 1.26.0 -toolchain go1.25.9 +toolchain go1.26.3 require ( dario.cat/mergo v1.0.2 // BSD-3-Clause github.com/BurntSushi/toml v1.6.0 // MIT - github.com/Masterminds/semver/v3 v3.4.0 // MIT + github.com/Masterminds/semver/v3 v3.5.0 // MIT github.com/charmbracelet/bubbles v1.0.0 // MIT github.com/charmbracelet/bubbletea v1.3.10 // MIT github.com/charmbracelet/huh v1.0.0 // MIT github.com/charmbracelet/lipgloss v1.1.0 // MIT - github.com/databricks/databricks-sdk-go v0.128.0 // Apache-2.0 - github.com/fatih/color v1.19.0 // MIT - github.com/google/jsonschema-go v0.4.2 // MIT + github.com/charmbracelet/x/ansi v0.11.6 // MIT + github.com/databricks/databricks-sdk-go v0.136.0 // Apache-2.0 + github.com/google/jsonschema-go v0.4.3 // MIT github.com/google/uuid v1.6.0 // BSD-3-Clause - github.com/gorilla/mux v1.8.1 // BSD-3-Clause github.com/gorilla/websocket v1.5.3 // BSD-2-Clause github.com/hashicorp/go-version v1.9.0 // MPL-2.0 - github.com/hashicorp/hc-install v0.9.3 // MPL-2.0 - github.com/hashicorp/terraform-exec v0.25.0 // MPL-2.0 + github.com/hashicorp/hc-install v0.9.5 // MPL-2.0 + github.com/hashicorp/terraform-exec v0.25.2 // MPL-2.0 github.com/hashicorp/terraform-json v0.27.2 // MPL-2.0 github.com/hexops/gotextdiff v1.0.3 // BSD-3-Clause - github.com/manifoldco/promptui v0.9.0 // BSD-3-Clause - github.com/mattn/go-isatty v0.0.20 // MIT - github.com/nwidger/jsoncolor v0.3.2 // MIT + github.com/jackc/pgx/v5 v5.9.2 // MIT + github.com/mattn/go-isatty v0.0.22 // MIT github.com/palantir/pkg/yamlpatch v1.5.0 // BSD-3-Clause github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // BSD-2-Clause github.com/quasilyte/go-ruleguard/dsl v0.3.22 // BSD-3-Clause @@ -36,12 +34,12 @@ require ( github.com/tailscale/hujson v0.0.0-20250605163823-992244df8c5a // BSD-3-Clause github.com/zalando/go-keyring v0.2.8 // MIT go.yaml.in/yaml/v3 v3.0.4 // MIT AND Apache-2.0 - golang.org/x/crypto v0.49.0 // BSD-3-Clause - golang.org/x/mod v0.34.0 // BSD-3-Clause + golang.org/x/crypto v0.50.0 // BSD-3-Clause + golang.org/x/mod v0.35.0 // BSD-3-Clause golang.org/x/oauth2 v0.36.0 // BSD-3-Clause golang.org/x/sync v0.20.0 // BSD-3-Clause - golang.org/x/sys v0.43.0 // BSD-3-Clause - golang.org/x/text v0.35.0 // BSD-3-Clause + golang.org/x/sys v0.44.0 // BSD-3-Clause + golang.org/x/text v0.36.0 // BSD-3-Clause gopkg.in/ini.v1 v1.67.1 // Apache-2.0 ) @@ -49,18 +47,16 @@ require ( cloud.google.com/go/auth v0.18.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/colorprofile v0.4.1 // indirect - github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/clipperhouse/displaywidth v0.9.0 // indirect github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.5.0 // indirect @@ -80,6 +76,8 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -93,14 +91,14 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/zclconf/go-cty v1.17.0 // indirect + github.com/zclconf/go-cty v1.18.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.51.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/api v0.265.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect diff --git a/go.sum b/go.sum index f9181b898a2..cb7bd44ee92 100644 --- a/go.sum +++ b/go.sum @@ -10,12 +10,12 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -56,12 +56,6 @@ github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo= github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= @@ -77,8 +71,8 @@ github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22r github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= -github.com/databricks/databricks-sdk-go v0.128.0 h1:4aGI3KkSZHDkxNIgwQL6dn6q6zZKcgyckidcQZNDGGo= -github.com/databricks/databricks-sdk-go v0.128.0/go.mod h1:C5LNgGe6hGuRrTwoxFmuup3XtQQEaqtq0e+K8IFDIS4= +github.com/databricks/databricks-sdk-go v0.136.0 h1:7TF+fDS9jpwBqnVy0foZ/ej7zL0h1EezuHfYxGYvhNE= +github.com/databricks/databricks-sdk-go v0.136.0/go.mod h1:C5LNgGe6hGuRrTwoxFmuup3XtQQEaqtq0e+K8IFDIS4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -88,17 +82,16 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= -github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -115,8 +108,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -125,8 +118,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dq github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= -github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= -github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -137,16 +128,24 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.9.3 h1:1H4dgmgzxEVwT6E/d/vIL5ORGVKz9twRwDw+qA5Hyho= -github.com/hashicorp/hc-install v0.9.3/go.mod h1:FQlQ5I3I/X409N/J1U4pPeQQz1R3BoV0IysB7aiaQE0= -github.com/hashicorp/terraform-exec v0.25.0 h1:Bkt6m3VkJqYh+laFMrWIpy9KHYFITpOyzRMNI35rNaY= -github.com/hashicorp/terraform-exec v0.25.0/go.mod h1:dl9IwsCfklDU6I4wq9/StFDp7dNbH/h5AnfS1RmiUl8= +github.com/hashicorp/hc-install v0.9.5 h1:XHCjcMn2563ysuaQ9v9ec2FNc7c2PJOIEEGobAFeIx4= +github.com/hashicorp/hc-install v0.9.5/go.mod h1:ihEW4LshrNkxq2bU/MpVbKyn+yt1is2hYqUTHDGhG84= +github.com/hashicorp/terraform-exec v0.25.2 h1:fFLAVEtAjKdGfawGUXDnKooCnqJi+TuohT3W99AGbhk= +github.com/hashicorp/terraform-exec v0.25.2/go.mod h1:uaQV2oqVLqM4cixJryk6qIWS1qji3GtuwPG5pjGXYfc= github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= @@ -157,15 +156,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= -github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= @@ -178,8 +172,6 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/nwidger/jsoncolor v0.3.2 h1:rVJJlwAWDJShnbTYOQ5RM7yTA20INyKXlJ/fg4JMhHQ= -github.com/nwidger/jsoncolor v0.3.2/go.mod h1:Cs34umxLbJvgBMnVNVqhji9BhoT/N/KinHqZptQ7cf4= github.com/palantir/pkg/yamlpatch v1.5.0 h1:186RUlcHFVf64onUhaI7nUCPzPIaRTQ5HJlKuv0d6NM= github.com/palantir/pkg/yamlpatch v1.5.0/go.mod h1:45cYAIiv9E0MiZnHjIIT2hGqi6Wah/DL6J1omJf2ny0= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= @@ -213,7 +205,9 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -227,8 +221,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs= github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= -github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0= -github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U= +github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= +github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= @@ -247,30 +241,26 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09 go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/integration/README.md b/integration/README.md index 1c1d7c6f653..9bcddfc54ad 100644 --- a/integration/README.md +++ b/integration/README.md @@ -33,5 +33,5 @@ go test ./integration/... Alternatively: ```bash -make integration +./task integration ``` diff --git a/integration/bundle/bundles/spark_jar_task/databricks_template_schema.json b/integration/bundle/bundles/spark_jar_task/databricks_template_schema.json deleted file mode 100644 index 1381da1ddc2..00000000000 --- a/integration/bundle/bundles/spark_jar_task/databricks_template_schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "properties": { - "project_name": { - "type": "string", - "default": "my_java_project", - "description": "Unique name for this project" - }, - "spark_version": { - "type": "string", - "description": "Spark version used for job cluster" - }, - "node_type_id": { - "type": "string", - "description": "Node type id for job cluster" - }, - "unique_id": { - "type": "string", - "description": "Unique ID for job name" - }, - "root": { - "type": "string", - "description": "Path to the root of the template" - }, - "artifact_path": { - "type": "string", - "description": "Path to the remote base path for artifacts" - }, - "instance_pool_id": { - "type": "string", - "description": "Instance pool id for job cluster" - } - } -} diff --git a/integration/bundle/bundles/spark_jar_task/template/databricks.yml.tmpl b/integration/bundle/bundles/spark_jar_task/template/databricks.yml.tmpl deleted file mode 100644 index db451cd93b1..00000000000 --- a/integration/bundle/bundles/spark_jar_task/template/databricks.yml.tmpl +++ /dev/null @@ -1,55 +0,0 @@ -bundle: - name: spark-jar-task - -workspace: - root_path: "~/.bundle/{{.unique_id}}" - -artifacts: - my_java_code: - path: ./{{.project_name}} - build: "javac PrintArgs.java && jar cvfm PrintArgs.jar META-INF/MANIFEST.MF PrintArgs.class" - files: - - source: ./{{.project_name}}/PrintArgs.jar - -resources: - jobs: - jar_job: - name: "[${bundle.target}] Test Spark Jar Job {{.unique_id}}" - tasks: - - task_key: TestSparkJarTask - new_cluster: - num_workers: 1 - spark_version: "{{.spark_version}}" - node_type_id: "{{.node_type_id}}" - instance_pool_id: "{{.instance_pool_id}}" - spark_jar_task: - main_class_name: PrintArgs - libraries: - - jar: ./{{.project_name}}/PrintArgs.jar - -targets: - volume: - # Override the artifact path to upload artifacts to a volume path - workspace: - artifact_path: {{.artifact_path}} - - resources: - jobs: - jar_job: - tasks: - - task_key: TestSparkJarTask - new_cluster: - - # Force cluster to run in single user mode (force it to be a UC cluster) - data_security_mode: SINGLE_USER - - workspace: - resources: - jobs: - jar_job: - tasks: - - task_key: TestSparkJarTask - new_cluster: - - # Force cluster to run in no isolation mode (force it to be a non-UC cluster) - data_security_mode: NONE diff --git a/integration/bundle/generate_job_test.go b/integration/bundle/generate_job_test.go deleted file mode 100644 index 8c51a55d407..00000000000 --- a/integration/bundle/generate_job_test.go +++ /dev/null @@ -1,116 +0,0 @@ -package bundle_test - -import ( - "context" - "os" - "path" - "path/filepath" - "strconv" - "strings" - "testing" - - "github.com/databricks/cli/integration/internal/acc" - "github.com/databricks/cli/internal/testcli" - "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/env" - "github.com/databricks/cli/libs/filer" - "github.com/databricks/databricks-sdk-go" - "github.com/databricks/databricks-sdk-go/service/compute" - "github.com/databricks/databricks-sdk-go/service/jobs" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestGenerateFromExistingJobAndDeploy(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - gt := &generateJobTest{T: wt, w: wt.W} - - uniqueId := uuid.New().String() - bundleRoot := initTestTemplate(t, ctx, "with_includes", map[string]any{ - "unique_id": uniqueId, - }) - - jobId := gt.createTestJob(ctx) - t.Cleanup(func() { - gt.destroyJob(context.WithoutCancel(ctx), jobId) - }) - - ctx = env.Set(ctx, "BUNDLE_ROOT", bundleRoot) - c := testcli.NewRunner(t, ctx, "bundle", "generate", "job", - "--existing-job-id", strconv.FormatInt(jobId, 10), - "--config-dir", filepath.Join(bundleRoot, "resources"), - "--source-dir", filepath.Join(bundleRoot, "src")) - _, _, err := c.Run() - require.NoError(t, err) - - _, err = os.Stat(filepath.Join(bundleRoot, "src", "test.py")) - require.NoError(t, err) - - matches, err := filepath.Glob(filepath.Join(bundleRoot, "resources", "generated_job_*.yml")) - require.NoError(t, err) - require.Len(t, matches, 1) - - // check the content of generated yaml - data, err := os.ReadFile(matches[0]) - require.NoError(t, err) - generatedYaml := string(data) - require.Contains(t, generatedYaml, "notebook_task:") - require.Contains(t, generatedYaml, "notebook_path: "+filepath.Join("..", "src", "test.py")) - require.Contains(t, generatedYaml, "task_key: test") - require.Contains(t, generatedYaml, "new_cluster:") - require.Contains(t, generatedYaml, "spark_version: 13.3.x-scala2.12") - require.Contains(t, generatedYaml, "num_workers: 1") - - deployBundle(t, ctx, bundleRoot) - - destroyBundle(t, ctx, bundleRoot) -} - -type generateJobTest struct { - T *acc.WorkspaceT - w *databricks.WorkspaceClient -} - -func (gt *generateJobTest) createTestJob(ctx context.Context) int64 { - t := gt.T - w := gt.w - - tmpdir := acc.TemporaryWorkspaceDir(t, "generate-job-") - f, err := filer.NewWorkspaceFilesClient(w, tmpdir) - require.NoError(t, err) - - err = f.Write(ctx, "test.py", strings.NewReader("# Databricks notebook source\nprint('Hello world!'))")) - require.NoError(t, err) - - resp, err := w.Jobs.Create(ctx, jobs.CreateJob{ - Name: testutil.RandomName("generated-job-"), - Tasks: []jobs.Task{ - { - TaskKey: "test", - NewCluster: &compute.ClusterSpec{ - SparkVersion: "13.3.x-scala2.12", - NumWorkers: 1, - NodeTypeId: testutil.GetCloud(t).NodeTypeID(), - SparkConf: map[string]string{ - "spark.databricks.enableWsfs": "true", - "spark.databricks.hive.metastore.glueCatalog.enabled": "true", - "spark.databricks.pip.ignoreSSL": "true", - }, - }, - NotebookTask: &jobs.NotebookTask{ - NotebookPath: path.Join(tmpdir, "test"), - }, - }, - }, - }) - require.NoError(t, err) - - return resp.JobId -} - -func (gt *generateJobTest) destroyJob(ctx context.Context, jobId int64) { - err := gt.w.Jobs.Delete(ctx, jobs.DeleteJob{ - JobId: jobId, - }) - require.NoError(gt.T, err) -} diff --git a/integration/bundle/generate_pipeline_test.go b/integration/bundle/generate_pipeline_test.go deleted file mode 100644 index b1f68f79df9..00000000000 --- a/integration/bundle/generate_pipeline_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package bundle_test - -import ( - "context" - "os" - "path" - "path/filepath" - "strings" - "testing" - - "github.com/databricks/cli/integration/internal/acc" - "github.com/databricks/cli/internal/testcli" - "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/env" - "github.com/databricks/cli/libs/filer" - "github.com/databricks/databricks-sdk-go" - "github.com/databricks/databricks-sdk-go/service/pipelines" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -func TestGenerateFromExistingPipelineAndDeploy(t *testing.T) { - ctx, wt := acc.WorkspaceTest(t) - gt := &generatePipelineTest{T: wt, w: wt.W} - - uniqueId := uuid.New().String() - bundleRoot := initTestTemplate(t, ctx, "with_includes", map[string]any{ - "unique_id": uniqueId, - }) - - pipelineId, name := gt.createTestPipeline(ctx) - t.Cleanup(func() { - gt.destroyPipeline(context.WithoutCancel(ctx), pipelineId) - }) - - ctx = env.Set(ctx, "BUNDLE_ROOT", bundleRoot) - c := testcli.NewRunner(t, ctx, "bundle", "generate", "pipeline", - "--existing-pipeline-id", pipelineId, - "--config-dir", filepath.Join(bundleRoot, "resources"), - "--source-dir", filepath.Join(bundleRoot, "src")) - _, _, err := c.Run() - require.NoError(t, err) - - _, err = os.Stat(filepath.Join(bundleRoot, "src", "notebook.py")) - require.NoError(t, err) - - _, err = os.Stat(filepath.Join(bundleRoot, "src", "test.py")) - require.NoError(t, err) - - matches, err := filepath.Glob(filepath.Join(bundleRoot, "resources", "generated_pipeline_*.yml")) - require.NoError(t, err) - require.Len(t, matches, 1) - - // check the content of generated yaml - fileName := matches[0] - data, err := os.ReadFile(fileName) - require.NoError(t, err) - generatedYaml := string(data) - - // Replace pipeline name - generatedYaml = strings.ReplaceAll(generatedYaml, name, testutil.RandomName("copy-generated-pipeline-")) - err = os.WriteFile(fileName, []byte(generatedYaml), 0o644) - require.NoError(t, err) - - require.Contains(t, generatedYaml, "libraries:") - require.Contains(t, generatedYaml, "- notebook:") - require.Contains(t, generatedYaml, "path: "+filepath.Join("..", "src", "notebook.py")) - require.Contains(t, generatedYaml, "- file:") - require.Contains(t, generatedYaml, "path: "+filepath.Join("..", "src", "test.py")) - - deployBundle(t, ctx, bundleRoot) - - destroyBundle(t, ctx, bundleRoot) -} - -type generatePipelineTest struct { - T *acc.WorkspaceT - w *databricks.WorkspaceClient -} - -func (gt *generatePipelineTest) createTestPipeline(ctx context.Context) (string, string) { - t := gt.T - w := gt.w - - tmpdir := acc.TemporaryWorkspaceDir(t, "generate-pipeline-") - f, err := filer.NewWorkspaceFilesClient(w, tmpdir) - require.NoError(t, err) - - err = f.Write(ctx, "notebook.py", strings.NewReader("# Databricks notebook source\nprint('Hello world!'))")) - require.NoError(t, err) - - err = f.Write(ctx, "test.py", strings.NewReader("print('Hello!')")) - require.NoError(t, err) - - nodeTypeId := testutil.GetCloud(t).NodeTypeID() - - name := testutil.RandomName("generated-pipeline-") - resp, err := w.Pipelines.Create(ctx, pipelines.CreatePipeline{ - Name: name, - Libraries: []pipelines.PipelineLibrary{ - { - Notebook: &pipelines.NotebookLibrary{ - Path: path.Join(tmpdir, "notebook"), - }, - }, - { - File: &pipelines.FileLibrary{ - Path: path.Join(tmpdir, "test.py"), - }, - }, - }, - Clusters: []pipelines.PipelineCluster{ - { - CustomTags: map[string]string{ - "Tag1": "Yes", - "Tag2": "24X7", - "Tag3": "APP-1234", - }, - NodeTypeId: nodeTypeId, - NumWorkers: 2, - SparkConf: map[string]string{ - "spark.databricks.enableWsfs": "true", - "spark.databricks.hive.metastore.glueCatalog.enabled": "true", - "spark.databricks.pip.ignoreSSL": "true", - }, - }, - }, - }) - require.NoError(t, err) - - return resp.PipelineId, name -} - -func (gt *generatePipelineTest) destroyPipeline(ctx context.Context, pipelineId string) { - err := gt.w.Pipelines.Delete(ctx, pipelines.DeletePipelineRequest{ - PipelineId: pipelineId, - }) - require.NoError(gt.T, err) -} diff --git a/integration/bundle/helpers_test.go b/integration/bundle/helpers_test.go deleted file mode 100644 index 48ca44585b2..00000000000 --- a/integration/bundle/helpers_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package bundle_test - -import ( - "context" - "encoding/json" - "os" - "path/filepath" - "strings" - - "github.com/databricks/cli/internal/testcli" - "github.com/databricks/cli/internal/testutil" - "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/template" - "github.com/stretchr/testify/require" -) - -func initTestTemplate(t testutil.TestingT, ctx context.Context, templateName string, config map[string]any) string { - bundleRoot := t.TempDir() - return initTestTemplateWithBundleRoot(t, ctx, templateName, config, bundleRoot) -} - -func initTestTemplateWithBundleRoot(t testutil.TestingT, ctx context.Context, templateName string, config map[string]any, bundleRoot string) string { - templateRoot := filepath.Join("bundles", templateName) - - configFilePath := writeConfigFile(t, config) - - ctx = cmdctx.SetWorkspaceClient(ctx, nil) - cmd := cmdio.NewIO(ctx, flags.OutputJSON, strings.NewReader(""), os.Stdout, os.Stderr, "", "bundles") - ctx = cmdio.InContext(ctx, cmd) - - r := template.Resolver{ - TemplatePathOrUrl: templateRoot, - ConfigFile: configFilePath, - OutputDir: bundleRoot, - } - - tmpl, err := r.Resolve(ctx) - require.NoError(t, err) - defer tmpl.Reader.Cleanup(ctx) - - err = tmpl.Writer.Materialize(ctx, tmpl.Reader) - require.NoError(t, err) - - return bundleRoot -} - -func writeConfigFile(t testutil.TestingT, config map[string]any) string { - bytes, err := json.Marshal(config) - require.NoError(t, err) - - dir := t.TempDir() - filepath := filepath.Join(dir, "config.json") - t.Log("Configuration for template: ", string(bytes)) - - err = os.WriteFile(filepath, bytes, 0o644) - require.NoError(t, err) - return filepath -} - -func deployBundle(t testutil.TestingT, ctx context.Context, path string) { - ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := testcli.NewRunner(t, ctx, "bundle", "deploy", "--force-lock", "--auto-approve") - _, _, err := c.Run() - require.NoError(t, err) -} - -func runResource(t testutil.TestingT, ctx context.Context, path, key string) (string, error) { - ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := testcli.NewRunner(t, ctx, "bundle", "run", key) - stdout, _, err := c.Run() - return stdout.String(), err -} - -func destroyBundle(t testutil.TestingT, ctx context.Context, path string) { - ctx = env.Set(ctx, "BUNDLE_ROOT", path) - c := testcli.NewRunner(t, ctx, "bundle", "destroy", "--auto-approve") - _, _, err := c.Run() - require.NoError(t, err) -} diff --git a/integration/bundle/init_test.go b/integration/bundle/init_test.go index 6bec010f7b1..ff8b7de2c87 100644 --- a/integration/bundle/init_test.go +++ b/integration/bundle/init_test.go @@ -10,6 +10,7 @@ import ( "github.com/databricks/cli/internal/testcli" "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/iamutil" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,7 +26,7 @@ func TestBundleInitHelpers(t *testing.T) { ctx, wt := acc.WorkspaceTest(t) w := wt.W - me, err := w.CurrentUser.Me(ctx) + me, err := w.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) var smallestNode string diff --git a/integration/bundle/spark_jar_test.go b/integration/bundle/spark_jar_test.go deleted file mode 100644 index 045af404fa5..00000000000 --- a/integration/bundle/spark_jar_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package bundle_test - -import ( - "context" - "testing" - - "github.com/databricks/cli/integration/internal/acc" - "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/env" - "github.com/google/uuid" - "github.com/stretchr/testify/require" -) - -// sparkJarTestCase defines a Databricks runtime version and a local Java version requirement -type sparkJarTestCase struct { - name string // Test name - runtimeVersion string // The Spark runtime version to test - requiredJavaVersion string // Java version that can compile jar to pass this test -} - -// runSparkJarTests runs a set of test cases with appropriate Java version checks -// testRunner is the function that runs the actual test with the runtime version -func runSparkJarTests(t *testing.T, testCases []sparkJarTestCase, testRunner func(t *testing.T, runtimeVersion string)) { - t.Helper() - - testCanRun := make(map[string]bool) - atLeastOneCanRun := false - for _, tc := range testCases { - if testutil.HasJDK(t, t.Context(), tc.requiredJavaVersion) { - testCanRun[tc.name] = true - atLeastOneCanRun = true - continue - } - testCanRun[tc.name] = false - } - - if !atLeastOneCanRun { - t.Fatal("At least one test is required to pass. All tests were skipped because no compatible Java version was found.") - } - - // Run the tests that can run - for _, tc := range testCases { - canRun := testCanRun[tc.name] - - t.Run(tc.name, func(t *testing.T) { - if !canRun { - t.Skipf("Skipping %s: requires Java version %v", tc.name, tc.requiredJavaVersion) - return - } - - t.Parallel() - testRunner(t, tc.runtimeVersion) - }) - } -} - -func runSparkJarTestCommon(t *testing.T, ctx context.Context, sparkVersion, artifactPath string) { - nodeTypeId := testutil.GetCloud(t).NodeTypeID() - tmpDir := t.TempDir() - instancePoolId := env.Get(ctx, "TEST_INSTANCE_POOL_ID") - bundleRoot := initTestTemplateWithBundleRoot(t, ctx, "spark_jar_task", map[string]any{ - "node_type_id": nodeTypeId, - "unique_id": uuid.New().String(), - "spark_version": sparkVersion, - "root": tmpDir, - "artifact_path": artifactPath, - "instance_pool_id": instancePoolId, - }, tmpDir) - - deployBundle(t, ctx, bundleRoot) - - t.Cleanup(func() { - destroyBundle(t, context.WithoutCancel(ctx), bundleRoot) - }) - - if testing.Short() { - t.Log("Skip the job run in short mode") - return - } - - out, err := runResource(t, ctx, bundleRoot, "jar_job") - require.NoError(t, err) - require.Contains(t, out, "Hello from Jar!") -} - -func runSparkJarTestFromVolume(t *testing.T, sparkVersion string) { - ctx, wt := acc.UcWorkspaceTest(t) - volumePath := acc.TemporaryVolume(wt) - ctx = env.Set(ctx, "DATABRICKS_BUNDLE_TARGET", "volume") - runSparkJarTestCommon(t, ctx, sparkVersion, volumePath) -} - -func runSparkJarTestFromWorkspace(t *testing.T, sparkVersion string) { - ctx, _ := acc.WorkspaceTest(t) - ctx = env.Set(ctx, "DATABRICKS_BUNDLE_TARGET", "workspace") - runSparkJarTestCommon(t, ctx, sparkVersion, "n/a") -} - -func TestSparkJarTaskDeployAndRunOnVolumes(t *testing.T) { - // Failure on earlier DBR versions: - // - // JAR installation from Volumes is supported on UC Clusters with DBR >= 13.3. - // Denied library is Jar(/Volumes/main/test-schema-ldgaklhcahlg/my-volume/.internal/PrintArgs.jar) - // - - testCases := []sparkJarTestCase{ - { - name: "Databricks Runtime 13.3 LTS", - runtimeVersion: "13.3.x-scala2.12", // 13.3 LTS (includes Apache Spark 3.4.1, Scala 2.12) - requiredJavaVersion: "1.8.0", // Only JDK 8 is supported - }, - { - name: "Databricks Runtime 14.3 LTS", - runtimeVersion: "14.3.x-scala2.12", // 14.3 LTS (includes Apache Spark 3.5.0, Scala 2.12) - requiredJavaVersion: "1.8.0", // Only JDK 8 is supported - }, - { - name: "Databricks Runtime 15.4 LTS", - runtimeVersion: "15.4.x-scala2.12", // 15.4 LTS (includes Apache Spark 3.5.0, Scala 2.12) - requiredJavaVersion: "1.8.0", // Only JDK 8 is supported - }, - { - name: "Databricks Runtime 16.2", - runtimeVersion: "16.2.x-scala2.12", // 16.2 (includes Apache Spark 3.5.2, Scala 2.12) - requiredJavaVersion: "11.0", // Can run jars compiled by Java 11 - }, - } - runSparkJarTests(t, testCases, runSparkJarTestFromVolume) -} - -func TestSparkJarTaskDeployAndRunOnWorkspace(t *testing.T) { - // Failure on earlier DBR versions: - // - // Library from /Workspace is not allowed on this cluster. - // Please switch to using DBR 14.1+ No Isolation Shared or DBR 13.1+ Shared cluster or 13.2+ Assigned cluster to use /Workspace libraries. - // - - testCases := []sparkJarTestCase{ - { - name: "Databricks Runtime 14.3 LTS", - runtimeVersion: "14.3.x-scala2.12", // 14.3 LTS (includes Apache Spark 3.5.0, Scala 2.12) - requiredJavaVersion: "1.8.0", // Only JDK 8 is supported - }, - { - name: "Databricks Runtime 15.4 LTS", - runtimeVersion: "15.4.x-scala2.12", // 15.4 LTS (includes Apache Spark 3.5.0, Scala 2.12) - requiredJavaVersion: "1.8.0", // Only JDK 8 is supported - }, - { - name: "Databricks Runtime 16.2", - runtimeVersion: "16.2.x-scala2.12", // 16.2 (includes Apache Spark 3.5.2, Scala 2.12) - requiredJavaVersion: "11.0", // Can run jars compiled by Java 11 - }, - } - runSparkJarTests(t, testCases, runSparkJarTestFromWorkspace) -} diff --git a/integration/cmd/auth/describe_test.go b/integration/cmd/auth/describe_test.go index 1ea843b6b93..49796b892a3 100644 --- a/integration/cmd/auth/describe_test.go +++ b/integration/cmd/auth/describe_test.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/internal/testutil" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/databricks-sdk-go/config" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/cli/internal/testcli" "github.com/databricks/databricks-sdk-go" @@ -28,7 +29,7 @@ func TestAuthDescribeSuccess(t *testing.T) { hostWithoutPrefix := strings.TrimPrefix(w.Config.Host, "https://") require.Regexp(t, "Host: (?:https://)?"+regexp.QuoteMeta(hostWithoutPrefix), outStr) - me, err := w.CurrentUser.Me(t.Context()) + me, err := w.CurrentUser.Me(t.Context(), iam.MeRequest{}) require.NoError(t, err) require.Contains(t, outStr, "User: "+me.UserName) require.Contains(t, outStr, "Authenticated with: "+w.Config.AuthType) diff --git a/integration/cmd/clusters/clusters_test.go b/integration/cmd/clusters/clusters_test.go index 33a2aa0b3ee..f15d77d440e 100644 --- a/integration/cmd/clusters/clusters_test.go +++ b/integration/cmd/clusters/clusters_test.go @@ -32,7 +32,7 @@ func TestClustersGet(t *testing.T) { clusterId := findValidClusterID(t) stdout, stderr := testcli.RequireSuccessfulRun(t, ctx, "clusters", "get", clusterId) outStr := stdout.String() - assert.Contains(t, outStr, fmt.Sprintf(`"cluster_id":"%s"`, clusterId)) + assert.Contains(t, outStr, fmt.Sprintf(`"cluster_id": "%s"`, clusterId)) assert.Equal(t, "", stderr.String()) } diff --git a/integration/cmd/sync/sync_test.go b/integration/cmd/sync/sync_test.go index 5871c41db24..49853099582 100644 --- a/integration/cmd/sync/sync_test.go +++ b/integration/cmd/sync/sync_test.go @@ -23,6 +23,7 @@ import ( "github.com/databricks/cli/libs/testfile" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/client" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -36,7 +37,7 @@ var ( // This test needs auth env vars to run. // Please run using the deco env test or deco env shell func setupRepo(t *testing.T, wsc *databricks.WorkspaceClient, ctx context.Context) (localRoot, remoteRoot string) { - me, err := wsc.CurrentUser.Me(ctx) + me, err := wsc.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) repoPath := fmt.Sprintf("/Repos/%s/%s", me.UserName, testutil.RandomName("empty-repo-sync-integration-")) @@ -487,7 +488,7 @@ func TestSyncEnsureRemotePathIsUsableIfRepoDoesntExist(t *testing.T) { ctx, wt := acc.WorkspaceTest(t) wsc := wt.W - me, err := wsc.CurrentUser.Me(ctx) + me, err := wsc.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) // Hypothetical repo path doesn't exist. @@ -526,7 +527,7 @@ func TestSyncEnsureRemotePathIsUsableInWorkspace(t *testing.T) { ctx, wt := acc.WorkspaceTest(t) wsc := wt.W - me, err := wsc.CurrentUser.Me(ctx) + me, err := wsc.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) remotePath := fmt.Sprintf("/Users/%s/%s", me.UserName, testutil.RandomName("ensure-path-exists-test-")) diff --git a/integration/internal/acc/fixtures.go b/integration/internal/acc/fixtures.go index d902d0b49a1..8d61a9d35da 100644 --- a/integration/internal/acc/fixtures.go +++ b/integration/internal/acc/fixtures.go @@ -8,13 +8,14 @@ import ( "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/catalog" "github.com/databricks/databricks-sdk-go/service/files" + "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/databricks-sdk-go/service/workspace" "github.com/stretchr/testify/require" ) func TemporaryWorkspaceDir(t *WorkspaceT, name ...string) string { ctx := t.ctx - me, err := t.W.CurrentUser.Me(ctx) + me, err := t.W.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) // Prefix the name with "integration-test-" to make it easier to identify. @@ -69,7 +70,7 @@ func TemporaryDbfsDir(t *WorkspaceT, name ...string) string { func TemporaryRepo(t *WorkspaceT, url string) string { ctx := t.ctx - me, err := t.W.CurrentUser.Me(ctx) + me, err := t.W.CurrentUser.Me(ctx, iam.MeRequest{}) require.NoError(t, err) // Prefix the path with "integration-test-" to make it easier to identify. diff --git a/integration/internal/acc/workspace.go b/integration/internal/acc/workspace.go index 878d4526568..60804509124 100644 --- a/integration/internal/acc/workspace.go +++ b/integration/internal/acc/workspace.go @@ -4,7 +4,6 @@ import ( "context" "github.com/databricks/cli/internal/testutil" - "github.com/databricks/cli/libs/env" "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/service/compute" "github.com/stretchr/testify/require" @@ -40,34 +39,6 @@ func WorkspaceTest(t testutil.TestingT) (context.Context, *WorkspaceT) { return wt.ctx, wt } -// Run the workspace test only on UC workspaces. -func UcWorkspaceTest(t testutil.TestingT) (context.Context, *WorkspaceT) { - t.Helper() - testutil.LoadDebugEnvIfRunFromIDE(t, "workspace") - - t.Logf("CLOUD_ENV=%s", testutil.GetEnvOrSkipTest(t, "CLOUD_ENV")) - - if env.Get(t.Context(), "TEST_METASTORE_ID") == "" { - t.Skipf("Skipping on non-UC workspaces") - } - if env.Get(t.Context(), "DATABRICKS_ACCOUNT_ID") != "" { - t.Skipf("Skipping on accounts") - } - - w, err := databricks.NewWorkspaceClient() - require.NoError(t, err) - - wt := &WorkspaceT{ - TestingT: t, - - W: w, - - ctx: t.Context(), - } - - return wt.ctx, wt -} - func (t *WorkspaceT) TestClusterID() string { t.Helper() clusterID := testutil.GetEnvOrSkipTest(t, "TEST_BRICKS_CLUSTER_ID") diff --git a/integration/libs/filer/filer_test.go b/integration/libs/filer/filer_test.go index 38cdf3c4060..58618628b7e 100644 --- a/integration/libs/filer/filer_test.go +++ b/integration/libs/filer/filer_test.go @@ -443,7 +443,7 @@ func TestFilerWorkspaceNotebook(t *testing.T) { // Assert uploading a second time fails due to overwrite mode missing err = f.Write(ctx, tc.name, strings.NewReader(tc.content2)) require.ErrorIs(t, err, fs.ErrExist) - assert.Regexp(t, `file already exists: .*/`+tc.nameWithoutExt+`$`, err.Error()) + assert.Regexp(t, `file already exists: .*/`+tc.name+`$`, err.Error()) // Try uploading the notebook again with overwrite flag. This time it should succeed. err = f.Write(ctx, tc.name, strings.NewReader(tc.content2), filer.OverwriteIfExists) diff --git a/internal/build/README.md b/internal/build/README.md new file mode 100644 index 00000000000..913ae7e5f4e --- /dev/null +++ b/internal/build/README.md @@ -0,0 +1,69 @@ +# CLI Compatibility Manifest + +`cli-compat.json` maps Databricks CLI versions to compatible AppKit and Agent Skills versions. The CLI uses this manifest to determine which template version to use for `apps init` and which skills version to use for `aitools install`. + +## Manifest format + +```json +{ + "0.296.0": { "appkit": "0.27.0", "skills": "0.1.5" }, + "0.290.0": { "appkit": "0.24.0", "skills": "0.1.4" }, + "0.280.0": { "appkit": "0.20.0", "skills": "0.1.0" } +} +``` + +Each key is a CLI version in semver format. Each entry defines a **range floor**: it applies to that CLI version and all versions above it, up to (but not including) the next entry. The manifest should be **sparse** — not every CLI version needs its own entry. Only add a new entry when a compatibility boundary changes. + +For example, with the manifest above: +- CLI `0.285.0` → uses `0.280.0` entry (appkit `0.20.0`) +- CLI `0.293.0` → uses `0.290.0` entry (appkit `0.24.0`) +- CLI `0.300.0` → uses `0.296.0` entry (appkit `0.27.0`, highest versioned) +- CLI `0.0.0-dev+abc` → uses `0.296.0` entry (dev builds use the highest versioned entry) + +## How the CLI resolves versions + +1. **Dev builds** (`0.0.0-dev*`) → use the highest versioned entry. +2. **Exact match** on CLI version → use that entry. +3. **No exact match**, between two entries → use the nearest lower version's entry. +4. **Newer than all entries** → use the highest versioned entry. +5. **Older than all entries** → use the lowest (oldest) entry. + +## Manifest sources (fallback chain) + +At runtime, the CLI resolves the manifest from four sources: + +1. **Local cache** (`~/.cache/databricks/compat-manifest.json`) — used if fresh (< 1 hour old). +2. **Remote fetch** from GitHub — used when cache is stale or missing. On success, the local cache is updated. +3. **Stale local cache** — if remote fetch fails but a previously cached file exists (even if expired), it is used as-is. +4. **Embedded manifest** — compiled into the binary via `go:embed`. Used as last resort when both remote and local cache fail. + +Set `DATABRICKS_FORCE_EMBEDDED_COMPAT=true` to skip all tiers and use only the embedded manifest. This is useful for local development when testing with a locally compiled binary that has a modified `cli-compat.json`. + +## When to update + +The goal is to **keep the manifest sparse** — only add entries at compatibility boundaries. After each AppKit or Agent Skills release: + +1. **Run evals** on the new AppKit version. If there is no regression, proceed. +2. **Open a PR** to update `cli-compat.json`. The change depends on the type of release: + - **No breaking changes** (the new AppKit/skills version works with all existing CLI versions): update the existing highest versioned entry's appkit/skills values in-place. Do NOT add a new versioned key. All CLI versions in that range automatically pick up the new versions. + - **Breaking changes** (the new AppKit templates require specific `apps init` features, or the new skills version requires CLI commands that older CLIs lack): add a new entry keyed to the **minimum CLI version** that supports the new features. Older entries keep their previous appkit/skills values so older CLI binaries stay compatible. + +This process is manual for now but can be automated as part of the release workflow in the future. Use the `/bump-cli-compat` Claude Code skill to automate the update and PR creation. + +## Validation + +The manifest is validated by Go tests in `libs/clicompat/`: + +```bash +go test ./libs/clicompat/... -run TestEmbeddedManifest -v +``` + +This checks: valid JSON, at least one entry, valid semver keys, valid semver entry values, and ascending key order. + +## Pruning policy + +Entries MUST NOT be removed from the manifest. Older CLI binaries use the lowest entry as their floor when the CLI version is older than all entries. Pruning it causes them to silently resolve to a newer entry that may require CLI features they lack. If the manifest grows too large, consider archiving very old entries to a separate file while keeping the oldest entry as a sentinel. + +## Trust model + +The live manifest is fetched over HTTPS from GitHub (`raw.githubusercontent.com`). The trust boundary is: TLS certificate validation + GitHub's access controls + write access to the `main` branch of `databricks/cli`. A compromised manifest can only steer clients to existing published tags (AppKit or skills); it cannot inject arbitrary code. The CLI binary always ships an embedded fallback manifest that limits exposure to cache-TTL windows (1 hour). The local cache (`~/.cache/databricks/compat-manifest.json`) is trust-on-disk: an attacker with user-level write access to the cache directory could swap in a malicious manifest pointing to different tags. diff --git a/internal/build/cli-compat.json b/internal/build/cli-compat.json new file mode 100644 index 00000000000..8458180432d --- /dev/null +++ b/internal/build/cli-compat.json @@ -0,0 +1,4 @@ +{ + "1.0.0": { "appkit": "0.38.0", "skills": "0.1.5" }, + "0.300.0": { "appkit": "0.24.0", "skills": "0.1.5" } +} diff --git a/internal/build/clicompat.go b/internal/build/clicompat.go new file mode 100644 index 00000000000..22cb8f13a03 --- /dev/null +++ b/internal/build/clicompat.go @@ -0,0 +1,9 @@ +package build + +import _ "embed" + +// CLICompatManifestJSON is the cli-compat.json manifest embedded at compile time. +// Used as the last-resort fallback when both remote fetch and local cache fail. +// +//go:embed cli-compat.json +var CLICompatManifestJSON []byte diff --git a/internal/genkit/tagging.py b/internal/genkit/tagging.py index 206ff6c7447..a3897bbeedf 100644 --- a/internal/genkit/tagging.py +++ b/internal/genkit/tagging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 - # /// script # dependencies = ["PyGithub>=2,<3", "pyjwt<2.12.0", "charset-normalizer<3.4.6"] # /// @@ -7,8 +6,8 @@ import os import re import argparse -from typing import Optional, List, Callable -from dataclasses import dataclass +from typing import Optional, List, Callable, Dict +from dataclasses import dataclass, replace import subprocess import time import json @@ -19,6 +18,7 @@ CHANGELOG_FILE_NAME = "CHANGELOG.md" PACKAGE_FILE_NAME = ".package.json" CODEGEN_FILE_NAME = ".codegen.json" +CREATED_TAGS_FILE_NAME = "created_tags.json" """ This script tags the release of the SDKs using a combination of the GitHub API and Git commands. It reads the local repository to determine necessary changes, updates changelogs, and creates tags. @@ -29,6 +29,122 @@ """ +@dataclass(frozen=True) +class Version: + """ + A semver 2.0.0-compliant version (https://semver.org). + + Mirrors the API of the `semver` PyPI package so this implementation can be + swapped for that library if it is ever added to the wheelhouse. Supports + parsing, stringification, and the two bumps we need: minor (for stable + releases) and prerelease (for release trains). + """ + + # Permissive pattern for locating a semver version string inside larger + # text (e.g. a changelog header). Callers use it in f-strings; strict + # validation happens via Version.parse. + PATTERN = r"\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?" + + # Strict anchored regex per https://semver.org. Rejects leading zeros in + # numeric identifiers and invalid pre-release/build identifier charsets. + _PARSE_REGEX = re.compile( + r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" + r"(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)" + r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + r"(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$" + ) + + major: int + minor: int + patch: int + prerelease: str = "" + build: str = "" + + @classmethod + def parse(cls, text: str) -> "Version": + """Parse a semver string, raising ValueError on malformed input.""" + match = cls._PARSE_REGEX.match(text) + if not match: + raise ValueError(f"Invalid semver version: {text!r}") + major, minor, patch, prerelease, build = match.groups() + return cls( + major=int(major), + minor=int(minor), + patch=int(patch), + prerelease=prerelease or "", + build=build or "", + ) + + def __str__(self) -> str: + result = f"{self.major}.{self.minor}.{self.patch}" + if self.prerelease: + result += f"-{self.prerelease}" + if self.build: + result += f"+{self.build}" + return result + + def bump_minor(self) -> "Version": + """ + Bump the minor version and reset patch. + + Per semver item 9, a pre-release version has lower precedence than + the same MAJOR.MINOR.PATCH, so bumping to a new minor drops any + pre-release and build metadata. + """ + return Version(major=self.major, minor=self.minor + 1, patch=0) + + def bump_prerelease(self) -> "Version": + """ + Increment the rightmost numeric identifier in the pre-release. + + Matches the npm `prerelease` bump semantics: + 0.0.0-alpha.1 -> 0.0.0-alpha.2 + 0.0.0-alpha -> 0.0.0-alpha.1 + 0.0.0-rc.1.2 -> 0.0.0-rc.1.3 + + Raises ValueError if the version has no pre-release to bump. + Build metadata is dropped since it does not affect precedence. + """ + if not self.prerelease: + raise ValueError(f"Cannot bump prerelease of {self}: no pre-release component") + parts = self.prerelease.split(".") + for i in range(len(parts) - 1, -1, -1): + if parts[i].isdigit(): + parts[i] = str(int(parts[i]) + 1) + return replace(self, prerelease=".".join(parts), build="") + # No numeric identifier exists; append ".1" to start a counter. + return replace(self, prerelease=f"{self.prerelease}.1", build="") + + def next_release_version(self) -> "Version": + """ + Default next version for the changelog after this one is released. + + If on a pre-release track, stay on it by bumping the pre-release + identifier (npm convention). Otherwise, bump the minor version, + the script's historical default for stable releases. Teams can + override the default in the release PR. + """ + if self.prerelease: + return self.bump_prerelease() + return self.bump_minor() + + +def _read_local_head_sha() -> str: + """ + Returns the SHA of the local working tree's HEAD via ``git rev-parse``. + """ + return subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() + + +class MainAdvancedError(Exception): + """ + Raised when ``origin/main`` has advanced since the workflow's + checkout — i.e., another commit landed during this run. The local + working tree is now stale, so any commit produced from it would + silently revert whatever the concurrent push added. + """ + + # GitHub does not support signing commits for GitHub Apps directly. # This class replaces usages for git commands such as "git add", "git commit", and "git push". @dataclass @@ -37,8 +153,12 @@ def __init__(self, repo: Repository): self.repo = repo self.changed_files: list[InputGitTreeElement] = [] self.ref = "heads/main" - head_ref = self.repo.get_git_ref(self.ref) - self.sha = head_ref.object.sha + # Anchor ``self.sha`` to the **local checkout** rather than a + # live API call. ``actions/checkout`` populates the working tree + # at this SHA, and every subsequent file read in this run is + # against that tree; the API HEAD is only relevant when we go + # to push. + self.sha = _read_local_head_sha() # Replaces "git add file" def add_file(self, loc: str, content: str): @@ -51,12 +171,19 @@ def add_file(self, loc: str, content: str): # Replaces "git commit && git push" def commit_and_push(self, message: str): head_ref = self.repo.get_git_ref(self.ref) + if head_ref.object.sha != self.sha: + raise MainAdvancedError( + f"origin/main advanced from {self.sha} to {head_ref.object.sha} " + f"during this run. Local working tree is stale; aborting before " + f"the commit would silently revert the new content. Re-run the " + f"workflow." + ) base_tree = self.repo.get_git_tree(sha=head_ref.object.sha) new_tree = self.repo.create_git_tree(self.changed_files, base_tree) parent_commit = self.repo.get_git_commit(head_ref.object.sha) new_commit = self.repo.create_git_commit(message=message, tree=new_tree, parents=[parent_commit]) - # Update branch reference + # Update branch reference. head_ref.edit(new_commit.sha) self.sha = new_commit.sha @@ -65,8 +192,7 @@ def reset(self, sha: Optional[str] = None): if sha: self.sha = sha else: - head_ref = self.repo.get_git_ref(self.ref) - self.sha = head_ref.object.sha + self.sha = _read_local_head_sha() def tag(self, tag_name: str, tag_message: str): # Create a tag pointing to the new commit @@ -154,35 +280,103 @@ def get_package_name(package_path: str) -> str: return "" -def update_version_references(tag_info: TagInfo) -> None: +def stage_version_updates(tag_infos: List[TagInfo], packages: List[Package]) -> None: """ - Updates the version of the package in code references. - Code references are defined in .package.json files. + Stages all version-related edits for the release in a single pass over + every package the workspace already opts in via ``.package.json``. """ - # Load version patterns from '.codegen.json' file at the top level of the repository + # Load patterns from '.codegen.json' at the top level of the repository. package_file_path = os.path.join(os.getcwd(), CODEGEN_FILE_NAME) with open(package_file_path, "r") as file: - package_file = json.load(file) + codegen = json.load(file) + + version_patterns = codegen.get("version", {}) + dep_patterns = codegen.get("dependency_pattern", {}) + name_template = codegen.get("dependency_name_template", "") - version = package_file.get("version") - if not version: - print("`version` not found in .codegen.json. Nothing to update.") + if not version_patterns and not dep_patterns: + print("Neither `version` nor `dependency_pattern` found in .codegen.json. Nothing to update.") return - # Update the versions - for filename, pattern in version.items(): - loc = os.path.join(os.getcwd(), tag_info.package.path, filename) - previous_version = re.sub(r"\$VERSION", r"\\d+\\.\\d+\\.\\d+", pattern) - new_version = re.sub(r"\$VERSION", tag_info.version, pattern) + bumped_by_dir: Dict[str, TagInfo] = {info.package.path: info for info in tag_infos} + new_dep_versions = compute_dependency_rewrites(tag_infos, name_template) + + files = sorted(set(version_patterns.keys()) | set(dep_patterns.keys())) - with open(loc, "r") as file: - content = file.read() + for pkg in packages: + for filename in files: + loc = os.path.join(os.getcwd(), pkg.path, filename) - # Replace the version in the file content - updated_content = re.sub(previous_version, new_version, content) + with open(loc, "r") as file: + content = file.read() + original = content - gh.add_file(loc, updated_content) + # Own version (only when this package is being released and the + # file has a version pattern declared). + info = bumped_by_dir.get(pkg.path) + if info is not None and filename in version_patterns: + pattern = version_patterns[filename] + previous_version = pattern.replace("$VERSION", Version.PATTERN) + new_version = pattern.replace("$VERSION", info.version) + content = re.sub(previous_version, new_version, content) + + # Sibling dependency rewrites (only when the file has a + # dependency pattern and there is at least one bumped sibling). + if filename in dep_patterns and new_dep_versions: + content = rewrite_dependencies(content, dep_patterns[filename], new_dep_versions) + + if content != original: + gh.add_file(loc, content) + + +def compute_dependency_rewrites( + tag_infos: List[TagInfo], + name_template: str, +) -> Dict[str, str]: + """ + Returns a map of dependency-name to the new semver string for each + bumped package. + """ + if not name_template: + return {} + rewrites: Dict[str, str] = {} + for info in tag_infos: + # Skip legacy releases that don't have a per-package name; their + # tag_info has an empty package.name and they can't be referenced + # as a sibling dep anyway. + if not info.package.name: + continue + dep_name = name_template.replace("$PACKAGE", info.package.name) + rewrites[dep_name] = info.version + return rewrites + + +def rewrite_dependencies(content: str, pattern: str, new_versions: Dict[str, str]) -> str: + """ + Apply ``pattern`` (with ``$DEPENDENCY`` and ``$VERSION`` placeholders) to + rewrite every entry in ``content`` whose dependency name appears in + ``new_versions``. + """ + # Sentinel strings used to protect the placeholders through re.escape: + # we substitute them in, escape the whole template, then swap them out + # for the dep-name literal and Version.PATTERN. Control characters so + # they can't collide with anything in real .codegen.json patterns. + dep_sentinel = "\x01DEPENDENCY\x01" + ver_sentinel = "\x01VERSION\x01" + + for dep_name, new_value in new_versions.items(): + regex = pattern.replace("$DEPENDENCY", dep_sentinel).replace("$VERSION", ver_sentinel) + regex = re.escape(regex) + regex = regex.replace(re.escape(dep_sentinel), re.escape(dep_name)) + regex = regex.replace(re.escape(ver_sentinel), Version.PATTERN) + + # Build the literal replacement text by substituting the same + # placeholders directly. A lambda is used instead of a string to + # avoid re.sub interpreting \1, \g<...>, etc. inside the value. + replacement_text = pattern.replace("$DEPENDENCY", dep_name).replace("$VERSION", new_value) + content = re.sub(regex, lambda _m, text=replacement_text: text, content) + return content def clean_next_changelog(package_path: str) -> None: @@ -197,23 +391,21 @@ def clean_next_changelog(package_path: str) -> None: with open(file_path, "r") as file: content = file.read() - # Remove content between ### sections + # Remove content between ### sections. cleaned_content = re.sub(r"(### [^\n]+\n)(?:.*?\n?)*?(?=###|$)", r"\1", content) - # Ensure there is exactly one empty line before each section + # Ensure there is exactly one empty line before each section. cleaned_content = re.sub(r"(\n*)(###[^\n]+)", r"\n\n\2", cleaned_content) - # Find the version number - version_match = re.search(r"Release v(\d+)\.(\d+)\.(\d+)", cleaned_content) + # Find the version number and compute the default next release version. + # Teams can adjust the version in the PR if the default is not desired. + # For stable versions, bump minor (historical default since minor releases + # are more common than patch or major). For pre-release versions, stay on + # the same track by bumping the pre-release identifier (npm convention). + version_match = re.search(rf"Release v({Version.PATTERN})", cleaned_content) if not version_match: raise Exception("Version not found in the changelog") - major, minor, patch = map(int, version_match.groups()) - # Prepare next release version. - # When doing a PR, teams can adjust the version. - # By default, we increase a minor version, since minor versions releases - # are more common than patch or major version releases. - minor += 1 - patch = 0 - new_version = f"Release v{major}.{minor}.{patch}" - cleaned_content = cleaned_content.replace(version_match.group(0), new_version) + current = Version.parse(version_match.group(1)) + new_header = f"Release v{current.next_release_version()}" + cleaned_content = cleaned_content.replace(version_match.group(0), new_header) # Update file with cleaned content gh.add_file(file_path, cleaned_content) @@ -229,20 +421,38 @@ def get_previous_tag_info(package: Package) -> Optional[TagInfo]: with open(changelog_path, "r") as f: changelog = f.read() - # Extract the latest release section using regex - match = re.search(r"## (\[Release\] )?Release v[\d\.]+.*?(?=\n## (\[Release\] )?Release v|\Z)", changelog, re.S) + # Extract the latest release section using regex. + match = re.search( + rf"## (\[Release\] )?Release v{Version.PATTERN}.*?(?=\n## (\[Release\] )?Release v|\Z)", + changelog, + re.S, + ) # E.g., for new packages. if not match: return None latest_release = match.group(0) - version_match = re.search(r"## (\[Release\] )?Release v(\d+\.\d+\.\d+)", latest_release) + version_match = re.search(rf"## (\[Release\] )?Release v({Version.PATTERN})", latest_release) if not version_match: raise Exception("Version not found in the changelog") - return TagInfo(package=package, version=version_match.group(2), content=latest_release) + # Validate the extracted string is spec-compliant; fail loudly otherwise. + version = str(Version.parse(version_match.group(2))) + return TagInfo(package=package, version=version, content=latest_release) + + +def _load_codegen_config() -> Dict: + """ + Loads ``.codegen.json`` from the repo root. Returns an empty dict when + the file is missing. + """ + package_file_path = os.path.join(os.getcwd(), CODEGEN_FILE_NAME) + if not os.path.exists(package_file_path): + return {} + with open(package_file_path, "r") as file: + return json.load(file) def get_next_tag_info(package: Package) -> Optional[TagInfo]: @@ -263,16 +473,22 @@ def get_next_tag_info(package: Package) -> Optional[TagInfo]: # Ensure there is exactly one empty line before each section next_changelog = re.sub(r"(\n*)(###[^\n]+)", r"\n\n\2", next_changelog) - if not re.search(r"###", next_changelog): + # By default, packages whose NEXT_CHANGELOG.md has no populated + # sections are skipped — there's nothing meaningful to release. + # Repos like sdk-js which are still in development can opt in + # by setting ``allow_empty_changelog: true`` in .codegen.json. + if not re.search(r"###", next_changelog) and not _load_codegen_config().get("allow_empty_changelog", False): print("All sections are empty. No changes will be made to the changelog.") return None - version_match = re.search(r"## Release v(\d+\.\d+\.\d+)", next_changelog) + version_match = re.search(rf"## Release v({Version.PATTERN})", next_changelog) if not version_match: raise Exception("Version not found in the changelog") - return TagInfo(package=package, version=version_match.group(1), content=next_changelog) + # Validate the extracted string is spec-compliant; fail loudly otherwise. + version = str(Version.parse(version_match.group(1))) + return TagInfo(package=package, version=version, content=next_changelog) def write_changelog(tag_info: TagInfo) -> None: @@ -283,10 +499,12 @@ def write_changelog(tag_info: TagInfo) -> None: with open(changelog_path, "r") as f: changelog = f.read() - # Add current date to the release header + # Add current date to the release header. current_date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d") content_with_date = re.sub( - r"## Release v(\d+\.\d+\.\d+)", rf"## Release v\1 ({current_date})", tag_info.content.strip() + rf"## Release v({Version.PATTERN})", + rf"## Release v\1 ({current_date})", + tag_info.content.strip(), ) updated_changelog = re.sub(r"(# Version changelog\n\n)", f"\\1{content_with_date}\n\n\n", changelog) @@ -295,9 +513,8 @@ def write_changelog(tag_info: TagInfo) -> None: def process_package(package: Package) -> TagInfo: """ - Processes a package + Processes a package's changelog scaffolding for the release. """ - # Prepare tag_info from NEXT_CHANGELOG.md print(f"Processing package {package.name}") tag_info = get_next_tag_info(package) @@ -307,7 +524,6 @@ def process_package(package: Package) -> TagInfo: write_changelog(tag_info) clean_next_changelog(package.path) - update_version_references(tag_info) return tag_info @@ -351,6 +567,131 @@ def is_tag_applied(tag: TagInfo) -> bool: raise Exception(f"Git command failed: {e.stderr.strip() or e}") from e +def find_last_release_tag(package: Package) -> Optional[str]: + """ + Returns the most recent ``/v*`` tag in the repository, or + ``None`` if no such tag exists. Tags are sorted by semver ordering + (``--sort=-v:refname``) so pre-releases sort below their stable + counterparts. + + :raises Exception: If the git command fails. + """ + pattern = f"{package.name}/v*" if package.name else "v*" + try: + output = subprocess.check_output( + ["git", "tag", "--list", pattern, "--sort=-v:refname"], + stderr=subprocess.PIPE, + text=True, + ).strip() + except subprocess.CalledProcessError as e: + raise Exception(f"Git command failed: {e.stderr.strip() or e}") from e + if not output: + return None + return output.split("\n")[0].strip() + + +def has_commits_since_tag(tag: str, path: str) -> bool: + """ + Returns True iff at least one commit reachable from HEAD but not from + ``tag`` touches ``path``. Used to detect that a sibling dependency has + unreleased changes that would ship stale if we tagged a dependent + without re-tagging the dependency. + + :raises Exception: If the git command fails. + """ + args = ["git", "log", "--oneline", f"{tag}..HEAD", "--", path or "."] + try: + output = subprocess.check_output(args, stderr=subprocess.PIPE, text=True).strip() + except subprocess.CalledProcessError as e: + raise Exception(f"Git command failed: {e.stderr.strip() or e}") from e + return bool(output) + + +def check_dependency_freshness(tag_infos: List[TagInfo], all_packages: List[Package]) -> None: + """ + Hard-fails when a package being released depends on a sibling package + that has unreleased commits since its last tag. + + Why: dependency rewrites (``stage_version_updates``) only fire for + siblings that are *also* being released. Without this check, releasing + package_a alone — when package_b has commits since its last tag — + publishes ``package_a@new`` pinning the *old* package_b artifact, which + won't have the changes package_a's source depends on. The check is + commit-based (not changelog-based) so a missing ``NEXT_CHANGELOG.md`` + entry on package_b is still caught. + + No-op when ``.codegen.json`` declares no dependency pattern (legacy + SDKs without per-package wiring). + """ + if not tag_infos: + return + + package_file_path = os.path.join(os.getcwd(), CODEGEN_FILE_NAME) + with open(package_file_path, "r") as file: + codegen = json.load(file) + + name_template = codegen.get("dependency_name_template", "") + dep_patterns = codegen.get("dependency_pattern", {}) + if not name_template or not dep_patterns: + return + + releasing_paths = {info.package.path for info in tag_infos} + by_dep_name: Dict[str, Package] = {} + for pkg in all_packages: + if not pkg.name: + continue + by_dep_name[name_template.replace("$PACKAGE", pkg.name)] = pkg + + issues: List[str] = [] + for info in tag_infos: + for filename, pattern in dep_patterns.items(): + loc = os.path.join(os.getcwd(), info.package.path, filename) + if not os.path.exists(loc): + continue + with open(loc, "r") as f: + content = f.read() + + for dep_name, dep_pkg in by_dep_name.items(): + if dep_pkg.path == info.package.path: + continue + if dep_pkg.path in releasing_paths: + continue + + # Same regex construction used by ``rewrite_dependencies``, + # so "is this dep referenced?" matches "would the rewrite + # touch it?". Keeps the two in lockstep. + regex = ( + re.escape(pattern) + .replace(re.escape("$DEPENDENCY"), re.escape(dep_name)) + .replace(re.escape("$VERSION"), Version.PATTERN) + ) + if not re.search(regex, content): + continue + + last_tag = find_last_release_tag(dep_pkg) + if last_tag is None: + # No prior tag means the dep was never released; we + # can't reason about staleness. Surface it anyway so + # the human resolves it explicitly. + issues.append( + f"{info.package.name} depends on {dep_pkg.name}, " + f"which has never been released. Release " + f"{dep_pkg.name} first or include it in this run." + ) + continue + if has_commits_since_tag(last_tag, dep_pkg.path): + issues.append( + f"{info.package.name} depends on {dep_pkg.name}, " + f"which has commits since {last_tag} but is not " + f"being released. Either release {dep_pkg.name} " + f"as well, or hold this release until its changes " + f"are reverted." + ) + + if issues: + raise Exception("Dependency freshness check failed:\n - " + "\n - ".join(issues)) + + def find_last_tags() -> List[TagInfo]: """ Finds the last tags for each package. @@ -384,11 +725,11 @@ def generate_commit_message(tag_infos: List[TagInfo]) -> str: raise Exception("Multiple packages found in legacy mode") return f"[Release] Release v{info.version}\n\n{info.content}" - # Sort tag_infos by package name for consistency + # Sort tag_infos by package name for consistency. tag_infos.sort(key=lambda info: info.package.name) - return "Release\n\n" + "\n\n".join( - f"## {info.package.name}/v{info.version}\n\n{info.content}" for info in tag_infos - ) + titles = ", ".join(f"{info.package.name}/v{info.version}" for info in tag_infos) + body = "\n\n".join(f"## {info.package.name}/v{info.version}\n\n{info.content}" for info in tag_infos) + return f"[Release] {titles}\n\n{body}" def push_changes(tag_infos: List[TagInfo]) -> None: @@ -411,21 +752,18 @@ def reset_repository(hash: Optional[str] = None) -> None: :param hash: The commit hash to reset to. If None, it resets to HEAD. """ - # Fetch the latest changes from the remote repository + # Fetch the latest changes from the remote repository. subprocess.run(["git", "fetch"]) - # Determine the commit hash (default to origin/main if none is provided) + # Determine the commit hash (default to origin/main if none is provided). commit_hash = hash or "origin/main" - # Reset in memory changed files and the commit hash + # ``git reset --hard`` must land before ``gh.reset(None)``, since + # ``gh.reset(None)`` reads ``git rev-parse HEAD`` to anchor + # ``self.sha`` to the local working tree. + subprocess.run(["git", "reset", "--hard", commit_hash], check=True) gh.reset(hash) - # Construct the Git reset command - command = ["git", "reset", "--hard", commit_hash] - - # Execute the git reset command - subprocess.run(command, check=True) - def retry_function( func: Callable[[], List[TagInfo]], cleanup: Callable[[], None], max_attempts: int = 5, delay: int = 5 @@ -443,6 +781,12 @@ def retry_function( while attempts <= max_attempts: try: return func() # Call the function + except MainAdvancedError: + # Permanent failure: another commit landed on main during + # this run, so the local tree is stale. Retrying with the + # same stale tree would just hit the same mismatch — only + # a fresh workflow run against the new main can recover. + raise except Exception as e: attempts += 1 print(f"Attempt {attempts} failed: {e}") @@ -454,23 +798,139 @@ def retry_function( raise e # Re-raise the exception after max retries -def update_changelogs(packages: List[Package]) -> List[TagInfo]: +def update_changelogs(selected_packages: List[Package], all_packages: List[Package]) -> List[TagInfo]: """ Updates changelogs and pushes the commits. + + ``selected_packages`` are the packages whose ``NEXT_CHANGELOG.md`` is + consulted to decide what gets released this run. ``all_packages`` is + the full repo inventory used for cross-package dep rewrites. + + The freshness check is deliberately *not* called here. ``process`` + runs it before entering the retry loop so a freshness violation + fails fast — the check is deterministic against the same git state, + so wrapping it in retry would just delay the same failure five + times. """ - tag_infos = [info for info in (process_package(package) for package in packages) if info is not None] - # If any package was changed, push the changes. + tag_infos = [info for info in (process_package(package) for package in selected_packages) if info is not None] + # If any package was changed, stage version updates and push. if tag_infos: + stage_version_updates(tag_infos, all_packages) push_changes(tag_infos) return tag_infos +def preview_tag_infos(packages: List[Package]) -> List[TagInfo]: + """ + Read-only sibling of ``process_package``: returns the TagInfos that + would be released for ``packages`` without writing any changelog + edits. ``process`` calls this before the retry loop so the freshness + check has a snapshot to validate against. ``process_package`` will + re-derive the same TagInfos when ``update_changelogs`` runs; the + duplication is just a couple of NEXT_CHANGELOG.md reads. + """ + return [info for info in (get_next_tag_info(package) for package in packages) if info is not None] + + +def order_tag_infos_by_dependency(tag_infos: List[TagInfo]) -> List[TagInfo]: + """ + Returns ``tag_infos`` in topological order: every package appears + after every sibling it depends on. + """ + if not tag_infos: + return list(tag_infos) + + if any(not info.package.name for info in tag_infos) and len(tag_infos) > 1: + raise Exception("Multiple packages found in legacy mode") + + package_file_path = os.path.join(os.getcwd(), CODEGEN_FILE_NAME) + with open(package_file_path, "r") as file: + codegen = json.load(file) + + name_template = codegen.get("dependency_name_template", "") + dep_patterns = codegen.get("dependency_pattern", {}) + if not name_template or not dep_patterns: + return list(tag_infos) + + by_dep_name: Dict[str, TagInfo] = { + name_template.replace("$PACKAGE", info.package.name): info for info in tag_infos if info.package.name + } + + # Adjacency: path -> set of paths it depends on (within tag_infos). + deps: Dict[str, set] = {info.package.path: set() for info in tag_infos} + for info in tag_infos: + for filename, pattern in dep_patterns.items(): + loc = os.path.join(os.getcwd(), info.package.path, filename) + if not os.path.exists(loc): + continue + with open(loc, "r") as f: + content = f.read() + for dep_name, dep_info in by_dep_name.items(): + if dep_info.package.path == info.package.path: + continue + regex = ( + re.escape(pattern) + .replace(re.escape("$DEPENDENCY"), re.escape(dep_name)) + .replace(re.escape("$VERSION"), Version.PATTERN) + ) + if re.search(regex, content): + deps[info.package.path].add(dep_info.package.path) + + # Stable topological sort: at each step, emit every node whose deps + # are already emitted, alphabetically by package name. Ties broken + # alphabetically so the manifest is reproducible across runs. + emitted: set = set() + ordered: List[TagInfo] = [] + while len(ordered) < len(tag_infos): + ready = sorted( + ( + info + for info in tag_infos + if info.package.path not in emitted and deps[info.package.path].issubset(emitted) + ), + key=lambda info: info.package.name, + ) + if not ready: + remaining = [info.package.name for info in tag_infos if info.package.path not in emitted] + raise Exception(f"Cyclic dependency detected among packages: {remaining}") + for info in ready: + ordered.append(info) + emitted.add(info.package.path) + return ordered + + def push_tags(tag_infos: List[TagInfo]) -> None: """ Creates and pushes tags to the repository. + + Tags are emitted in topological order — dependencies before + dependents — so downstream publishing pipelines reading + ``created_tags.json`` can walk it sequentially without re-deriving + the dependency graph. See ``order_tag_infos_by_dependency``. + + As a side effect, writes the names of successfully created tags to + ``./created_tags.json`` so that workflows triggering this script can + discover what was produced (the GitHub Actions workflow uploads this + file as the ``created-tags`` artifact). + + Schema: + {"tags": ["service-a/v1.2.3", "service-b/v0.4.0"]} + + The manifest is written even if tag creation fails partway through: + tags that succeeded before the failure are flushed before the + exception is re-raised, so recovery-mode runs still surface their + output. """ - for tag_info in tag_infos: - gh.tag(tag_info.tag_name(), tag_info.content) + tag_infos = order_tag_infos_by_dependency(tag_infos) + created: List[str] = [] + try: + for tag_info in tag_infos: + gh.tag(tag_info.tag_name(), tag_info.content) + created.append(tag_info.tag_name()) + finally: + manifest_path = os.path.join(os.getcwd(), CREATED_TAGS_FILE_NAME) + with open(manifest_path, "w") as f: + json.dump({"tags": created}, f) def run_command(command: List[str]) -> str: @@ -499,15 +959,26 @@ def pull_last_release_commit() -> None: reset_repository(commit_hash) -def get_package_from_args() -> Optional[str]: +def get_packages_from_args() -> List[str]: """ - Retrieves an optional package - python3 ./tagging.py --package + Retrieves the list of packages to tag. + + python3 ./tagging.py --package # single package + python3 ./tagging.py --package , # multiple packages + + Returns an empty list when --package is omitted, which means all packages + with pending releases will be tagged. """ parser = argparse.ArgumentParser(description="Update changelogs and tag the release.") - parser.add_argument("--package", "-p", type=str, help="Tag a single package") + parser.add_argument( + "--package", + "-p", + type=str, + default="", + help="Comma-separated list of packages to tag. Leave empty to tag all packages with pending releases.", + ) args = parser.parse_args() - return args.package + return [name.strip() for name in args.package.split(",") if name.strip()] def init_github(): @@ -533,15 +1004,15 @@ def process(): If any tag are pending from an early process, it will skip updating the CHANGELOG.md files and only apply the tags. """ - package_name = get_package_from_args() + package_names = get_packages_from_args() pending_tags = find_pending_tags() # pending_tags is non-empty only when the tagging process previously failed or interrupted. # We must complete the interrupted tagging process before starting a new one to avoid inconsistent states and missing changelog entries. - # Therefore, we don't support specifying the package until the previously started process has been successfully completed. - if pending_tags and package_name: + # Therefore, we don't support specifying packages until the previously started process has been successfully completed. + if pending_tags and package_names: pending_packages = [tag.package.name for tag in pending_tags] - raise Exception(f"Cannot release package {package_name}. Pending release for {pending_packages}") + raise Exception(f"Cannot release packages {package_names}. Pending release for {pending_packages}") if pending_tags: print("Found pending tags from previous executions, entering recovery mode.") @@ -549,12 +1020,24 @@ def process(): push_tags(pending_tags) return - packages = find_packages() - # If a package is specified as an argument, only process that package - if package_name: - packages = [package for package in packages if package.name == package_name] - - pending_tags = retry_function(func=lambda: update_changelogs(packages), cleanup=reset_repository) + all_packages = find_packages() + # If packages are specified as an argument, only release those — but + # dep rewrites and the freshness check still operate over the full + # set. + selected_packages = all_packages + if package_names: + selected_packages = [package for package in all_packages if package.name in package_names] + + # Run the freshness check against a read-only preview before the + # retry loop, since the check is deterministic. A freshness + # violation fails the run immediately, with no commits, no tags, no + # retry storm. + check_dependency_freshness(preview_tag_infos(selected_packages), all_packages) + + pending_tags = retry_function( + func=lambda: update_changelogs(selected_packages, all_packages), + cleanup=reset_repository, + ) push_tags(pending_tags) diff --git a/internal/genkit/tagging.py.lock b/internal/genkit/tagging.py.lock index d680b187f5b..2bd746a66c2 100644 --- a/internal/genkit/tagging.py.lock +++ b/internal/genkit/tagging.py.lock @@ -12,7 +12,7 @@ requirements = [ [[package]] name = "certifi" version = "2026.2.25" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "cffi" version = "2.0.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pycparser", marker = "implementation_name != 'PyPy'" }, ] @@ -78,7 +78,7 @@ wheels = [ [[package]] name = "charset-normalizer" version = "3.4.5" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/1d/35/02daf95b9cd686320bb622eb148792655c9412dbb9b67abb5694e5910a24/charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644", size = 134804, upload-time = "2026-03-06T06:03:19.46Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/9c/b6/9ee9c1a608916ca5feae81a344dffbaa53b26b90be58cc2159e3332d44ec/charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade", size = 280976, upload-time = "2026-03-06T06:01:15.276Z" }, @@ -135,7 +135,7 @@ wheels = [ [[package]] name = "cryptography" version = "46.0.5" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -188,7 +188,7 @@ wheels = [ [[package]] name = "idna" version = "3.11" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, @@ -197,7 +197,7 @@ wheels = [ [[package]] name = "pycparser" version = "3.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, @@ -206,7 +206,7 @@ wheels = [ [[package]] name = "pygithub" version = "2.8.1" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "pyjwt", extra = ["crypto"] }, { name = "pynacl" }, @@ -222,7 +222,7 @@ wheels = [ [[package]] name = "pyjwt" version = "2.11.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, @@ -236,7 +236,7 @@ crypto = [ [[package]] name = "pynacl" version = "1.6.2" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] @@ -271,7 +271,7 @@ wheels = [ [[package]] name = "requests" version = "2.32.5" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } dependencies = [ { name = "certifi" }, { name = "charset-normalizer" }, @@ -286,7 +286,7 @@ wheels = [ [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, @@ -295,7 +295,7 @@ wheels = [ [[package]] name = "urllib3" version = "2.6.3" -source = { registry = "https://pypi-proxy.dev.databricks.com/simple/" } +source = { registry = "https://pypi-proxy.cloud.databricks.com/simple/" } sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, diff --git a/internal/mocks/.gitattributes b/internal/mocks/.gitattributes new file mode 100644 index 00000000000..80ff44c834c --- /dev/null +++ b/internal/mocks/.gitattributes @@ -0,0 +1 @@ +*.go linguist-generated=true diff --git a/internal/testutil/jdk.go b/internal/testutil/jdk.go deleted file mode 100644 index 6c32777190e..00000000000 --- a/internal/testutil/jdk.go +++ /dev/null @@ -1,44 +0,0 @@ -package testutil - -import ( - "context" - "os/exec" - "strings" -) - -// HasJDK checks if the specified Java version is available in the system. -// It returns true if the required JDK version is present, false otherwise. -// This is a non-failing variant of RequireJDK. -// -// Example output of `java -version` in eclipse-temurin:8: -// openjdk version "1.8.0_442" -// OpenJDK Runtime Environment (Temurin)(build 1.8.0_442-b06) -// OpenJDK 64-Bit Server VM (Temurin)(build 25.442-b06, mixed mode) -// -// Example output of `java -version` in java11 (homebrew): -// openjdk version "11.0.26" 2025-01-21 -// OpenJDK Runtime Environment Homebrew (build 11.0.26+0) -// OpenJDK 64-Bit Server VM Homebrew (build 11.0.26+0, mixed mode) -func HasJDK(t TestingT, ctx context.Context, version string) bool { - t.Helper() - - // Try to execute "java -version" command - cmd := exec.CommandContext(ctx, "java", "-version") - output, err := cmd.CombinedOutput() - if err != nil { - t.Logf("Failed to execute java -version: %v", err) - return false - } - - javaVersionOutput := string(output) - - // Check if the output contains the expected version - expectedVersionString := "version \"" + version - if strings.Contains(javaVersionOutput, expectedVersionString) { - t.Logf("Detected JDK version %s", version) - return true - } - - t.Logf("Required JDK version %s not found, instead got: %s", version, javaVersionOutput) - return false -} diff --git a/libs/agent/agent_notice.go b/libs/agent/agent_notice.go new file mode 100644 index 00000000000..22f63b9d5de --- /dev/null +++ b/libs/agent/agent_notice.go @@ -0,0 +1,20 @@ +package agent + +import ( + "fmt" + + "github.com/databricks/databricks-sdk-go/useragent" +) + +// AgentNotice returns a notice string to append to error messages when an AI +// agent is detected. Returns an empty string for non-agent callers. +func AgentNotice() string { + name := useragent.AgentProvider() + if name == "" { + return "" + } + return fmt.Sprintf("\n\nNote for AI agents (%s): do not retry with the flag suggested above\n"+ + "unless the user has explicitly approved it. The flag bypasses a safety check\n"+ + "and the operation may be irreversible.", + name) +} diff --git a/experimental/aitools/lib/agents/agents.go b/libs/aitools/agents/agents.go similarity index 100% rename from experimental/aitools/lib/agents/agents.go rename to libs/aitools/agents/agents.go diff --git a/experimental/aitools/lib/agents/recommend.go b/libs/aitools/agents/recommend.go similarity index 90% rename from experimental/aitools/lib/agents/recommend.go rename to libs/aitools/agents/recommend.go index bf10c67bfd9..de906752316 100644 --- a/experimental/aitools/lib/agents/recommend.go +++ b/libs/aitools/agents/recommend.go @@ -15,7 +15,7 @@ func RecommendSkillsInstall(ctx context.Context, installFn func(context.Context) } if !cmdio.IsPromptSupported(ctx) { - cmdio.LogString(ctx, "Tip: coding agents detected without Databricks skills. Run 'databricks experimental aitools skills install' to install them.") + cmdio.LogString(ctx, "Tip: coding agents detected without Databricks skills. Run 'databricks aitools install' to install them.") return nil } diff --git a/experimental/aitools/lib/agents/recommend_test.go b/libs/aitools/agents/recommend_test.go similarity index 91% rename from experimental/aitools/lib/agents/recommend_test.go rename to libs/aitools/agents/recommend_test.go index c2c27699212..ede6d4eed12 100644 --- a/experimental/aitools/lib/agents/recommend_test.go +++ b/libs/aitools/agents/recommend_test.go @@ -17,6 +17,7 @@ func noopInstall(context.Context) error { return nil } func TestRecommendSkillsInstallSkipsWhenSkillsExist(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) // Skills must be in canonical location to be detected. require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, CanonicalSkillsDir, "databricks"), 0o755)) @@ -39,7 +40,9 @@ func TestRecommendSkillsInstallSkipsWhenSkillsExist(t *testing.T) { } func TestRecommendSkillsInstallSkipsWhenNoAgents(t *testing.T) { - t.Setenv("HOME", t.TempDir()) + tmp := t.TempDir() + t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) origRegistry := Registry Registry = []Agent{} @@ -53,6 +56,7 @@ func TestRecommendSkillsInstallSkipsWhenNoAgents(t *testing.T) { func TestRecommendSkillsInstallNonInteractive(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) origRegistry := Registry Registry = []Agent{ @@ -67,12 +71,13 @@ func TestRecommendSkillsInstallNonInteractive(t *testing.T) { ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) err := RecommendSkillsInstall(ctx, noopInstall) require.NoError(t, err) - assert.Contains(t, stderr.String(), "databricks experimental aitools skills install") + assert.Contains(t, stderr.String(), "databricks aitools install") } func TestRecommendSkillsInstallInteractiveDecline(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) origRegistry := Registry Registry = []Agent{ diff --git a/experimental/aitools/lib/agents/skills.go b/libs/aitools/agents/skills.go similarity index 100% rename from experimental/aitools/lib/agents/skills.go rename to libs/aitools/agents/skills.go diff --git a/experimental/aitools/lib/agents/skills_test.go b/libs/aitools/agents/skills_test.go similarity index 95% rename from experimental/aitools/lib/agents/skills_test.go rename to libs/aitools/agents/skills_test.go index 09192d721c9..e7b3d57d081 100644 --- a/experimental/aitools/lib/agents/skills_test.go +++ b/libs/aitools/agents/skills_test.go @@ -21,6 +21,7 @@ func TestHasDatabricksSkillsInstalledNoAgents(t *testing.T) { func TestHasDatabricksSkillsInstalledCanonicalOnly(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) + t.Setenv("USERPROFILE", tmpHome) require.NoError(t, os.MkdirAll(filepath.Join(tmpHome, CanonicalSkillsDir, "databricks"), 0o755)) origRegistry := Registry @@ -39,6 +40,7 @@ func TestHasDatabricksSkillsInstalledCanonicalOnly(t *testing.T) { func TestHasDatabricksSkillsInstalledIgnoresAgentDir(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) + t.Setenv("USERPROFILE", tmpHome) // Skills in agent dir only (e.g., installed by another tool) should not count. agentDir := filepath.Join(tmpHome, ".claude") require.NoError(t, os.MkdirAll(filepath.Join(agentDir, "skills", "databricks"), 0o755)) @@ -59,6 +61,7 @@ func TestHasDatabricksSkillsInstalledIgnoresAgentDir(t *testing.T) { func TestHasDatabricksSkillsInstalledWithOnlyNonDatabricksSkills(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) // Non-databricks skills should not count. require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "skills", "mcp-builder"), 0o755)) require.NoError(t, os.MkdirAll(filepath.Join(tmpDir, "skills", "rust-webapp"), 0o755)) @@ -79,6 +82,7 @@ func TestHasDatabricksSkillsInstalledWithOnlyNonDatabricksSkills(t *testing.T) { func TestHasDatabricksSkillsInstalledNoSkillsDir(t *testing.T) { tmpDir := t.TempDir() t.Setenv("HOME", tmpDir) + t.Setenv("USERPROFILE", tmpDir) origRegistry := Registry Registry = []Agent{ @@ -96,6 +100,7 @@ func TestHasDatabricksSkillsInstalledNoSkillsDir(t *testing.T) { func TestHasDatabricksSkillsInstalledCustomSubdirNotChecked(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) + t.Setenv("USERPROFILE", tmpHome) // Skills in agent's custom subdir should not count — only canonical matters. require.NoError(t, os.MkdirAll(filepath.Join(tmpHome, ".gemini", "antigravity", "global_skills", "databricks"), 0o755)) @@ -116,6 +121,7 @@ func TestHasDatabricksSkillsInstalledCustomSubdirNotChecked(t *testing.T) { func TestHasDatabricksSkillsInstalledDatabricksAppsCanonical(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) + t.Setenv("USERPROFILE", tmpHome) // databricks-apps prefix should match in canonical location. require.NoError(t, os.MkdirAll(filepath.Join(tmpHome, CanonicalSkillsDir, "databricks-apps"), 0o755)) @@ -138,6 +144,7 @@ func TestHasDatabricksSkillsInstalledDatabricksAppsCanonical(t *testing.T) { func TestHasDatabricksSkillsInstalledLegacyPath(t *testing.T) { tmpHome := t.TempDir() t.Setenv("HOME", tmpHome) + t.Setenv("USERPROFILE", tmpHome) // Skills only in the legacy location should still be detected. require.NoError(t, os.MkdirAll(filepath.Join(tmpHome, legacySkillsDir, "databricks"), 0o755)) diff --git a/experimental/aitools/lib/installer/installer.go b/libs/aitools/installer/installer.go similarity index 75% rename from experimental/aitools/lib/installer/installer.go rename to libs/aitools/installer/installer.go index 982df0c1631..548be8dcf3e 100644 --- a/experimental/aitools/lib/installer/installer.go +++ b/libs/aitools/installer/installer.go @@ -14,32 +14,65 @@ import ( "strings" "time" - "github.com/databricks/cli/experimental/aitools/lib/agents" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/aitools/agents" + "github.com/databricks/cli/libs/clicompat" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" - "github.com/fatih/color" "golang.org/x/mod/semver" ) const ( - skillsRepoOwner = "databricks" - skillsRepoName = "databricks-agent-skills" - skillsRepoPath = "skills" + skillsRepoOwner = "databricks" + skillsRepoName = "databricks-agent-skills" + stableSkillsRepoPath = "skills" + experimentalRepoPath = "experimental" ) +func manifestHasExperimental(m *Manifest) bool { + for _, meta := range m.Skills { + if meta.IsExperimental() { + return true + } + } + return false +} + +func stateRepoDir(state *InstallState, name string) string { + if state != nil && state.RepoDirs != nil { + if repoDir := state.RepoDirs[name]; repoDir != "" { + return repoDir + } + } + return stableSkillsRepoPath +} + // fetchFileFn is the function used to download individual skill files. // It is a package-level var so tests can replace it with a mock. -var fetchFileFn = fetchSkillFile +var fetchFileFn func(ctx context.Context, ref, repoDir, skillName, filePath string) ([]byte, error) = fetchSkillFile -// GetSkillsRef returns the skills repo ref to use. If DATABRICKS_SKILLS_REF -// is set, it returns that value; otherwise it returns the default ref. -func GetSkillsRef(ctx context.Context) string { +// GetSkillsRef returns the skills repo ref to use and whether it was explicitly +// set via DATABRICKS_SKILLS_REF (as opposed to auto-resolved from the manifest). +// Resolution order: DATABRICKS_SKILLS_REF env var → compatibility manifest → error. +func GetSkillsRef(ctx context.Context) (ref string, explicit bool, err error) { if ref := env.Get(ctx, "DATABRICKS_SKILLS_REF"); ref != "" { - return ref + return ref, true, nil + } + v, err := clicompat.ResolveAgentSkillsVersion(ctx) + if err != nil { + return "", false, fmt.Errorf("could not resolve skills version: %w", err) + } + return "v" + v, false, nil +} + +// GetSkillsBaseURL returns the manifest and skill fetch base URL. +// DATABRICKS_SKILLS_BASE_URL overrides GitHub raw URLs for acceptance tests. +func GetSkillsBaseURL(ctx context.Context) string { + if base := env.Get(ctx, "DATABRICKS_SKILLS_BASE_URL"); base != "" { + return strings.TrimRight(base, "/") } - return defaultSkillsRepoRef + return "https://raw.githubusercontent.com/" + skillsRepoOwner + "/" + skillsRepoName } // Manifest describes the skills manifest fetched from the skills repo. @@ -51,12 +84,22 @@ type Manifest struct { // SkillMeta describes a single skill entry in the manifest. type SkillMeta struct { - Version string `json:"version"` - UpdatedAt string `json:"updated_at"` - Files []string `json:"files"` - Experimental bool `json:"experimental,omitempty"` - Description string `json:"description,omitempty"` - MinCLIVer string `json:"min_cli_version,omitempty"` + Version string `json:"version"` + UpdatedAt string `json:"updated_at"` + Files []string `json:"files"` + Description string `json:"description,omitempty"` + MinCLIVer string `json:"min_cli_version,omitempty"` + + // RepoDir is "skills" or "experimental" (manifest field repo_dir). + RepoDir string `json:"repo_dir,omitempty"` + + // SourceName is the upstream skill directory name within RepoDir. + // Set during normalization, not from JSON. + SourceName string `json:"-"` +} + +func (s SkillMeta) IsExperimental() bool { + return s.RepoDir == experimentalRepoPath } // InstallOptions controls the behavior of InstallSkillsForAgents. @@ -66,9 +109,12 @@ type InstallOptions struct { Scope string // ScopeGlobal or ScopeProject (default: global) } -func fetchSkillFile(ctx context.Context, ref, skillName, filePath string) ([]byte, error) { - url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s/%s/%s", - skillsRepoOwner, skillsRepoName, ref, skillsRepoPath, skillName, filePath) +func fetchSkillFile(ctx context.Context, ref, repoDir, skillName, filePath string) ([]byte, error) { + if repoDir == "" { + repoDir = stableSkillsRepoPath + } + url := fmt.Sprintf("%s/%s/%s/%s/%s", + GetSkillsBaseURL(ctx), ref, repoDir, skillName, filePath) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -89,15 +135,43 @@ func fetchSkillFile(ctx context.Context, ref, skillName, filePath string) ([]byt return io.ReadAll(resp.Body) } +// FetchSkillsManifestWithFallback fetches the skills manifest at the given ref. +// If the ref points to a non-existent tag (not-found error), it falls back to +// the embedded manifest's skills version. Returns the manifest, the (possibly +// updated) ref, and any error. +func FetchSkillsManifestWithFallback(ctx context.Context, src ManifestSource, ref string, allowFallback bool) (*Manifest, string, error) { + tag := strings.TrimPrefix(ref, "v") + manifest, err := src.FetchManifest(ctx, ref) + if err != nil && allowFallback && clicompat.IsNotFoundError(err) { + fallbackVersion, fbErr := clicompat.ResolveEmbeddedAgentSkillsVersion() + if fbErr == nil && fallbackVersion != "" && fallbackVersion != tag { + log.Warnf(ctx, "Skills version %s not found, falling back to embedded version %s", tag, fallbackVersion) + ref = "v" + fallbackVersion + manifest, err = src.FetchManifest(ctx, ref) + } else if fbErr != nil { + log.Warnf(ctx, "Could not resolve embedded skills version: %v", fbErr) + } + } + return manifest, ref, err +} + // InstallSkillsForAgents fetches the manifest and installs skills for the given agents. // This is the core installation function. Callers are responsible for agent detection, // prompting, and printing the "Installing..." header. func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgents []*agents.Agent, opts InstallOptions) error { - ref := GetSkillsRef(ctx) - manifest, err := src.FetchManifest(ctx, ref) + ref, explicit, err := GetSkillsRef(ctx) if err != nil { return err } + cmdio.LogString(ctx, "Using skills version "+strings.TrimPrefix(ref, "v")) + manifest, ref, err := FetchSkillsManifestWithFallback(ctx, src, ref, !explicit) + if err != nil { + return err + } + + if opts.IncludeExperimental && !manifestHasExperimental(manifest) { + log.Warnf(ctx, "--experimental was set but the manifest at %s exposes no experimental skills. Set DATABRICKS_SKILLS_REF to a release that includes them (or =main for the latest).", ref) + } scope := opts.Scope if scope == "" { @@ -135,7 +209,7 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent if state == nil && scope == ScopeGlobal { isLegacy := checkLegacyInstall(ctx, baseDir) if isLegacy && len(opts.SpecificSkills) > 0 { - return errors.New("legacy install detected without state tracking; run 'databricks experimental aitools install' (without a skill name) first to rebuild state") + return errors.New("legacy install detected without state tracking; run 'databricks aitools install' (without a skill name) first to rebuild state") } } @@ -157,9 +231,10 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent for _, name := range skillNames { meta := targetSkills[name] + // Idempotency: skip if same version is already installed, the canonical // dir exists, AND every requested agent already has the skill on disk. - if state != nil && state.Skills[name] == meta.Version { + if state != nil && state.Skills[name] == meta.Version && stateRepoDir(state, name) == meta.RepoDir { skillDir := filepath.Join(baseDir, name) if _, statErr := os.Stat(skillDir); statErr == nil && allAgentsHaveSkill(ctx, name, targetAgents, scope, cwd) { log.Debugf(ctx, "%s v%s already installed for all agents, skipping", name, meta.Version) @@ -167,7 +242,7 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent } } - if err := installSkillForAgents(ctx, name, meta.Files, targetAgents, params); err != nil { + if err := installSkillForAgents(ctx, name, meta, targetAgents, params); err != nil { return err } } @@ -178,8 +253,12 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent state = &InstallState{ SchemaVersion: 1, Skills: make(map[string]string, len(targetSkills)), + RepoDirs: make(map[string]string, len(targetSkills)), } } + if state.RepoDirs == nil { + state.RepoDirs = make(map[string]string, len(state.Skills)+len(targetSkills)) + } state.Release = ref state.LastUpdated = time.Now() // IncludeExperimental reflects the last invocation's flag value. The Skills @@ -189,17 +268,17 @@ func InstallSkillsForAgents(ctx context.Context, src ManifestSource, targetAgent state.Scope = scope for name, meta := range targetSkills { state.Skills[name] = meta.Version + state.RepoDirs[name] = meta.RepoDir } if err := SaveState(baseDir, state); err != nil { return err } - tag := strings.TrimPrefix(ref, "v") noun := "skills" if len(targetSkills) == 1 { noun = "skill" } - cmdio.LogString(ctx, fmt.Sprintf("Installed %d %s (v%s).", len(targetSkills), noun, tag)) + cmdio.LogString(ctx, fmt.Sprintf("Installed %d %s.", len(targetSkills), noun)) return nil } @@ -259,7 +338,7 @@ func resolveSkills(ctx context.Context, skills map[string]SkillMeta, opts Instal result := make(map[string]SkillMeta, len(candidates)) for name, meta := range candidates { - if meta.Experimental && !opts.IncludeExperimental { + if meta.IsExperimental() && !opts.IncludeExperimental { if isSpecific { return nil, fmt.Errorf("skill %q is experimental; use --experimental to install", name) } @@ -304,7 +383,7 @@ func PrintInstallingFor(ctx context.Context, targetAgents []*agents.Agent) { } func printNoAgentsDetected(ctx context.Context) { - cmdio.LogString(ctx, color.YellowString("No supported coding agents detected.")) + cmdio.LogString(ctx, cmdio.Yellow(ctx, "No supported coding agents detected.")) cmdio.LogString(ctx, "") cmdio.LogString(ctx, "Supported agents: Claude Code, Cursor, Codex CLI, OpenCode, GitHub Copilot, Antigravity") cmdio.LogString(ctx, "Please install at least one coding agent first.") @@ -314,7 +393,7 @@ func printNoAgentsDetected(ctx context.Context) { // Returns true if a legacy install was detected. func checkLegacyInstall(ctx context.Context, globalDir string) bool { if hasSkillsOnDisk(globalDir) { - cmdio.LogString(ctx, "Found skills installed before state tracking was added. Run 'databricks experimental aitools install' to refresh.") + cmdio.LogString(ctx, "Found skills installed before state tracking was added. Run 'databricks aitools install' to refresh.") return true } homeDir, err := env.UserHomeDir(ctx) @@ -323,7 +402,7 @@ func checkLegacyInstall(ctx context.Context, globalDir string) bool { } legacyDir := filepath.Join(homeDir, ".databricks", "agent-skills") if hasSkillsOnDisk(legacyDir) { - cmdio.LogString(ctx, "Found skills installed before state tracking was added. Run 'databricks experimental aitools install' to refresh.") + cmdio.LogString(ctx, "Found skills installed before state tracking was added. Run 'databricks aitools install' to refresh.") return true } return false @@ -366,9 +445,9 @@ type installParams struct { ref string } -func installSkillForAgents(ctx context.Context, skillName string, files []string, detectedAgents []*agents.Agent, params installParams) error { +func installSkillForAgents(ctx context.Context, skillName string, meta SkillMeta, detectedAgents []*agents.Agent, params installParams) error { canonicalDir := filepath.Join(params.baseDir, skillName) - if err := installSkillToDir(ctx, params.ref, skillName, canonicalDir, files); err != nil { + if err := installSkillToDir(ctx, params.ref, meta.RepoDir, meta.SourceName, canonicalDir, meta.Files); err != nil { return err } @@ -466,7 +545,7 @@ func backupThirdPartySkill(ctx context.Context, destDir, canonicalDir, skillName return nil } -func installSkillToDir(ctx context.Context, ref, skillName, destDir string, files []string) error { +func installSkillToDir(ctx context.Context, ref, repoDir, skillName, destDir string, files []string) error { // remove existing skill directory for clean install if err := os.RemoveAll(destDir); err != nil { return fmt.Errorf("failed to remove existing skill: %w", err) @@ -477,7 +556,7 @@ func installSkillToDir(ctx context.Context, ref, skillName, destDir string, file } for _, file := range files { - content, err := fetchFileFn(ctx, ref, skillName, file) + content, err := fetchFileFn(ctx, ref, repoDir, skillName, file) if err != nil { return err } diff --git a/experimental/aitools/lib/installer/installer_test.go b/libs/aitools/installer/installer_test.go similarity index 81% rename from experimental/aitools/lib/installer/installer_test.go rename to libs/aitools/installer/installer_test.go index b769143906d..710f4861439 100644 --- a/experimental/aitools/lib/installer/installer_test.go +++ b/libs/aitools/installer/installer_test.go @@ -3,22 +3,22 @@ package installer import ( "bytes" "context" - "fmt" "io/fs" "log/slog" "os" "path/filepath" - "strings" "testing" - "github.com/databricks/cli/experimental/aitools/lib/agents" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/aitools/agents" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +const testSkillsRef = "v0.1.5" + // mockManifestSource is a test double for ManifestSource. type mockManifestSource struct { manifest *Manifest @@ -29,6 +29,7 @@ func (m *mockManifestSource) FetchManifest(_ context.Context, _ string) (*Manife if m.fetchErr != nil { return nil, m.fetchErr } + normalizeManifest(m.manifest) return m.manifest, nil } @@ -53,7 +54,7 @@ func setupFetchMock(t *testing.T) { t.Helper() orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, skillName, filePath string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, skillName, filePath string) ([]byte, error) { return []byte("# " + skillName + "/" + filePath), nil } } @@ -72,6 +73,7 @@ func setupTestHome(t *testing.T) string { t.Helper() tmp := t.TempDir() t.Setenv("HOME", tmp) + t.Setenv("USERPROFILE", tmp) // Create agent config dir so the agent is "detected". require.NoError(t, os.MkdirAll(filepath.Join(tmp, ".test-agent"), 0o755)) return tmp @@ -192,6 +194,7 @@ func TestInstallSkillsForAgentsWritesState(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -204,18 +207,19 @@ func TestInstallSkillsForAgentsWritesState(t *testing.T) { require.NoError(t, err) require.NotNil(t, state) assert.Equal(t, 1, state.SchemaVersion) - assert.Equal(t, defaultSkillsRepoRef, state.Release) + assert.Equal(t, testSkillsRef, state.Release) assert.Len(t, state.Skills, 2) assert.Equal(t, "0.1.0", state.Skills["databricks-sql"]) assert.Equal(t, "0.1.0", state.Skills["databricks-jobs"]) - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 2 skills (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 2 skills.") } func TestInstallSkillForSingleWritesState(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -232,13 +236,14 @@ func TestInstallSkillForSingleWritesState(t *testing.T) { assert.Len(t, state.Skills, 1) assert.Equal(t, "0.1.0", state.Skills["databricks-sql"]) - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 1 skill (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 1 skill.") } func TestInstallSkillsSpecificNotFound(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -254,12 +259,13 @@ func TestExperimentalSkillsSkippedByDefault(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) manifest := testManifest() - manifest.Skills["databricks-experimental"] = SkillMeta{ - Version: "0.1.0", - Files: []string{"SKILL.md"}, - Experimental: true, + manifest.Skills["databricks-iceberg"] = SkillMeta{ + Version: "0.1.0", + Files: []string{"SKILL.md"}, + RepoDir: experimentalRepoPath, } src := &mockManifestSource{manifest: manifest} @@ -273,21 +279,22 @@ func TestExperimentalSkillsSkippedByDefault(t *testing.T) { require.NoError(t, err) // Only non-experimental skills should be installed. assert.Len(t, state.Skills, 2) - assert.NotContains(t, state.Skills, "databricks-experimental") + assert.NotContains(t, state.Skills, "databricks-iceberg") - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 2 skills (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 2 skills.") } func TestExperimentalSkillsIncludedWithFlag(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) manifest := testManifest() - manifest.Skills["databricks-experimental"] = SkillMeta{ - Version: "0.1.0", - Files: []string{"SKILL.md"}, - Experimental: true, + manifest.Skills["databricks-iceberg"] = SkillMeta{ + Version: "0.1.0", + Files: []string{"SKILL.md"}, + RepoDir: experimentalRepoPath, } src := &mockManifestSource{manifest: manifest} @@ -302,16 +309,17 @@ func TestExperimentalSkillsIncludedWithFlag(t *testing.T) { state, err := LoadState(globalDir) require.NoError(t, err) assert.Len(t, state.Skills, 3) - assert.Contains(t, state.Skills, "databricks-experimental") + assert.Contains(t, state.Skills, "databricks-iceberg") assert.True(t, state.IncludeExperimental) - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 3 skills (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 3 skills.") } func TestMinCLIVersionSkipWithWarningForInstallAll(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) setBuildVersion(t, "0.200.0") // Capture log output to verify the warning. @@ -339,7 +347,7 @@ func TestMinCLIVersionSkipWithWarningForInstallAll(t *testing.T) { assert.Len(t, state.Skills, 2) assert.NotContains(t, state.Skills, "databricks-future") - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 2 skills (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 2 skills.") assert.Contains(t, logBuf.String(), "requires CLI version 0.300.0") } @@ -347,6 +355,7 @@ func TestMinCLIVersionHardErrorForInstallSingle(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) setBuildVersion(t, "0.200.0") manifest := testManifest() @@ -371,6 +380,7 @@ func TestIdempotentSecondInstallSkips(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -383,7 +393,7 @@ func TestIdempotentSecondInstallSkips(t *testing.T) { fetchCalls := 0 orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, skillName, filePath string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, skillName, filePath string) ([]byte, error) { fetchCalls++ return []byte("# " + skillName + "/" + filePath), nil } @@ -400,6 +410,7 @@ func TestIdempotentInstallUpdatesNewVersions(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -420,7 +431,7 @@ func TestIdempotentInstallUpdatesNewVersions(t *testing.T) { var fetchedSkills []string orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, skillName, filePath string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, skillName, filePath string) ([]byte, error) { fetchedSkills = append(fetchedSkills, skillName) return []byte("# " + skillName + "/" + filePath), nil } @@ -437,7 +448,7 @@ func TestIdempotentInstallUpdatesNewVersions(t *testing.T) { globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") state, err := LoadState(globalDir) require.NoError(t, err) - assert.Equal(t, defaultSkillsRepoRef, state.Release) + assert.Equal(t, testSkillsRef, state.Release) assert.Equal(t, "0.2.0", state.Skills["databricks-sql"]) } @@ -445,6 +456,7 @@ func TestLegacyDetectMessagePrinted(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Create skills on disk at canonical location but no state file. globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") @@ -463,6 +475,7 @@ func TestLegacyDetectLegacyDir(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Create skills in the legacy location. legacyDir := filepath.Join(tmp, ".databricks", "agent-skills") @@ -481,6 +494,7 @@ func TestIdempotentInstallReinstallsForNewAgent(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent1 := testAgent(tmp) @@ -504,7 +518,7 @@ func TestIdempotentInstallReinstallsForNewAgent(t *testing.T) { fetchCalls := 0 orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, skillName, filePath string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, skillName, filePath string) ([]byte, error) { fetchCalls++ return []byte("# " + skillName + "/" + filePath), nil } @@ -526,6 +540,7 @@ func TestLegacyTargetedInstallBlocked(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Create skills on disk at canonical location but no state file (legacy). globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") @@ -546,6 +561,7 @@ func TestLegacyFullInstallAllowed(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Create skills on disk at canonical location but no state file (legacy). globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") @@ -590,6 +606,7 @@ func TestInstallProjectScopeWritesState(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Use project dir as cwd. projectDir := filepath.Join(tmp, "myproject") @@ -607,17 +624,17 @@ func TestInstallProjectScopeWritesState(t *testing.T) { require.NoError(t, err) require.NotNil(t, state) assert.Equal(t, ScopeProject, state.Scope) - assert.Equal(t, defaultSkillsRepoRef, state.Release) + assert.Equal(t, testSkillsRef, state.Release) assert.Len(t, state.Skills, 2) - tag := strings.TrimPrefix(defaultSkillsRepoRef, "v") - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 2 skills (v%s).", tag)) + assert.Contains(t, stderr.String(), "Installed 2 skills.") } func TestInstallProjectScopeCreatesSymlinks(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) projectDir := filepath.Join(tmp, "myproject") require.NoError(t, os.MkdirAll(projectDir, 0o755)) @@ -660,6 +677,7 @@ func TestInstallProjectScopeFiltersIncompatibleAgents(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) projectDir := filepath.Join(tmp, "myproject") require.NoError(t, os.MkdirAll(projectDir, 0o755)) @@ -680,13 +698,14 @@ func TestInstallProjectScopeFiltersIncompatibleAgents(t *testing.T) { require.NoError(t, err) assert.Contains(t, stderr.String(), "Skipped No Project Agent: does not support project-scoped skills.") - assert.Contains(t, stderr.String(), fmt.Sprintf("Installed 2 skills (%s).", defaultSkillsRepoRef)) + assert.Contains(t, stderr.String(), "Installed 2 skills.") } func TestInstallProjectScopeZeroCompatibleAgentsReturnsError(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) projectDir := filepath.Join(tmp, "myproject") require.NoError(t, os.MkdirAll(projectDir, 0o755)) @@ -709,6 +728,53 @@ func TestInstallProjectScopeZeroCompatibleAgentsReturnsError(t *testing.T) { assert.Contains(t, err.Error(), "No Project Agent") } +func TestInstallKeepsNameWhenRepoDirChanges(t *testing.T) { + tmp := setupTestHome(t) + ctx := cmdio.MockDiscard(t.Context()) + agent := testAgent(tmp) + var fetchedFrom []string + orig := fetchFileFn + t.Cleanup(func() { fetchFileFn = orig }) + fetchFileFn = func(_ context.Context, _, repoDir, skillName, filePath string) ([]byte, error) { + fetchedFrom = append(fetchedFrom, filepath.Join(repoDir, skillName, filePath)) + return []byte("# " + skillName + "/" + filePath), nil + } + + stableManifest := &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}}, + }, + } + require.NoError(t, InstallSkillsForAgents( + ctx, &mockManifestSource{manifest: stableManifest}, + []*agents.Agent{agent}, InstallOptions{}, + )) + + globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") + require.DirExists(t, filepath.Join(globalDir, "databricks-jobs")) + assert.Contains(t, fetchedFrom, filepath.Join(stableSkillsRepoPath, "databricks-jobs", "SKILL.md")) + fetchedFrom = nil + + experimentalManifest := &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}, RepoDir: experimentalRepoPath}, + }, + } + require.NoError(t, InstallSkillsForAgents( + ctx, &mockManifestSource{manifest: experimentalManifest}, + []*agents.Agent{agent}, InstallOptions{IncludeExperimental: true}, + )) + + state, err := LoadState(globalDir) + require.NoError(t, err) + assert.Equal(t, "0.1.0", state.Skills["databricks-jobs"]) + assert.Equal(t, experimentalRepoPath, state.RepoDirs["databricks-jobs"]) + assert.DirExists(t, filepath.Join(globalDir, "databricks-jobs")) + assert.Contains(t, fetchedFrom, filepath.Join(experimentalRepoPath, "databricks-jobs", "SKILL.md")) +} + func TestSupportsProjectScopeSetCorrectly(t *testing.T) { expected := map[string]bool{ "claude-code": true, @@ -725,3 +791,20 @@ func TestSupportsProjectScopeSetCorrectly(t *testing.T) { assert.Equal(t, want, agent.SupportsProjectScope, "SupportsProjectScope for %s", agent.Name) } } + +func TestGetSkillsRefResolvesFromManifest(t *testing.T) { + // Pre-populate the cache so FetchManifest returns from tier 1 (local cache) + // without hitting the network. The embedded manifest fallback is tested + // separately in clicompat_test.go. + cacheDir := t.TempDir() + t.Setenv("DATABRICKS_CACHE_DIR", cacheDir) + cachePath := filepath.Join(cacheDir, "compat-manifest.json") + manifest := `{"next":{"appkit":"0.24.0","skills":"0.1.5"},"0.300.0":{"appkit":"0.24.0","skills":"0.1.5"}}` + require.NoError(t, os.WriteFile(cachePath, []byte(manifest), 0o644)) + + ref, explicit, err := GetSkillsRef(t.Context()) + require.NoError(t, err, "GetSkillsRef should succeed via cached manifest") + assert.False(t, explicit, "ref resolved from manifest should not be explicit") + assert.NotEmpty(t, ref) + assert.True(t, len(ref) > 1 && ref[0] == 'v', "ref should start with 'v', got %q", ref) +} diff --git a/experimental/aitools/lib/installer/source.go b/libs/aitools/installer/source.go similarity index 68% rename from experimental/aitools/lib/installer/source.go rename to libs/aitools/installer/source.go index e601b26d66d..f3d9842f876 100644 --- a/experimental/aitools/lib/installer/source.go +++ b/libs/aitools/installer/source.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/databricks/cli/libs/clicompat" "github.com/databricks/cli/libs/log" ) @@ -22,8 +23,7 @@ type GitHubManifestSource struct{} // FetchManifest fetches the skills manifest from GitHub at the given ref. func (s *GitHubManifestSource) FetchManifest(ctx context.Context, ref string) (*Manifest, error) { log.Debugf(ctx, "Fetching skills manifest from %s/%s@%s", skillsRepoOwner, skillsRepoName, ref) - url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/manifest.json", - skillsRepoOwner, skillsRepoName, ref) + url := fmt.Sprintf("%s/%s/manifest.json", GetSkillsBaseURL(ctx), ref) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -37,6 +37,9 @@ func (s *GitHubManifestSource) FetchManifest(ctx context.Context, ref string) (* } defer resp.Body.Close() + if resp.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("skills manifest at %s@%s: %w", skillsRepoName, ref, clicompat.ErrNotFound) + } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to fetch manifest: HTTP %d", resp.StatusCode) } @@ -46,5 +49,20 @@ func (s *GitHubManifestSource) FetchManifest(ctx context.Context, ref string) (* return nil, fmt.Errorf("failed to parse manifest: %w", err) } + normalizeManifest(&manifest) return &manifest, nil } + +// normalizeManifest stamps SourceName and defaults RepoDir for older manifests. +func normalizeManifest(m *Manifest) { + if m.Skills == nil { + m.Skills = map[string]SkillMeta{} + } + for name, meta := range m.Skills { + if meta.RepoDir == "" { + meta.RepoDir = stableSkillsRepoPath + } + meta.SourceName = name + m.Skills[name] = meta + } +} diff --git a/libs/aitools/installer/source_test.go b/libs/aitools/installer/source_test.go new file mode 100644 index 00000000000..fb52563b686 --- /dev/null +++ b/libs/aitools/installer/source_test.go @@ -0,0 +1,62 @@ +package installer + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizeManifestStampsSourceNameAndRepoDir(t *testing.T) { + m := &Manifest{ + Skills: map[string]SkillMeta{ + "databricks-apps": {Version: "0.1.0", Files: []string{"SKILL.md"}}, + "databricks-iceberg": {Version: "0.0.1", Files: []string{"SKILL.md"}, RepoDir: experimentalRepoPath}, + }, + } + + normalizeManifest(m) + + stable := m.Skills["databricks-apps"] + assert.False(t, stable.IsExperimental()) + assert.Equal(t, stableSkillsRepoPath, stable.RepoDir) + assert.Equal(t, "databricks-apps", stable.SourceName) + + exp, ok := m.Skills["databricks-iceberg"] + assert.True(t, ok) + assert.True(t, exp.IsExperimental()) + assert.Equal(t, experimentalRepoPath, exp.RepoDir) + assert.Equal(t, "databricks-iceberg", exp.SourceName) +} + +func TestManifestHasExperimental(t *testing.T) { + stableOnly := &Manifest{Skills: map[string]SkillMeta{ + "databricks-apps": {Version: "0.1.0"}, + }} + normalizeManifest(stableOnly) + assert.False(t, manifestHasExperimental(stableOnly)) + + withExperimental := &Manifest{ + Skills: map[string]SkillMeta{ + "databricks-apps": {Version: "0.1.0"}, + "databricks-iceberg": {Version: "0.0.1", RepoDir: experimentalRepoPath}, + }, + } + normalizeManifest(withExperimental) + assert.True(t, manifestHasExperimental(withExperimental)) +} + +func TestNormalizeManifestOnlyExperimentalSkills(t *testing.T) { + m := &Manifest{ + Skills: map[string]SkillMeta{ + "x": {Version: "0.0.1", RepoDir: experimentalRepoPath}, + }, + } + + normalizeManifest(m) + + got, ok := m.Skills["x"] + assert.True(t, ok) + assert.True(t, got.IsExperimental()) + assert.Equal(t, experimentalRepoPath, got.RepoDir) + assert.Equal(t, "x", got.SourceName) +} diff --git a/experimental/aitools/lib/installer/state.go b/libs/aitools/installer/state.go similarity index 97% rename from experimental/aitools/lib/installer/state.go rename to libs/aitools/installer/state.go index a666a585057..9aa52359bd5 100644 --- a/experimental/aitools/lib/installer/state.go +++ b/libs/aitools/installer/state.go @@ -27,6 +27,7 @@ type InstallState struct { Release string `json:"release"` LastUpdated time.Time `json:"last_updated"` Skills map[string]string `json:"skills"` + RepoDirs map[string]string `json:"repo_dirs,omitempty"` Scope string `json:"scope,omitempty"` } diff --git a/experimental/aitools/lib/installer/state_test.go b/libs/aitools/installer/state_test.go similarity index 93% rename from experimental/aitools/lib/installer/state_test.go rename to libs/aitools/installer/state_test.go index f1fcdb8c229..4ed1e78d5f2 100644 --- a/experimental/aitools/lib/installer/state_test.go +++ b/libs/aitools/installer/state_test.go @@ -26,6 +26,9 @@ func TestSaveAndLoadStateRoundtrip(t *testing.T) { Skills: map[string]string{ "databricks": "1.0.0", }, + RepoDirs: map[string]string{ + "databricks": stableSkillsRepoPath, + }, } err := SaveState(dir, original) @@ -105,6 +108,10 @@ func TestSaveAndLoadStateWithOptionalFields(t *testing.T) { "databricks": "1.0.0", "sql-tools": "0.2.0", }, + RepoDirs: map[string]string{ + "databricks": stableSkillsRepoPath, + "sql-tools": experimentalRepoPath, + }, Scope: "project", } diff --git a/experimental/aitools/lib/installer/uninstall.go b/libs/aitools/installer/uninstall.go similarity index 97% rename from experimental/aitools/lib/installer/uninstall.go rename to libs/aitools/installer/uninstall.go index 1ad9f58511c..2e97fa784a5 100644 --- a/experimental/aitools/lib/installer/uninstall.go +++ b/libs/aitools/installer/uninstall.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - "github.com/databricks/cli/experimental/aitools/lib/agents" + "github.com/databricks/cli/libs/aitools/agents" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" ) @@ -54,7 +54,7 @@ func UninstallSkillsOpts(ctx context.Context, opts UninstallOptions) error { if state == nil { if scope == ScopeGlobal && hasLegacyInstall(ctx, baseDir) { - return errors.New("found skills from a previous install without state tracking; run 'databricks experimental aitools install' first, then uninstall") + return errors.New("found skills from a previous install without state tracking; run 'databricks aitools install' first, then uninstall") } return errors.New("no skills installed") } @@ -64,13 +64,13 @@ func UninstallSkillsOpts(ctx context.Context, opts UninstallOptions) error { if len(opts.Skills) > 0 { seen := make(map[string]bool) for _, name := range opts.Skills { + if _, ok := state.Skills[name]; !ok { + return fmt.Errorf("skill %q is not installed", name) + } if seen[name] { continue } seen[name] = true - if _, ok := state.Skills[name]; !ok { - return fmt.Errorf("skill %q is not installed", name) - } toRemove = append(toRemove, name) } } else { @@ -89,6 +89,7 @@ func UninstallSkillsOpts(ctx context.Context, opts UninstallOptions) error { log.Warnf(ctx, "Failed to remove %s: %v", canonicalDir, err) } delete(state.Skills, name) + delete(state.RepoDirs, name) } if removeAll { diff --git a/experimental/aitools/lib/installer/uninstall_test.go b/libs/aitools/installer/uninstall_test.go similarity index 98% rename from experimental/aitools/lib/installer/uninstall_test.go rename to libs/aitools/installer/uninstall_test.go index 6c7589f6f29..3dc8e03af35 100644 --- a/experimental/aitools/lib/installer/uninstall_test.go +++ b/libs/aitools/installer/uninstall_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/databricks/cli/experimental/aitools/lib/agents" + "github.com/databricks/cli/libs/aitools/agents" "github.com/databricks/cli/libs/cmdio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -18,6 +18,7 @@ func installTestSkills(t *testing.T, tmp string) string { t.Helper() ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) src := &mockManifestSource{manifest: testManifest()} agent := testAgent(tmp) @@ -48,6 +49,7 @@ func TestUninstallRemovesSymlinks(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Use two registry-based agents so uninstall can find them. // Create config dirs for claude-code and cursor (both in agents.Registry). diff --git a/experimental/aitools/lib/installer/update.go b/libs/aitools/installer/update.go similarity index 86% rename from experimental/aitools/lib/installer/update.go rename to libs/aitools/installer/update.go index 663ad5e908e..39db94c8f62 100644 --- a/experimental/aitools/lib/installer/update.go +++ b/libs/aitools/installer/update.go @@ -11,8 +11,8 @@ import ( "strings" "time" - "github.com/databricks/cli/experimental/aitools/lib/agents" "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/aitools/agents" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/env" "github.com/databricks/cli/libs/log" @@ -76,19 +76,22 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent if state == nil { if scope == ScopeGlobal && hasLegacyInstall(ctx, baseDir) { - return nil, errors.New("found skills from a previous install without state tracking; run 'databricks experimental aitools install' to refresh before updating") + return nil, errors.New("found skills from a previous install without state tracking; run 'databricks aitools install' to refresh before updating") } - return nil, errors.New("no skills installed. Run 'databricks experimental aitools install' to install") + return nil, errors.New("no skills installed. Run 'databricks aitools install' to install") } - latestTag := GetSkillsRef(ctx) + latestTag, explicit, err := GetSkillsRef(ctx) + if err != nil { + return nil, err + } if state.Release == latestTag && !opts.Force { cmdio.LogString(ctx, "Already up to date.") return &UpdateResult{Unchanged: slices.Sorted(maps.Keys(state.Skills))}, nil } - manifest, err := src.FetchManifest(ctx, latestTag) + manifest, latestTag, err := FetchSkillsManifestWithFallback(ctx, src, latestTag, !explicit) if err != nil { if opts.Check { log.Warnf(ctx, "Could not fetch manifest: %v", err) @@ -110,19 +113,17 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent for _, name := range names { meta, inManifest := manifest.Skills[name] - oldVersion := state.Skills[name] if !inManifest { - _, wasInstalled := state.Skills[name] - if wasInstalled { + if _, ok := state.Skills[name]; ok { log.Warnf(ctx, "Warning: %q not found in manifest %s (keeping installed version).", name, latestTag) + result.Unchanged = append(result.Unchanged, name) } - result.Unchanged = append(result.Unchanged, name) continue } // Filter experimental skills unless state opted in. - if meta.Experimental && !state.IncludeExperimental { + if meta.IsExperimental() && !state.IncludeExperimental { log.Debugf(ctx, "Skipping experimental skill %s", name) result.Skipped = append(result.Skipped, name) continue @@ -135,10 +136,9 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent continue } - // Check if this is a new skill (not in state). - _, wasInstalled := state.Skills[name] + oldVersion, wasInstalled := state.Skills[name] - if meta.Version == oldVersion && !opts.Force { + if meta.Version == oldVersion && stateRepoDir(state, name) == meta.RepoDir && !opts.Force { result.Unchanged = append(result.Unchanged, name) continue } @@ -149,10 +149,10 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent NewVersion: meta.Version, } - if !wasInstalled { - result.Added = append(result.Added, update) - } else { + if wasInstalled { result.Updated = append(result.Updated, update) + } else { + result.Added = append(result.Added, update) } } @@ -174,7 +174,7 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent for _, change := range allChanges { meta := manifest.Skills[change.Name] - if err := installSkillForAgents(ctx, change.Name, meta.Files, targetAgents, params); err != nil { + if err := installSkillForAgents(ctx, change.Name, meta, targetAgents, params); err != nil { return nil, err } } @@ -182,8 +182,13 @@ func UpdateSkills(ctx context.Context, src ManifestSource, targetAgents []*agent // Update state. state.Release = latestTag state.LastUpdated = time.Now() + if state.RepoDirs == nil { + state.RepoDirs = make(map[string]string, len(state.Skills)+len(allChanges)) + } for _, change := range allChanges { + meta := manifest.Skills[change.Name] state.Skills[change.Name] = change.NewVersion + state.RepoDirs[change.Name] = meta.RepoDir } if err := SaveState(baseDir, state); err != nil { return nil, err @@ -197,7 +202,6 @@ func buildUpdateSkillSet(state *InstallState, manifest *Manifest, opts UpdateOpt skillSet := make(map[string]bool) if len(opts.Skills) > 0 { - // Only named skills. for _, name := range opts.Skills { skillSet[name] = true } diff --git a/experimental/aitools/lib/installer/update_test.go b/libs/aitools/installer/update_test.go similarity index 68% rename from experimental/aitools/lib/installer/update_test.go rename to libs/aitools/installer/update_test.go index 97e3014be65..72f5a6b25f2 100644 --- a/experimental/aitools/lib/installer/update_test.go +++ b/libs/aitools/installer/update_test.go @@ -8,7 +8,7 @@ import ( "path/filepath" "testing" - "github.com/databricks/cli/experimental/aitools/lib/agents" + "github.com/databricks/cli/libs/aitools/agents" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/log" "github.com/stretchr/testify/assert" @@ -24,7 +24,7 @@ func TestUpdateNoStateReturnsInstallHint(t *testing.T) { _, err := UpdateSkills(ctx, src, nil, UpdateOptions{}) require.Error(t, err) assert.Contains(t, err.Error(), "no skills installed") - assert.Contains(t, err.Error(), "databricks experimental aitools install") + assert.Contains(t, err.Error(), "databricks aitools install") } func TestUpdateLegacyInstallDetected(t *testing.T) { @@ -46,6 +46,7 @@ func TestUpdateAlreadyUpToDate(t *testing.T) { tmp := setupTestHome(t) ctx, stderr := cmdio.NewTestContextWithStderr(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install first. src := &mockManifestSource{manifest: testManifest()} @@ -68,6 +69,7 @@ func TestUpdateVersionDiffDetected(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with default ref. src := &mockManifestSource{manifest: testManifest()} @@ -108,6 +110,7 @@ func TestUpdateCheckDryRun(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with default ref. src := &mockManifestSource{manifest: testManifest()} @@ -129,7 +132,7 @@ func TestUpdateCheckDryRun(t *testing.T) { fetchCalls := 0 orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, _, _ string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, _, _ string) ([]byte, error) { fetchCalls++ return []byte("content"), nil } @@ -148,13 +151,14 @@ func TestUpdateCheckDryRun(t *testing.T) { globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") state, err := LoadState(globalDir) require.NoError(t, err) - assert.Equal(t, defaultSkillsRepoRef, state.Release) + assert.Equal(t, testSkillsRef, state.Release) } func TestUpdateForceRedownloads(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install v0.1.0. src := &mockManifestSource{manifest: testManifest()} @@ -165,7 +169,7 @@ func TestUpdateForceRedownloads(t *testing.T) { fetchCalls := 0 orig := fetchFileFn t.Cleanup(func() { fetchFileFn = orig }) - fetchFileFn = func(_ context.Context, _, _, _ string) ([]byte, error) { + fetchFileFn = func(_ context.Context, _, _, _, _ string) ([]byte, error) { fetchCalls++ return []byte("content"), nil } @@ -182,6 +186,7 @@ func TestUpdateAutoAddsNewSkills(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with default ref. src := &mockManifestSource{manifest: testManifest()} @@ -218,6 +223,7 @@ func TestUpdateNoNewIgnoresNewSkills(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with default ref. src := &mockManifestSource{manifest: testManifest()} @@ -254,6 +260,7 @@ func TestUpdateOutputSortedAlphabetically(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with skills. src := &mockManifestSource{manifest: testManifest()} @@ -281,6 +288,7 @@ func TestUpdateSkillRemovedFromManifestWarning(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Capture log output to verify warning. var logBuf bytes.Buffer @@ -325,6 +333,7 @@ func TestUpdateSkipsExperimentalSkills(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) // Install with default ref (not experimental). src := &mockManifestSource{manifest: testManifest()} @@ -336,10 +345,10 @@ func TestUpdateSkipsExperimentalSkills(t *testing.T) { // New manifest with an experimental skill. updatedManifest := testManifest() - updatedManifest.Skills["databricks-experimental"] = SkillMeta{ - Version: "0.1.0", - Files: []string{"SKILL.md"}, - Experimental: true, + updatedManifest.Skills["databricks-iceberg"] = SkillMeta{ + Version: "0.1.0", + Files: []string{"SKILL.md"}, + RepoDir: experimentalRepoPath, } src2 := &mockManifestSource{manifest: updatedManifest} @@ -347,14 +356,167 @@ func TestUpdateSkipsExperimentalSkills(t *testing.T) { require.NoError(t, err) // Experimental skill should be skipped. - assert.Contains(t, result.Skipped, "databricks-experimental") + assert.Contains(t, result.Skipped, "databricks-iceberg") assert.Empty(t, result.Added) } +func TestUpdateKeepsNameWhenRepoDirChanges(t *testing.T) { + tests := []struct { + name string + installedManifest func() *Manifest + updatedManifest func() *Manifest + wantRepoDir string + }{ + { + name: "stable to experimental", + installedManifest: func() *Manifest { + return &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}}, + }, + } + }, + updatedManifest: func() *Manifest { + return &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}, RepoDir: experimentalRepoPath}, + }, + } + }, + wantRepoDir: experimentalRepoPath, + }, + { + name: "experimental to stable", + installedManifest: func() *Manifest { + return &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}, RepoDir: experimentalRepoPath}, + }, + } + }, + updatedManifest: func() *Manifest { + return &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}}, + }, + } + }, + wantRepoDir: stableSkillsRepoPath, + }, + } + + for _, tt := range tests { + for _, targeted := range []bool{false, true} { + mode := "all" + opts := UpdateOptions{} + if targeted { + mode = "targeted" + opts.Skills = []string{"databricks-jobs"} + } + + t.Run(tt.name+" "+mode, func(t *testing.T) { + tmp := setupTestHome(t) + ctx := cmdio.MockDiscard(t.Context()) + setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) + agent := testAgent(tmp) + + require.NoError(t, InstallSkillsForAgents( + ctx, &mockManifestSource{manifest: tt.installedManifest()}, + []*agents.Agent{agent}, InstallOptions{IncludeExperimental: true}, + )) + + globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") + require.DirExists(t, filepath.Join(globalDir, "databricks-jobs")) + + t.Setenv("DATABRICKS_SKILLS_REF", "v0.2.0") + result, err := UpdateSkills( + ctx, &mockManifestSource{manifest: tt.updatedManifest()}, + []*agents.Agent{agent}, opts, + ) + require.NoError(t, err) + + require.Len(t, result.Updated, 1) + assert.Equal(t, "databricks-jobs", result.Updated[0].Name) + assert.Equal(t, "0.1.0", result.Updated[0].OldVersion) + assert.Equal(t, "0.1.0", result.Updated[0].NewVersion) + assert.NotContains(t, result.Unchanged, "databricks-jobs") + assert.Empty(t, result.Added) + + state, err := LoadState(globalDir) + require.NoError(t, err) + assert.Equal(t, "0.1.0", state.Skills["databricks-jobs"]) + assert.Equal(t, tt.wantRepoDir, state.RepoDirs["databricks-jobs"]) + assert.DirExists(t, filepath.Join(globalDir, "databricks-jobs")) + }) + } + } +} + +func TestUpdateRepoDirChangeFromLegacyState(t *testing.T) { + tmp := setupTestHome(t) + ctx := cmdio.MockDiscard(t.Context()) + agent := testAgent(tmp) + t.Setenv("DATABRICKS_SKILLS_REF", "v0.2.0") + + globalDir := filepath.Join(tmp, ".databricks", "aitools", "skills") + require.NoError(t, SaveState(globalDir, &InstallState{ + SchemaVersion: 1, + IncludeExperimental: true, + Release: testSkillsRef, + Skills: map[string]string{ + "databricks-jobs": "0.1.0", + }, + })) + + fetchCalls := 0 + orig := fetchFileFn + t.Cleanup(func() { fetchFileFn = orig }) + fetchFileFn = func(_ context.Context, _, _, _, _ string) ([]byte, error) { + fetchCalls++ + return []byte("content"), nil + } + + stableManifest := &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}}, + }, + } + stableResult, err := UpdateSkills(ctx, &mockManifestSource{manifest: stableManifest}, []*agents.Agent{agent}, UpdateOptions{}) + require.NoError(t, err) + assert.Contains(t, stableResult.Unchanged, "databricks-jobs") + assert.Equal(t, 0, fetchCalls) + + t.Setenv("DATABRICKS_SKILLS_REF", "v0.3.0") + experimentalManifest := &Manifest{ + Version: "1", + Skills: map[string]SkillMeta{ + "databricks-jobs": {Version: "0.1.0", Files: []string{"SKILL.md"}, RepoDir: experimentalRepoPath}, + }, + } + experimentalResult, err := UpdateSkills(ctx, &mockManifestSource{manifest: experimentalManifest}, []*agents.Agent{agent}, UpdateOptions{}) + require.NoError(t, err) + require.Len(t, experimentalResult.Updated, 1) + assert.Equal(t, "databricks-jobs", experimentalResult.Updated[0].Name) + assert.Equal(t, "0.1.0", experimentalResult.Updated[0].OldVersion) + assert.Equal(t, "0.1.0", experimentalResult.Updated[0].NewVersion) + assert.Positive(t, fetchCalls) + + state, err := LoadState(globalDir) + require.NoError(t, err) + assert.Equal(t, experimentalRepoPath, state.RepoDirs["databricks-jobs"]) +} + func TestUpdateSkipsMinCLIVersionSkills(t *testing.T) { tmp := setupTestHome(t) ctx := cmdio.MockDiscard(t.Context()) setupFetchMock(t) + t.Setenv("DATABRICKS_SKILLS_REF", testSkillsRef) setBuildVersion(t, "0.200.0") var logBuf bytes.Buffer diff --git a/libs/apps/logstream/formatter.go b/libs/apps/logstream/formatter.go index ad1b19543fc..fa317f57af2 100644 --- a/libs/apps/logstream/formatter.go +++ b/libs/apps/logstream/formatter.go @@ -1,13 +1,14 @@ package logstream import ( + "context" "encoding/json" "fmt" "strings" "time" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" - "github.com/fatih/color" ) // wsEntry represents a structured log entry from the websocket stream. @@ -38,28 +39,31 @@ func newLogFormatter(colorize bool, outputFormat flags.Output) *logFormatter { } // FormatEntry formats a structured log entry for output. -func (f *logFormatter) FormatEntry(entry *wsEntry) string { +func (f *logFormatter) FormatEntry(ctx context.Context, entry *wsEntry) string { if f.outputFormat == flags.OutputJSON { return f.formatEntryJSON(entry) } - return f.formatEntryText(entry) + return f.formatEntryText(ctx, entry) } // formatEntryText formats a structured log entry as human-readable text. -func (f *logFormatter) formatEntryText(entry *wsEntry) string { +func (f *logFormatter) formatEntryText(ctx context.Context, entry *wsEntry) string { timestamp := formatTimestamp(entry.Timestamp) source := strings.ToUpper(entry.Source) message := strings.TrimRight(entry.Message, "\r\n") if f.colorize { - timestamp = color.HiBlackString(timestamp) - source = color.HiBlueString(source) + timestamp = cmdio.HiBlack(ctx, timestamp) + source = cmdio.HiBlue(ctx, source) } return fmt.Sprintf("%s [%s] %s", timestamp, source, message) } // formatEntryJSON formats a structured log entry as JSON (NDJSON line). +// On marshal failure it falls back to the plain text path; that fallback is +// uncolored because we have no ctx at that point and JSON output is never +// piped to a TTY-colored renderer anyway. func (f *logFormatter) formatEntryJSON(entry *wsEntry) string { normalized := wsEntry{ Source: strings.ToUpper(entry.Source), @@ -68,7 +72,11 @@ func (f *logFormatter) formatEntryJSON(entry *wsEntry) string { } data, err := json.Marshal(normalized) if err != nil { - return f.formatEntryText(entry) + return fmt.Sprintf("%s [%s] %s", + formatTimestamp(entry.Timestamp), + strings.ToUpper(entry.Source), + strings.TrimRight(entry.Message, "\r\n"), + ) } return string(data) } diff --git a/libs/apps/logstream/formatter_test.go b/libs/apps/logstream/formatter_test.go index d470de43fbb..ff91d0b27d3 100644 --- a/libs/apps/logstream/formatter_test.go +++ b/libs/apps/logstream/formatter_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -11,10 +12,11 @@ import ( func TestFormatter_FormatEntry(t *testing.T) { entry := &wsEntry{Source: "app", Timestamp: 1705315800.0, Message: "hello world\n"} + ctx := cmdio.MockDiscard(t.Context()) t.Run("json output", func(t *testing.T) { jsonFormatter := newLogFormatter(false, flags.OutputJSON) - output := jsonFormatter.FormatEntry(entry) + output := jsonFormatter.FormatEntry(ctx, entry) var parsed wsEntry require.NoError(t, json.Unmarshal([]byte(output), &parsed)) @@ -27,7 +29,7 @@ func TestFormatter_FormatEntry(t *testing.T) { t.Run("text output", func(t *testing.T) { textFormatter := newLogFormatter(false, flags.OutputText) - output := textFormatter.FormatEntry(entry) + output := textFormatter.FormatEntry(ctx, entry) assert.Contains(t, output, "[APP]") assert.Contains(t, output, "hello world") diff --git a/libs/apps/logstream/streamer.go b/libs/apps/logstream/streamer.go index 81624bedb9c..5cbfc87ede9 100644 --- a/libs/apps/logstream/streamer.go +++ b/libs/apps/logstream/streamer.go @@ -251,7 +251,7 @@ func (s *logStreamer) consume(ctx context.Context, conn *websocket.Conn) (retErr continue } - line := s.formatMessage(message) + line := s.formatMessage(ctx, message) if line == "" { continue } @@ -261,7 +261,7 @@ func (s *logStreamer) consume(ctx context.Context, conn *websocket.Conn) (retErr } } -func (s *logStreamer) formatMessage(message []byte) string { +func (s *logStreamer) formatMessage(ctx context.Context, message []byte) string { entry, err := parseLogEntry(message) if err != nil { return s.formatter.FormatPlain(message) @@ -272,7 +272,7 @@ func (s *logStreamer) formatMessage(message []byte) string { return "" } } - return s.formatter.FormatEntry(entry) + return s.formatter.FormatEntry(ctx, entry) } func (s *logStreamer) ensureToken(ctx context.Context) error { diff --git a/libs/apps/logstream/streamer_test.go b/libs/apps/logstream/streamer_test.go index 20d12227386..cde5db8c94c 100644 --- a/libs/apps/logstream/streamer_test.go +++ b/libs/apps/logstream/streamer_test.go @@ -14,8 +14,8 @@ import ( "testing" "time" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/flags" - "github.com/fatih/color" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -221,19 +221,18 @@ func TestLogStreamerFiltersSources(t *testing.T) { } func TestFormatLogEntryColorizesWhenEnabled(t *testing.T) { - original := color.NoColor - color.NoColor = false - defer func() { color.NoColor = original }() - entry := &wsEntry{Source: "app", Timestamp: 1, Message: "hello\n"} + ttyCtx, _ := cmdio.SetupTest(t.Context(), cmdio.TestOptions{PromptSupported: true}) + plainCtx := cmdio.MockDiscard(t.Context()) + colorFormatter := newLogFormatter(true, flags.OutputText) - colored := colorFormatter.FormatEntry(entry) + colored := colorFormatter.FormatEntry(ttyCtx, entry) assert.Contains(t, colored, "\x1b[") - assert.Contains(t, colored, fmt.Sprintf("[%s]", color.HiBlueString("APP"))) + assert.Contains(t, colored, fmt.Sprintf("[%s]", cmdio.HiBlue(ttyCtx, "APP"))) plainFormatter := newLogFormatter(false, flags.OutputText) - plain := plainFormatter.FormatEntry(entry) + plain := plainFormatter.FormatEntry(plainCtx, entry) assert.NotContains(t, plain, "\x1b[") assert.Contains(t, plain, "[APP]") } diff --git a/libs/apps/manifest/manifest.go b/libs/apps/manifest/manifest.go index c4ecdd7f82f..0d8f4abb3a9 100644 --- a/libs/apps/manifest/manifest.go +++ b/libs/apps/manifest/manifest.go @@ -35,6 +35,10 @@ type Resource struct { Permission string `json:"permission"` // e.g., "CAN_USE" Fields map[string]ResourceField `json:"fields"` // field definitions with env var mappings + // PluginName is the machine name of the plugin (e.g., "lakebase"). + // Set during resource collection. Not part of the JSON manifest. + PluginName string `json:"-"` + // PluginDisplayName is set during resource collection to identify which // plugin requires this resource. Not part of the JSON manifest. PluginDisplayName string `json:"-"` @@ -218,6 +222,7 @@ func (m *Manifest) CollectResources(pluginNames []string) []Resource { key := r.Type + ":" + r.Key() if !seen[key] { seen[key] = true + r.PluginName = name r.PluginDisplayName = plugin.DisplayName resources = append(resources, r) } @@ -246,6 +251,7 @@ func (m *Manifest) CollectOptionalResources(pluginNames []string) []Resource { key := r.Type + ":" + r.Key() if !seen[key] { seen[key] = true + r.PluginName = name r.PluginDisplayName = plugin.DisplayName resources = append(resources, r) } diff --git a/libs/apps/prompt/prompt.go b/libs/apps/prompt/prompt.go index 277aa949e1f..22042214334 100644 --- a/libs/apps/prompt/prompt.go +++ b/libs/apps/prompt/prompt.go @@ -124,8 +124,25 @@ type CreateProjectConfig struct { const ( MaxAppNameLength = 30 DevTargetPrefix = "dev-" + + // InPlaceName is the sentinel value for --name that scaffolds the app + // into the current working directory instead of a new subdirectory. + InPlaceName = "." ) +// inPlaceAllowedEntries lists entries permitted in the destination directory +// when scaffolding in place. Anything else triggers an error so that the +// template never overwrites existing user files. +// +// We don't allow anything that the template itself emits (including .gitignore via +// the _gitignore rename) — otherwise +// the user's existing file would be silently overwritten by os.WriteFile during +// copyTemplate. Symlinks named like an allow-listed entry are rejected by +// CheckInPlaceDirectory below to block symlink-follow overwrites. +var inPlaceAllowedEntries = map[string]bool{ + ".git": true, +} + // projectNamePattern is the compiled regex for validating project names. // Pre-compiled for efficiency since validation is called on every keystroke. var projectNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) @@ -133,8 +150,12 @@ var projectNamePattern = regexp.MustCompile(`^[a-z][a-z0-9-]*$`) // ValidateProjectName validates the project name for length and pattern constraints. // It checks that the name plus the "dev-" prefix doesn't exceed 30 characters, // and that the name follows the pattern: starts with a letter, contains only -// lowercase letters, numbers, or hyphens. +// lowercase letters, numbers, or hyphens. The literal "." is accepted as the +// in-place sentinel and validated separately by DeriveInPlaceAppName. func ValidateProjectName(s string) error { + if s == InPlaceName { + return nil + } if s == "" { return errors.New("project name is required") } @@ -154,8 +175,68 @@ func ValidateProjectName(s string) error { return nil } +// DeriveInPlaceAppName returns the app name to use when scaffolding in place. +// The name is taken from the basename of the absolute path of cwd and must +// pass ValidateProjectName so that it is a legal Databricks app name. +func DeriveInPlaceAppName(cwd string) (string, error) { + absCwd, err := filepath.Abs(cwd) + if err != nil { + return "", fmt.Errorf("resolve current directory: %w", err) + } + base := filepath.Base(absCwd) + if err := ValidateProjectName(base); err != nil { + maxAllowed := MaxAppNameLength - len(DevTargetPrefix) + return "", fmt.Errorf("current directory name %q is not a valid Databricks app name (must match [a-z][a-z0-9-]* and be at most %d chars): %w; rename the directory or run from one whose name is valid", base, maxAllowed, err) + } + return base, nil +} + +// CheckInPlaceDirectory verifies that dir contains nothing other than the +// allow-listed entries (currently just .git). Returns a concise error +// otherwise; we deliberately do not list the offending entries because the +// user can `ls` themselves and a long list buries the actionable part. +// +// Symlinks named like an allow-listed entry are rejected too: os.WriteFile +// follows symlinks, so a symlink named .git in cwd (or any future allow-listed +// dotfile) would let the template scaffold overwrite an arbitrary file the +// running user can reach. +func CheckInPlaceDirectory(dir string) error { + entries, err := os.ReadDir(dir) + if err != nil { + return fmt.Errorf("read directory %s: %w", dir, err) + } + for _, e := range entries { + if inPlaceAllowedEntries[e.Name()] && e.Type()&os.ModeSymlink == 0 { + continue + } + shown := dir + if abs, absErr := filepath.Abs(dir); absErr == nil { + shown = abs + } + return fmt.Errorf("%s is not empty; apps init --name . requires an empty directory (.git is allowed)", shown) + } + return nil +} + +// ShouldOfferInPlace returns the cwd basename and true when both the +// directory contents and basename are suitable for in-place scaffolding. +// Used to decide whether the interactive flow surfaces the in-place option. +func ShouldOfferInPlace(cwd string) (string, bool) { + if err := CheckInPlaceDirectory(cwd); err != nil { + return "", false + } + name, err := DeriveInPlaceAppName(cwd) + if err != nil { + return "", false + } + return name, true +} + // PrintHeader prints the AppKit header banner. -func PrintHeader(ctx context.Context) { +// If refLabel is non-empty (e.g. "version 0.24.0" or "branch feature-x"), +// an extra dimmed line shows the resolved template ref so the user can +// decide whether to continue before naming the project. +func PrintHeader(ctx context.Context, refLabel string) { headerStyle := lipgloss.NewStyle(). Foreground(colorRed). Bold(true) @@ -166,14 +247,51 @@ func PrintHeader(ctx context.Context) { cmdio.LogString(ctx, "") cmdio.LogString(ctx, headerStyle.Render("◆ Create a new Databricks AppKit project")) cmdio.LogString(ctx, subtitleStyle.Render(" Full-stack TypeScript • React • Tailwind CSS")) + if refLabel != "" { + cmdio.LogString(ctx, subtitleStyle.Render(" Template "+refLabel)) + } cmdio.LogString(ctx, "") } +// ErrNameDotWithOutputDir is returned when --name . (or the equivalent typed at +// the interactive prompt) is combined with a non-empty --output-dir. The two +// are mutually exclusive: --name . already targets the current directory. +var ErrNameDotWithOutputDir = errors.New("--name . and --output-dir are mutually exclusive: --name . already targets the current directory") + +// validateProjectNameForPrompt is the per-keystroke validator used by +// PromptForProjectName. Exposed (unexported) as a helper so the sentinel +// handling is unit-testable without a TTY. +func validateProjectNameForPrompt(s, outputDir string) error { + if err := ValidateProjectName(s); err != nil { + return err + } + if s == InPlaceName { + if outputDir != "" { + return ErrNameDotWithOutputDir + } + // In-place: skip the directory-exists check; the caller will + // derive the app name from the cwd and verify the directory + // is suitable. + return nil + } + destDir := s + if outputDir != "" { + destDir = filepath.Join(outputDir, s) + } + if _, err := os.Stat(destDir); err == nil { + return fmt.Errorf("directory %s already exists", destDir) + } + return nil +} + // PromptForProjectName prompts only for project name. // Used as the first step before resolving templates. -// outputDir is used to check if the destination directory already exists. +// outputDir is used to check if the destination directory already exists, +// and to reject the in-place sentinel "." when --output-dir is set. +// The caller is responsible for printing the AppKit header before invoking +// this function so the header also covers preceding prompts (e.g. the +// in-place scaffold-location chooser). func PromptForProjectName(ctx context.Context, outputDir string) (string, error) { - PrintHeader(ctx) theme := AppkitTheme() var name string @@ -183,17 +301,7 @@ func PromptForProjectName(ctx context.Context, outputDir string) (string, error) Placeholder("my-app"). Value(&name). Validate(func(s string) error { - if err := ValidateProjectName(s); err != nil { - return err - } - destDir := s - if outputDir != "" { - destDir = filepath.Join(outputDir, s) - } - if _, err := os.Stat(destDir); err == nil { - return fmt.Errorf("directory %s already exists", destDir) - } - return nil + return validateProjectNameForPrompt(s, outputDir) }). WithTheme(theme). Run() @@ -205,6 +313,40 @@ func PromptForProjectName(ctx context.Context, outputDir string) (string, error) return name, nil } +// PromptScaffoldLocation asks where the new app should be scaffolded when the +// current directory is suitable for in-place use. Defaults to a new +// subdirectory so users who hit Enter through the prompts get today's +// behaviour. Returns true when the user opts in to in-place scaffolding. +func PromptScaffoldLocation(ctx context.Context, basename string) (bool, error) { + theme := AppkitTheme() + + const ( + locSubdir = "subdir" + locCurrent = "current" + ) + + choice := locSubdir + err := huh.NewSelect[string](). + Title("Where should we create the app?"). + Options( + huh.NewOption("Create a new subdirectory", locSubdir), + huh.NewOption(fmt.Sprintf("Use the current directory (%q)", basename), locCurrent), + ). + Value(&choice). + WithTheme(theme). + Run() + if err != nil { + return false, err + } + + if choice == locCurrent { + printAnswered(ctx, "Location", "current directory") + return true, nil + } + printAnswered(ctx, "Location", "new subdirectory") + return false, nil +} + // PromptForDeployAndRun prompts for post-creation deploy and run options. func PromptForDeployAndRun(ctx context.Context) (deploy bool, runMode RunMode, err error) { theme := AppkitTheme() @@ -1048,7 +1190,9 @@ func PromptForAppSelection(ctx context.Context, title string) (string, error) { // PrintSuccess prints a success message after project creation. // If nextStepsCmd is non-empty, also prints the "Next steps" section with the given command. -func PrintSuccess(ctx context.Context, projectName, outputDir string, fileCount int, nextStepsCmd string) { +// When inPlace is true, the "cd " line is omitted because the +// user is already in the destination directory. +func PrintSuccess(ctx context.Context, projectName, outputDir string, fileCount int, nextStepsCmd string, inPlace bool) { successStyle := lipgloss.NewStyle(). Foreground(colorYellow). Bold(true) @@ -1069,7 +1213,9 @@ func PrintSuccess(ctx context.Context, projectName, outputDir string, fileCount cmdio.LogString(ctx, "") cmdio.LogString(ctx, dimStyle.Render(" Next steps:")) cmdio.LogString(ctx, "") - cmdio.LogString(ctx, codeStyle.Render(" cd "+projectName)) + if !inPlace { + cmdio.LogString(ctx, codeStyle.Render(" cd "+projectName)) + } cmdio.LogString(ctx, codeStyle.Render(" "+nextStepsCmd)) } cmdio.LogString(ctx, "") diff --git a/libs/apps/prompt/prompt_test.go b/libs/apps/prompt/prompt_test.go index 01091cf72ce..da92a679213 100644 --- a/libs/apps/prompt/prompt_test.go +++ b/libs/apps/prompt/prompt_test.go @@ -3,6 +3,8 @@ package prompt import ( "context" "errors" + "os" + "path/filepath" "testing" "time" @@ -87,6 +89,11 @@ func TestValidateProjectName(t *testing.T) { expectError: true, errorMsg: "lowercase letters, numbers, or hyphens", }, + { + name: "in-place sentinel", + projectName: InPlaceName, + expectError: false, + }, } for _, tt := range tests { @@ -332,3 +339,187 @@ func TestRenderStabilityTier(t *testing.T) { }) } } + +// mkDirNamed creates a child directory with the given name under t.TempDir() +// and returns its absolute path. Used to control filepath.Base(absCwd) when +// exercising in-place name derivation. +func mkDirNamed(t *testing.T, name string) string { + t.Helper() + dir := filepath.Join(t.TempDir(), name) + require.NoError(t, os.MkdirAll(dir, 0o755)) + return dir +} + +func TestDeriveInPlaceAppName(t *testing.T) { + t.Run("valid basename", func(t *testing.T) { + dir := mkDirNamed(t, "my-app") + got, err := DeriveInPlaceAppName(dir) + require.NoError(t, err) + assert.Equal(t, "my-app", got) + }) + + t.Run("uppercase basename rejected", func(t *testing.T) { + dir := mkDirNamed(t, "MyApp") + _, err := DeriveInPlaceAppName(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "MyApp") + assert.Contains(t, err.Error(), "rename the directory") + }) + + t.Run("underscore basename rejected", func(t *testing.T) { + dir := mkDirNamed(t, "my_app") + _, err := DeriveInPlaceAppName(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "my_app") + }) + + t.Run("leading digit basename rejected", func(t *testing.T) { + dir := mkDirNamed(t, "1app") + _, err := DeriveInPlaceAppName(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "1app") + }) + + t.Run("too long basename rejected", func(t *testing.T) { + dir := mkDirNamed(t, "this-is-far-too-long-for-the-app-name-limit") + _, err := DeriveInPlaceAppName(dir) + require.Error(t, err) + }) +} + +func TestCheckInPlaceDirectory(t *testing.T) { + t.Run("empty directory is OK", func(t *testing.T) { + dir := t.TempDir() + assert.NoError(t, CheckInPlaceDirectory(dir)) + }) + + t.Run("dotgit only is OK", func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) + assert.NoError(t, CheckInPlaceDirectory(dir)) + }) + + t.Run("pre-existing gitignore is rejected", func(t *testing.T) { + // .gitignore is intentionally NOT allow-listed: the template ships + // _gitignore that renames to .gitignore, which would silently + // overwrite the user's file. + dir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("node_modules\n"), 0o644)) + err := CheckInPlaceDirectory(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") + }) + + t.Run("unexpected file is rejected", func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "README.md"), []byte("hi"), 0o644)) + err := CheckInPlaceDirectory(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") + assert.Contains(t, err.Error(), "apps init --name .") + // Concise wording: we deliberately do not enumerate offending files. + assert.NotContains(t, err.Error(), "README.md") + }) + + t.Run("mix of allowed and disallowed is rejected", func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "stray.txt"), []byte(""), 0o644)) + err := CheckInPlaceDirectory(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") + }) + + t.Run("symlink named .git is rejected", func(t *testing.T) { + // A symlink masquerading as .git would let os.WriteFile follow the + // link if any allow-listed name later became a write target. + dir := t.TempDir() + target := filepath.Join(t.TempDir(), "elsewhere") + require.NoError(t, os.MkdirAll(target, 0o755)) + require.NoError(t, os.Symlink(target, filepath.Join(dir, ".git"))) + err := CheckInPlaceDirectory(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), "not empty") + }) + + t.Run("error message shows absolute path", func(t *testing.T) { + dir := t.TempDir() + require.NoError(t, os.WriteFile(filepath.Join(dir, "stray.txt"), []byte(""), 0o644)) + err := CheckInPlaceDirectory(dir) + require.Error(t, err) + assert.Contains(t, err.Error(), dir) + }) + + t.Run("missing directory returns error", func(t *testing.T) { + err := CheckInPlaceDirectory(filepath.Join(t.TempDir(), "nope")) + require.Error(t, err) + }) +} + +func TestShouldOfferInPlace(t *testing.T) { + t.Run("returns basename when dir is empty and name is valid", func(t *testing.T) { + dir := mkDirNamed(t, "my-app") + name, ok := ShouldOfferInPlace(dir) + assert.True(t, ok) + assert.Equal(t, "my-app", name) + }) + + t.Run("declines when dir has stray files", func(t *testing.T) { + dir := mkDirNamed(t, "my-app") + require.NoError(t, os.WriteFile(filepath.Join(dir, "stray.txt"), []byte(""), 0o644)) + _, ok := ShouldOfferInPlace(dir) + assert.False(t, ok) + }) + + t.Run("declines when basename is invalid", func(t *testing.T) { + dir := mkDirNamed(t, "Bad_Name") + _, ok := ShouldOfferInPlace(dir) + assert.False(t, ok) + }) + + t.Run("declines when dir does not exist", func(t *testing.T) { + _, ok := ShouldOfferInPlace(filepath.Join(t.TempDir(), "missing")) + assert.False(t, ok) + }) +} + +func TestValidateProjectNameForPrompt(t *testing.T) { + t.Run("valid name without outputDir", func(t *testing.T) { + assert.NoError(t, validateProjectNameForPrompt("my-app", "")) + }) + + t.Run("in-place sentinel without outputDir is accepted", func(t *testing.T) { + assert.NoError(t, validateProjectNameForPrompt(InPlaceName, "")) + }) + + t.Run("in-place sentinel with outputDir is rejected with sentinel error", func(t *testing.T) { + err := validateProjectNameForPrompt(InPlaceName, "/some/dir") + require.Error(t, err) + assert.ErrorIs(t, err, ErrNameDotWithOutputDir) + }) + + t.Run("invalid name surfaces ValidateProjectName error", func(t *testing.T) { + err := validateProjectNameForPrompt("My_App", "") + require.Error(t, err) + assert.Contains(t, err.Error(), "lowercase letters") + }) +} + +func TestPrintSuccessInPlace(t *testing.T) { + ctx, out := cmdio.NewTestContextWithStderr(t.Context()) + PrintSuccess(ctx, "my-app", "/abs/path/my-app", 12, "npm run dev", true) + got := out.String() + assert.Contains(t, got, "Location: /abs/path/my-app") + assert.Contains(t, got, "Files: 12") + assert.Contains(t, got, "npm run dev") + assert.NotContains(t, got, "cd my-app") +} + +func TestPrintSuccessNotInPlace(t *testing.T) { + ctx, out := cmdio.NewTestContextWithStderr(t.Context()) + PrintSuccess(ctx, "my-app", "/abs/path/my-app", 12, "npm run dev", false) + got := out.String() + assert.Contains(t, got, "cd my-app") + assert.Contains(t, got, "npm run dev") +} diff --git a/libs/apps/runlocal/spec_test.go b/libs/apps/runlocal/spec_test.go index 3b3cfde51aa..f4218c09e41 100644 --- a/libs/apps/runlocal/spec_test.go +++ b/libs/apps/runlocal/spec_test.go @@ -28,11 +28,11 @@ func TestAppSpecLoadEnvVars(t *testing.T) { EnvVars: []AppEnvVar{ { Name: "VAR1", - Value: stringPtr("value1"), + Value: new("value1"), }, { Name: "VAR2", - Value: stringPtr("value2"), + Value: new("value2"), }, }, } @@ -53,11 +53,11 @@ func TestAppSpecLoadEnvVars(t *testing.T) { EnvVars: []AppEnvVar{ { Name: "VAR1", - ValueFrom: stringPtr("VAR1"), + ValueFrom: new("VAR1"), }, { Name: "VAR2", - ValueFrom: stringPtr("VAR2"), + ValueFrom: new("VAR2"), }, }, } @@ -77,11 +77,11 @@ func TestAppSpecLoadEnvVars(t *testing.T) { EnvVars: []AppEnvVar{ { Name: "VAR1", - Value: stringPtr("value1"), + Value: new("value1"), }, { Name: "VAR2", - ValueFrom: stringPtr("VAR2"), + ValueFrom: new("VAR2"), }, }, } @@ -101,7 +101,7 @@ func TestAppSpecLoadEnvVars(t *testing.T) { EnvVars: []AppEnvVar{ { Name: "VAR1", - ValueFrom: stringPtr("VAR1"), + ValueFrom: new("VAR1"), }, }, } @@ -121,7 +121,7 @@ func TestAppSpecLoadEnvVars(t *testing.T) { EnvVars: []AppEnvVar{ { Name: "VAR1", - ValueFrom: stringPtr("MISSING_VAR"), + ValueFrom: new("MISSING_VAR"), }, }, } @@ -149,8 +149,3 @@ func TestAppSpecLoadEnvVars(t *testing.T) { }) } } - -// Helper function to create a string pointer -func stringPtr(s string) *string { - return &s -} diff --git a/libs/auth/credentials_test.go b/libs/auth/credentials_test.go index 3ce2e4b0c4e..20c55e4d056 100644 --- a/libs/auth/credentials_test.go +++ b/libs/auth/credentials_test.go @@ -96,18 +96,18 @@ func TestAuthArgumentsFromConfig(t *testing.T) { { name: "all fields", cfg: &config.Config{ - Host: "https://myhost.com", + Host: "https://myhost.test", AccountID: "acc-123", WorkspaceID: "ws-456", Profile: "my-profile", - DiscoveryURL: "https://myhost.com/oidc/accounts/acc-123/.well-known/oauth-authorization-server", + DiscoveryURL: "https://myhost.test/oidc/accounts/acc-123/.well-known/oauth-authorization-server", }, want: AuthArguments{ - Host: "https://myhost.com", + Host: "https://myhost.test", AccountID: "acc-123", WorkspaceID: "ws-456", Profile: "my-profile", - DiscoveryURL: "https://myhost.com/oidc/accounts/acc-123/.well-known/oauth-authorization-server", + DiscoveryURL: "https://myhost.test/oidc/accounts/acc-123/.well-known/oauth-authorization-server", }, }, } diff --git a/libs/auth/env_test.go b/libs/auth/env_test.go index b1a41187f7d..4cb706c7795 100644 --- a/libs/auth/env_test.go +++ b/libs/auth/env_test.go @@ -10,7 +10,7 @@ import ( func TestAuthEnv(t *testing.T) { in := &config.Config{ Profile: "myprofile", - Host: "https://test.com", + Host: "https://test.test", Token: "test-token", Password: "test-password", MetadataServiceURL: "http://somurl.com", @@ -25,7 +25,7 @@ func TestAuthEnv(t *testing.T) { expected := map[string]string{ "DATABRICKS_CONFIG_PROFILE": "myprofile", - "DATABRICKS_HOST": "https://test.com", + "DATABRICKS_HOST": "https://test.test", "DATABRICKS_TOKEN": "test-token", "DATABRICKS_PASSWORD": "test-password", "DATABRICKS_METADATA_SERVICE_URL": "http://somurl.com", diff --git a/libs/auth/storage/cache.go b/libs/auth/storage/cache.go index 151646081d3..c3d1fc43dcf 100644 --- a/libs/auth/storage/cache.go +++ b/libs/auth/storage/cache.go @@ -2,8 +2,12 @@ package storage import ( "context" + "errors" "fmt" + "github.com/databricks/cli/libs/databrickscfg" + "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" ) @@ -12,15 +16,19 @@ import ( // so unit tests can inject stubs without hitting the real OS keyring or // filesystem. Production code uses defaultCacheFactories(). type cacheFactories struct { - newFile func(context.Context) (cache.TokenCache, error) - newKeyring func() cache.TokenCache + newFile func(context.Context) (cache.TokenCache, error) + newKeyring func() cache.TokenCache + probeKeyring func() error + probeKeyringRead func() error } // defaultCacheFactories returns the production factory set. func defaultCacheFactories() cacheFactories { return cacheFactories{ - newFile: func(ctx context.Context) (cache.TokenCache, error) { return NewFileTokenCache(ctx) }, - newKeyring: NewKeyringCache, + newFile: func(ctx context.Context) (cache.TokenCache, error) { return NewFileTokenCache(ctx) }, + newKeyring: NewKeyringCache, + probeKeyring: ProbeKeyring, + probeKeyringRead: ProbeKeyringRead, } } @@ -31,11 +39,48 @@ func defaultCacheFactories() cacheFactories { // override is usually the command-level flag value. Pass "" when the command // has no flag; precedence then falls through to env -> config -> default. // +// When the resolver returns (mode=Secure, source=Default) and the OS +// keyring is definitively unreachable (a non-timeout probe error), reads +// fall back to the plaintext file cache so post-upgrade users with legacy +// token-cache.json entries are not stranded. Unlike the login path, this +// fallback does not persist auth_storage = plaintext to [__settings__]; +// pinning happens only on successful login. +// // Every CLI code path that calls u2m.NewPersistentAuth must route the result // through u2m.WithTokenCache, otherwise the SDK defaults to the file cache // and splits the user's tokens across two backends. func ResolveCache(ctx context.Context, override StorageMode) (cache.TokenCache, StorageMode, error) { - return resolveCacheWith(ctx, override, defaultCacheFactories()) + inner, mode, err := resolveCacheForReadWith(ctx, override, defaultCacheFactories()) + if err != nil { + return nil, "", err + } + return withNotFoundHint(ctx, inner, mode), mode, nil +} + +// ResolveCacheForLogin resolves the cache like ResolveCache with extra rules +// for the auth login path: +// +// 1. When the resolved mode is secure and the user did not explicitly ask +// for it (no override flag, no env var, no config), and the OS keyring +// is unreachable, fall back silently to plaintext and persist +// auth_storage = plaintext to [__settings__] so subsequent commands +// skip the (slow/blocking) probe and route directly to the file cache. +// 2. When the user explicitly asked for secure (override, env var, or +// config) but the keyring is unreachable, return an error. An explicit +// "I want secure" is honored strictly: never silently downgrade. +// 3. When the probe times out, stay on keyring regardless of explicit. +// The timeout is ambiguous (locked vs hung); a misdiagnosis fails +// the final Store rather than silently downgrading to plaintext. +// +// Login-specific. Read paths (auth token, bundle commands) keep the original +// keyring error so they don't silently mint plaintext copies of tokens that +// were stored in the keyring on another machine. +func ResolveCacheForLogin(ctx context.Context, override StorageMode) (cache.TokenCache, StorageMode, error) { + inner, mode, err := resolveCacheForLoginWith(ctx, override, defaultCacheFactories()) + if err != nil { + return nil, "", err + } + return withNotFoundHint(ctx, inner, mode), mode, nil } // WrapForOAuthArgument wraps tokenCache so SDK-side writes (Challenge, refresh) @@ -53,8 +98,11 @@ func WrapForOAuthArgument(tokenCache cache.TokenCache, mode StorageMode, arg u2m return NewDualWritingTokenCache(tokenCache, arg) } -// resolveCacheWith is the pure form of ResolveCache. It takes the factory -// set as a parameter so tests can inject stubs. +// resolveCacheWith is the pure form of ResolveCache without the read-path +// fallback. Takes the factory set as a parameter so tests can inject stubs. +// Used directly by ResolveCacheForLogin (which has its own fallback rules) +// and indirectly by ResolveCache (which adds the read-path fallback in +// resolveCacheForReadWith). func resolveCacheWith(ctx context.Context, override StorageMode, f cacheFactories) (cache.TokenCache, StorageMode, error) { mode, err := ResolveStorageMode(ctx, override) if err != nil { @@ -73,3 +121,156 @@ func resolveCacheWith(ctx context.Context, override StorageMode, f cacheFactorie return nil, "", fmt.Errorf("unsupported storage mode %q", string(mode)) } } + +// resolveCacheForReadWith is the pure form of ResolveCache. It applies the +// read-path fallback: when mode is secure-from-default and the keyring +// probes as definitively unavailable, return the file cache instead. +// Timeouts keep the keyring (could be transient). +func resolveCacheForReadWith(ctx context.Context, override StorageMode, f cacheFactories) (cache.TokenCache, StorageMode, error) { + mode, source, err := ResolveStorageModeWithSource(ctx, override) + if err != nil { + return nil, "", err + } + return applyReadFallback(ctx, mode, source.Explicit(), f) +} + +// applyReadFallback realizes the read-path fallback. Mirrors +// applyLoginFallback but: +// +// - Uses a read-only probe (ProbeKeyringRead) so calls do not write to +// the keyring on every CLI invocation. +// - Does not persist auth_storage = plaintext. Pinning happens only on +// successful login, where the write-probe gives us stronger evidence +// that the keyring is truly unavailable on this machine. +// +// Explicit secure is honored: callers who asked for secure get the keyring +// cache even if the probe fails, so the actual Lookup error surfaces the +// unreachability instead of silently using a different backend. +func applyReadFallback(ctx context.Context, mode StorageMode, explicit bool, f cacheFactories) (cache.TokenCache, StorageMode, error) { + switch mode { + case StorageModePlaintext: + c, err := f.newFile(ctx) + if err != nil { + return nil, "", fmt.Errorf("open file token cache: %w", err) + } + return c, mode, nil + case StorageModeSecure: + if explicit { + return f.newKeyring(), mode, nil + } + if probeErr := f.probeKeyringRead(); probeErr != nil { + var timeoutErr *TimeoutError + if errors.As(probeErr, &timeoutErr) { + log.Debugf(ctx, "keyring read probe timed out (%v); staying on keyring", probeErr) + return f.newKeyring(), mode, nil + } + log.Debugf(ctx, "secure storage unavailable on read path (%v), using file cache", probeErr) + fileCache, fileErr := f.newFile(ctx) + if fileErr != nil { + return nil, "", fmt.Errorf("open file token cache: %w", fileErr) + } + return fileCache, StorageModePlaintext, nil + } + return f.newKeyring(), mode, nil + default: + return nil, "", fmt.Errorf("unsupported storage mode %q", string(mode)) + } +} + +// resolveCacheForLoginWith is the pure form of ResolveCacheForLogin. It takes +// the factory set as a parameter so tests can inject stubs. +func resolveCacheForLoginWith(ctx context.Context, override StorageMode, f cacheFactories) (cache.TokenCache, StorageMode, error) { + mode, source, err := ResolveStorageModeWithSource(ctx, override) + if err != nil { + return nil, "", err + } + return applyLoginFallback(ctx, mode, source.Explicit(), f) +} + +// applyLoginFallback realizes the login-time fallback rules given an already- +// resolved mode and whether the user explicitly asked for it. Split out so +// tests can drive the (mode, explicit) input space directly without depending +// on whatever the resolver's default mode happens to be at any point in time. +func applyLoginFallback(ctx context.Context, mode StorageMode, explicit bool, f cacheFactories) (cache.TokenCache, StorageMode, error) { + switch mode { + case StorageModePlaintext: + c, err := f.newFile(ctx) + if err != nil { + return nil, "", fmt.Errorf("open file token cache: %w", err) + } + return c, mode, nil + case StorageModeSecure: + if probeErr := f.probeKeyring(); probeErr != nil { + // Stay on keyring on timeout: a locked keyring being unlocked + // during OAuth is the common case, and a misdiagnosed hang + // fails the final Store anyway, which is better than a + // silent plaintext downgrade. + var timeoutErr *TimeoutError + if errors.As(probeErr, &timeoutErr) { + log.Debugf(ctx, "keyring probe timed out (%v); staying on keyring", probeErr) + return f.newKeyring(), mode, nil + } + if explicit { + return nil, "", fmt.Errorf("secure storage was requested but the OS keyring is not reachable: %w", probeErr) + } + log.Debugf(ctx, "secure storage unavailable (%v), falling back to plaintext", probeErr) + fileCache, fileErr := f.newFile(ctx) + if fileErr != nil { + return nil, "", fmt.Errorf("open file token cache: %w", fileErr) + } + persistPlaintextFallback(ctx) + return fileCache, StorageModePlaintext, nil + } + return f.newKeyring(), mode, nil + default: + return nil, "", fmt.Errorf("unsupported storage mode %q", string(mode)) + } +} + +// persistPlaintextFallback writes auth_storage = plaintext to [__settings__] +// in .databrickscfg so subsequent commands skip the (slow/blocking) keyring +// probe and route straight to the file cache. +// +// Only called on the (mode=Secure, explicit=false) probe-failure branch. +// Best-effort: persistence failures are logged at debug and never block +// login. +func persistPlaintextFallback(ctx context.Context) { + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + if err := databrickscfg.SetConfiguredAuthStorage(ctx, string(StorageModePlaintext), configPath); err != nil { + log.Debugf(ctx, "persist auth_storage=plaintext fallback failed: %v", err) + } +} + +// PinSecureMode persists auth_storage = secure to [__settings__] when the +// user is currently on the secure-from-default path. Once pinned, subsequent +// invocations see source=Config (explicit), so applyLoginFallback returns an +// error on a transient keyring probe failure instead of silently demoting +// the user to plaintext. +// +// No-op when mode is not secure or when the user already chose a mode +// explicitly via override, env var, or config. override must be the same +// value the caller passed to ResolveCacheForLogin so the source check sees +// the caller's intent rather than re-resolving without it. +// +// Persistence failures are logged at warn: they do not block login, but +// the user should know the pin did not happen, since a later transient +// keyring failure could then silently route a default-secure user to +// plaintext. Concurrent logins racing this write is benign because both +// write the same value. +func PinSecureMode(ctx context.Context, mode, override StorageMode) { + if mode != StorageModeSecure { + return + } + _, source, err := ResolveStorageModeWithSource(ctx, override) + if err != nil { + log.Debugf(ctx, "pin secure mode: resolve: %v", err) + return + } + if source.Explicit() { + return + } + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + if err := databrickscfg.SetConfiguredAuthStorage(ctx, string(StorageModeSecure), configPath); err != nil { + log.Warnf(ctx, "could not persist auth_storage=secure to %s: %v. Future commands may need DATABRICKS_AUTH_STORAGE=secure to keep using the OS keyring.", configPath, err) + } +} diff --git a/libs/auth/storage/cache_test.go b/libs/auth/storage/cache_test.go index b84c1ef3ba0..52044eef986 100644 --- a/libs/auth/storage/cache_test.go +++ b/libs/auth/storage/cache_test.go @@ -3,9 +3,13 @@ package storage import ( "context" "errors" + "fmt" + "io/fs" + "os" "path/filepath" "testing" + "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/env" "github.com/databricks/databricks-sdk-go/credentials/u2m" "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" @@ -24,8 +28,10 @@ func (stubCache) Lookup(string) (*oauth2.Token, error) { return nil, cache.ErrNo func fakeFactories(t *testing.T) cacheFactories { t.Helper() return cacheFactories{ - newFile: func(context.Context) (cache.TokenCache, error) { return stubCache{source: "file"}, nil }, - newKeyring: func() cache.TokenCache { return stubCache{source: "keyring"} }, + newFile: func(context.Context) (cache.TokenCache, error) { return stubCache{source: "file"}, nil }, + newKeyring: func() cache.TokenCache { return stubCache{source: "keyring"} }, + probeKeyring: func() error { return nil }, + probeKeyringRead: func() error { return nil }, } } @@ -37,15 +43,15 @@ func hermetic(t *testing.T) { t.Setenv("DATABRICKS_CONFIG_FILE", filepath.Join(t.TempDir(), "databrickscfg")) } -func TestResolveCache_DefaultsToPlaintextFile(t *testing.T) { +func TestResolveCache_DefaultsToSecureKeyring(t *testing.T) { hermetic(t) ctx := t.Context() got, mode, err := resolveCacheWith(ctx, "", fakeFactories(t)) require.NoError(t, err) - assert.Equal(t, StorageModePlaintext, mode) - assert.Equal(t, "file", got.(stubCache).source) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) } func TestResolveCache_OverrideSecureUsesKeyring(t *testing.T) { @@ -106,8 +112,10 @@ func TestResolveCache_FileFactoryErrorPropagates(t *testing.T) { ctx := t.Context() boom := errors.New("disk full") factories := cacheFactories{ - newFile: func(context.Context) (cache.TokenCache, error) { return nil, boom }, - newKeyring: func() cache.TokenCache { return stubCache{source: "keyring"} }, + newFile: func(context.Context) (cache.TokenCache, error) { return nil, boom }, + newKeyring: func() cache.TokenCache { return stubCache{source: "keyring"} }, + probeKeyring: func() error { return nil }, + probeKeyringRead: func() error { return nil }, } _, _, err := resolveCacheWith(ctx, StorageModePlaintext, factories) @@ -116,6 +124,362 @@ func TestResolveCache_FileFactoryErrorPropagates(t *testing.T) { assert.ErrorIs(t, err, boom) } +// applyReadFallback mirrors applyLoginFallback's logic on the read path: +// keyring is probed read-only, definitive failures fall through to the file +// cache so legacy plaintext tokens stay reachable, timeouts stay on the +// keyring, and explicit-secure is honored even when the probe fails. + +func TestApplyReadFallback_PlaintextSkipsProbe(t *testing.T) { + hermetic(t) + ctx := t.Context() + probed := false + f := fakeFactories(t) + f.probeKeyringRead = func() error { + probed = true + return nil + } + + got, mode, err := applyReadFallback(ctx, StorageModePlaintext, false, f) + + require.NoError(t, err) + assert.Equal(t, StorageModePlaintext, mode) + assert.Equal(t, "file", got.(stubCache).source) + assert.False(t, probed, "probe must not run when mode is already plaintext") +} + +func TestApplyReadFallback_ExplicitSecureSkipsProbe(t *testing.T) { + hermetic(t) + ctx := t.Context() + probed := false + f := fakeFactories(t) + f.probeKeyringRead = func() error { + probed = true + return errors.New("unreachable") + } + + got, mode, err := applyReadFallback(ctx, StorageModeSecure, true, f) + + require.NoError(t, err) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) + assert.False(t, probed, "probe must not run when user is explicit about secure mode") +} + +func TestApplyReadFallback_DefaultSecure_ProbeOK_UsesKeyring(t *testing.T) { + hermetic(t) + ctx := t.Context() + + got, mode, err := applyReadFallback(ctx, StorageModeSecure, false, fakeFactories(t)) + + require.NoError(t, err) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) +} + +func TestApplyReadFallback_DefaultSecure_ProbeFail_FallsBack(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyringRead = func() error { return errors.New("no keyring") } + + got, mode, err := applyReadFallback(ctx, StorageModeSecure, false, f) + + require.NoError(t, err) + assert.Equal(t, StorageModePlaintext, mode) + assert.Equal(t, "file", got.(stubCache).source) + + // Read-path fallback must NOT pin: pinning is reserved for login, + // where the write-probe gives stronger evidence of unavailability. + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "", persisted, "read-path fallback must not persist auth_storage") +} + +// A timeout could mean a locked keyring that will work once the user unlocks +// it. Stay on the keyring so the actual Lookup surfaces the real outcome +// rather than silently routing reads to the file cache. +func TestApplyReadFallback_DefaultSecure_ProbeTimeout_StaysOnKeyring(t *testing.T) { + cases := []struct { + name string + probeErr error + }{ + {"bare TimeoutError", &TimeoutError{Op: "get"}}, + {"wrapped TimeoutError", fmt.Errorf("get: %w", &TimeoutError{Op: "get"})}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyringRead = func() error { return tc.probeErr } + + got, mode, err := applyReadFallback(ctx, StorageModeSecure, false, f) + + require.NoError(t, err) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) + + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "", persisted, "probe timeout must not persist anything") + }) + } +} + +func TestResolveCacheForLogin_PlaintextSkipsProbe(t *testing.T) { + hermetic(t) + ctx := t.Context() + probed := false + f := fakeFactories(t) + f.probeKeyring = func() error { + probed = true + return nil + } + + got, mode, err := resolveCacheForLoginWith(ctx, StorageModePlaintext, f) + + require.NoError(t, err) + assert.Equal(t, StorageModePlaintext, mode) + assert.Equal(t, "file", got.(stubCache).source) + assert.False(t, probed, "probe must not run when mode is already plaintext") +} + +func TestResolveCacheForLogin_SecureProbeOK(t *testing.T) { + hermetic(t) + ctx := env.Set(t.Context(), EnvVar, "secure") + + got, mode, err := resolveCacheForLoginWith(ctx, "", fakeFactories(t)) + + require.NoError(t, err) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) +} + +func TestResolveCacheForLogin_ExplicitEnvSecure_ProbeFail_Errors(t *testing.T) { + hermetic(t) + ctx := env.Set(t.Context(), EnvVar, "secure") + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyring = func() error { return errors.New("no keyring") } + + _, _, err := resolveCacheForLoginWith(ctx, "", f) + require.Error(t, err) + assert.ErrorContains(t, err, "secure storage was requested") + + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "", persisted, "env-set secure must not be persisted as plaintext") +} + +func TestResolveCacheForLogin_ExplicitConfigSecure_ProbeFail_Errors(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + require.NoError(t, os.WriteFile(configPath, []byte("[__settings__]\nauth_storage = secure\n"), 0o600)) + + f := fakeFactories(t) + f.probeKeyring = func() error { return errors.New("no keyring") } + + _, _, err := resolveCacheForLoginWith(ctx, "", f) + require.Error(t, err) + assert.ErrorContains(t, err, "secure storage was requested") + + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "secure", persisted, "config-set secure must not be silently rewritten") +} + +func TestResolveCacheForLogin_ExplicitOverrideSecure_ProbeFail_Errors(t *testing.T) { + hermetic(t) + ctx := t.Context() + + f := fakeFactories(t) + f.probeKeyring = func() error { return errors.New("no keyring") } + + _, _, err := resolveCacheForLoginWith(ctx, StorageModeSecure, f) + require.Error(t, err) + assert.ErrorContains(t, err, "secure storage was requested") +} + +func TestApplyLoginFallback_DefaultSecure_ProbeFail_FallsBackAndPersists(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyring = func() error { return errors.New("no keyring") } + + got, mode, err := applyLoginFallback(ctx, StorageModeSecure, false, f) + + require.NoError(t, err) + assert.Equal(t, StorageModePlaintext, mode) + assert.Equal(t, "file", got.(stubCache).source) + + persisted, err := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, err) + assert.Equal(t, "plaintext", persisted, "default-mode fallback must persist auth_storage = plaintext") +} + +func TestApplyLoginFallback_ExplicitSecure_ProbeFail_Errors(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyring = func() error { return errors.New("no keyring") } + + _, _, err := applyLoginFallback(ctx, StorageModeSecure, true, f) + require.Error(t, err) + assert.ErrorContains(t, err, "secure storage was requested") + + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "", persisted, "explicit-secure error must not write config") +} + +// A locked keyring with a slow user surfaces as TimeoutError. We want login +// to stay on the keyring so the final Store lands there once the user has +// finished unlocking, regardless of whether secure was explicit. Cover both +// the bare TimeoutError (in case probe wraps thinner in the future) and the +// real wrapped form returned by probeWithBackend. +func TestApplyLoginFallback_ProbeTimeout_StaysOnKeyring(t *testing.T) { + cases := []struct { + name string + explicit bool + probeErr error + }{ + {"default-secure, bare TimeoutError", false, &TimeoutError{Op: "set"}}, + {"default-secure, wrapped TimeoutError", false, fmt.Errorf("write: %w", &TimeoutError{Op: "set"})}, + {"explicit-secure, bare TimeoutError", true, &TimeoutError{Op: "set"}}, + {"explicit-secure, wrapped TimeoutError", true, fmt.Errorf("write: %w", &TimeoutError{Op: "set"})}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + f := fakeFactories(t) + f.probeKeyring = func() error { return tc.probeErr } + + got, mode, err := applyLoginFallback(ctx, StorageModeSecure, tc.explicit, f) + + require.NoError(t, err) + assert.Equal(t, StorageModeSecure, mode) + assert.Equal(t, "keyring", got.(stubCache).source) + + persisted, gerr := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, gerr) + assert.Equal(t, "", persisted, "probe timeout must not persist plaintext fallback") + }) + } +} + +func TestPinSecureMode(t *testing.T) { + cases := []struct { + name string + mode StorageMode + override StorageMode + envValue string + configBody string + wantWritten string + }{ + { + name: "secure from default persists secure", + mode: StorageModeSecure, + wantWritten: "secure", + }, + { + name: "plaintext mode is a no-op", + mode: StorageModePlaintext, + wantWritten: "", + }, + { + name: "secure from env is a no-op", + mode: StorageModeSecure, + envValue: "secure", + wantWritten: "", + }, + { + name: "secure from config is a no-op (already pinned)", + mode: StorageModeSecure, + configBody: "[__settings__]\nauth_storage = secure\n", + wantWritten: "secure", + }, + { + // The override signal is per-invocation, so persisting it to + // config would silently turn an ephemeral choice into a + // persistent one. Honor the caller's explicit override by + // no-op'ing the pin. + name: "secure from override is a no-op", + mode: StorageModeSecure, + override: StorageModeSecure, + wantWritten: "", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + if tc.configBody != "" { + require.NoError(t, os.WriteFile(configPath, []byte(tc.configBody), 0o600)) + } + if tc.envValue != "" { + ctx = env.Set(ctx, EnvVar, tc.envValue) + } + + PinSecureMode(ctx, tc.mode, tc.override) + + got, err := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, err) + assert.Equal(t, tc.wantWritten, got) + }) + } +} + +func TestPinSecureMode_IsIdempotent(t *testing.T) { + hermetic(t) + ctx := t.Context() + configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") + + PinSecureMode(ctx, StorageModeSecure, StorageModeUnknown) + first, err := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, err) + require.Equal(t, "secure", first) + + // Second call should see source=Config and skip the write. + PinSecureMode(ctx, StorageModeSecure, StorageModeUnknown) + second, err := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) + require.NoError(t, err) + assert.Equal(t, "secure", second) +} + +func TestPinSecureMode_PersistFailureIsSwallowed(t *testing.T) { + hermetic(t) + ctx := t.Context() + // Point DATABRICKS_CONFIG_FILE at a path whose parent does not exist. + // loadOrCreateConfigFile does not mkdir, so the underlying os.OpenFile + // fails and SetConfiguredAuthStorage returns an error. + configPath := filepath.Join(t.TempDir(), "no-such-dir", ".databrickscfg") + t.Setenv("DATABRICKS_CONFIG_FILE", configPath) + + // Must not panic or block; failure surfaces in the warn log. + PinSecureMode(ctx, StorageModeSecure, StorageModeUnknown) + + // The persist failure must not have produced any file. + _, err := os.Stat(configPath) + assert.ErrorIs(t, err, fs.ErrNotExist, "no file should have been written") +} + func TestWrapForOAuthArgument(t *testing.T) { const ( host = "https://example.com" diff --git a/libs/auth/storage/keyring.go b/libs/auth/storage/keyring.go index e9fc7d13dfa..e0099ebe6bd 100644 --- a/libs/auth/storage/keyring.go +++ b/libs/auth/storage/keyring.go @@ -8,6 +8,7 @@ import ( "time" "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" + "github.com/google/uuid" "github.com/zalando/go-keyring" "golang.org/x/oauth2" ) @@ -17,6 +18,14 @@ import ( // cache key the SDK passes through TokenCache.Store / Lookup. const keyringServiceName = "databricks-cli" +// keyringProbeAccountPrefix is prefixed onto a per-call random suffix to form +// the account name ProbeKeyring writes and deletes. A fixed name like +// "__probe__" could collide with a user profile of the same name (which is +// what keyringCache uses as the account field), so the probe would clobber +// and delete that user's stored token. Per-call randomness also means +// concurrent probes don't step on each other. +const keyringProbeAccountPrefix = "__probe_" + // defaultKeyringTimeout is how long a single keyring operation is allowed // to run before the wrapper returns a TimeoutError. Matches the value used // by GitHub CLI. @@ -79,6 +88,63 @@ func NewKeyringCache() cache.TokenCache { } } +// ProbeKeyring returns nil if the OS keyring accepted a write+delete +// cycle within the standard timeout. *TimeoutError means the keyring +// was unresponsive (locked or hung, indistinguishable here); other +// errors are definitive failures. +// +// Used by the login path, where we want to validate both read and write +// capability before committing to the keyring backend. +func ProbeKeyring() error { + return probeWithBackend(zalandoBackend{}, defaultKeyringTimeout) +} + +func probeWithBackend(backend keyringBackend, timeout time.Duration) error { + c := &keyringCache{ + backend: backend, + timeout: timeout, + keyringSvcName: keyringServiceName, + } + account := keyringProbeAccountPrefix + uuid.NewString() + tok := &oauth2.Token{AccessToken: "probe"} + if err := c.Store(account, tok); err != nil { + return fmt.Errorf("write: %w", err) + } + if err := c.Store(account, nil); err != nil { + return fmt.Errorf("delete: %w", err) + } + return nil +} + +// ProbeKeyringRead returns nil if the OS keyring accepted a Get for a +// non-existent account within the standard timeout (i.e. the backend is +// reachable and responded with keyring.ErrNotFound). *TimeoutError means +// the keyring was unresponsive; other errors are definitive failures. +// +// Used by the read path so probing does not write to the keyring. A +// successful probe is indistinguishable from the user not having an +// entry for that probe account; we treat both as "reachable". +func ProbeKeyringRead() error { + return probeReadWithBackend(zalandoBackend{}, defaultKeyringTimeout) +} + +func probeReadWithBackend(backend keyringBackend, timeout time.Duration) error { + c := &keyringCache{ + backend: backend, + timeout: timeout, + keyringSvcName: keyringServiceName, + } + account := keyringProbeAccountPrefix + uuid.NewString() + err := c.withTimeout("get", func() error { + _, gerr := c.backend.Get(c.keyringSvcName, account) + return gerr + }) + if errors.Is(err, keyring.ErrNotFound) { + return nil + } + return err +} + // Store stores t under key. Nil t deletes the entry; deleting a missing // entry is not an error. func (k *keyringCache) Store(key string, t *oauth2.Token) error { @@ -115,7 +181,7 @@ func (k *keyringCache) Lookup(key string) (*oauth2.Token, error) { return nil, cache.ErrNotFound } if err != nil { - return nil, err + return nil, wrapKeyringUnreachable(err) } var entry keyringEntry @@ -125,6 +191,24 @@ func (k *keyringCache) Lookup(key string) (*oauth2.Token, error) { return entry.Token, nil } +// wrapKeyringUnreachable wraps a non-ErrNotFound keyring error with +// actionable guidance for users whose system has no usable keyring +// backend (Linux without a Secret Service / D-Bus session bus, headless +// containers, certain SSH sessions). Surfaces on the read path, where +// the resolver does not silently fall back to plaintext: a missing +// token might actually be reachable from the keyring on another machine, +// so we surface the unreachability instead of minting a fresh plaintext +// copy. +// +// ErrNotFound passes through unchanged because a clean miss is not an +// availability problem. +func wrapKeyringUnreachable(err error) error { + if err == nil || errors.Is(err, keyring.ErrNotFound) { + return err + } + return fmt.Errorf("OS keyring unreachable: %w (set DATABRICKS_AUTH_STORAGE=plaintext or run `databricks auth login` to use file-based token storage)", err) +} + // Compile-time confirmation that keyringCache satisfies the SDK interface. var _ cache.TokenCache = (*keyringCache)(nil) diff --git a/libs/auth/storage/keyring_test.go b/libs/auth/storage/keyring_test.go index 74ea3c0c63f..868e3ef5d3c 100644 --- a/libs/auth/storage/keyring_test.go +++ b/libs/auth/storage/keyring_test.go @@ -137,6 +137,25 @@ func TestKeyringCache_Lookup_PropagatesOtherErrors(t *testing.T) { _, err := c.Lookup("my-profile") require.Error(t, err) assert.ErrorIs(t, err, boom) + // The non-ErrNotFound path wraps the backend error with actionable + // guidance so users on systems without a usable keyring backend + // (e.g. headless Linux) know what to do. + assert.Contains(t, err.Error(), "OS keyring unreachable") + assert.Contains(t, err.Error(), "DATABRICKS_AUTH_STORAGE=plaintext") + assert.Contains(t, err.Error(), "databricks auth login") +} + +// ErrNotFound has to pass through unwrapped because callers branch on it +// (cache.ErrNotFound is the "no token, please log in" signal). Wrapping it +// with the unreachability hint would mislead the user. +func TestKeyringCache_Lookup_NotFoundIsNotWrapped(t *testing.T) { + backend := newFakeBackend() + c := newTestCache(backend) + + _, err := c.Lookup("nope") + require.Error(t, err) + assert.ErrorIs(t, err, cache.ErrNotFound) + assert.NotContains(t, err.Error(), "OS keyring unreachable") } func TestKeyringCache_Lookup_CorruptedJSONReturnsError(t *testing.T) { @@ -217,3 +236,119 @@ func TestKeyringCache_StoreNil_TimesOut(t *testing.T) { var timeoutErr *TimeoutError assert.ErrorAs(t, err, &timeoutErr, "expected TimeoutError, got %T: %v", err, err) } + +func TestProbeKeyring(t *testing.T) { + boom := errors.New("backend boom") + cases := []struct { + name string + setErr error + deleteErr error + setBlock bool + timeout time.Duration + wantErr error + wantTimeout bool + }{ + { + name: "success leaves no entry", + timeout: 100 * time.Millisecond, + }, + { + name: "set error propagates", + setErr: boom, + timeout: 100 * time.Millisecond, + wantErr: boom, + }, + { + name: "set times out", + setBlock: true, + timeout: 50 * time.Millisecond, + wantTimeout: true, + }, + { + name: "delete error propagates", + deleteErr: boom, + timeout: 100 * time.Millisecond, + wantErr: boom, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + backend := newFakeBackend() + backend.setErr = tc.setErr + backend.deleteErr = tc.deleteErr + backend.setBlock = tc.setBlock + + err := probeWithBackend(backend, tc.timeout) + + switch { + case tc.wantErr != nil: + require.Error(t, err) + assert.ErrorIs(t, err, tc.wantErr) + case tc.wantTimeout: + require.Error(t, err) + var timeoutErr *TimeoutError + assert.ErrorAs(t, err, &timeoutErr) + default: + require.NoError(t, err) + assert.Empty(t, backend.items, "probe must clean up after itself") + } + }) + } +} + +func TestProbeKeyringRead(t *testing.T) { + boom := errors.New("backend boom") + cases := []struct { + name string + getErr error + getBlock bool + timeout time.Duration + wantErr error + wantTimeout bool + }{ + { + // keyring.ErrNotFound is the success signal: the backend + // responded that no entry exists for our probe account, + // which means it is reachable. + name: "ErrNotFound counts as reachable", + getErr: keyring.ErrNotFound, + timeout: 100 * time.Millisecond, + }, + { + name: "other backend error propagates", + getErr: boom, + timeout: 100 * time.Millisecond, + wantErr: boom, + }, + { + name: "get times out", + getBlock: true, + timeout: 50 * time.Millisecond, + wantTimeout: true, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + backend := newFakeBackend() + backend.getErr = tc.getErr + backend.getBlock = tc.getBlock + + err := probeReadWithBackend(backend, tc.timeout) + + switch { + case tc.wantErr != nil: + require.Error(t, err) + assert.ErrorIs(t, err, tc.wantErr) + case tc.wantTimeout: + require.Error(t, err) + var timeoutErr *TimeoutError + assert.ErrorAs(t, err, &timeoutErr) + default: + require.NoError(t, err) + assert.Empty(t, backend.items, "read probe must not write to the keyring") + } + }) + } +} diff --git a/libs/auth/storage/mode.go b/libs/auth/storage/mode.go index b3dc846536b..60c285f51ad 100644 --- a/libs/auth/storage/mode.go +++ b/libs/auth/storage/mode.go @@ -1,9 +1,10 @@ // Package storage selects and constructs the CLI's U2M token storage backend. // -// Two modes are supported. Plaintext writes to ~/.databricks/token-cache.json -// with host-key dual-write for older Go SDK versions (v0.61-v0.103); it is the -// resolver default. Secure writes to the OS-native keyring under the profile -// cache key only; it is opt-in pre-GA and slated to become the default at GA. +// Two modes are supported. Secure writes to the OS-native keyring under the +// profile cache key only; it is the resolver default. Plaintext writes to +// ~/.databricks/token-cache.json with host-key dual-write for older Go SDK +// versions (v0.61-v0.103); it is the opt-in fallback for environments where +// the OS keyring is not available. package storage import ( @@ -26,18 +27,65 @@ const ( // StorageModePlaintext writes tokens to ~/.databricks/token-cache.json // and mirrors each token under the legacy host-based cache key for - // older Go SDK versions (v0.61-v0.103). This is the resolver default. + // older Go SDK versions (v0.61-v0.103). Opt-in via DATABRICKS_AUTH_STORAGE + // or [__settings__].auth_storage for environments where the OS keyring + // is not available. StorageModePlaintext StorageMode = "plaintext" // StorageModeSecure writes tokens to the OS-native secure store // (macOS Keychain, Windows Credential Manager, Linux Secret Service) // under the profile cache key only. No host-key entry is written. + // This is the resolver default. StorageModeSecure StorageMode = "secure" ) // EnvVar is the environment variable that selects the storage mode. const EnvVar = "DATABRICKS_AUTH_STORAGE" +// StorageSource identifies which precedence level produced the resolved +// storage mode. Callers use it both to decide whether the user explicitly +// asked for a mode (everything except StorageSourceDefault) and to surface +// where the choice came from in user-facing output. +type StorageSource int + +const ( + // StorageSourceDefault is the zero value: no override, env, or config + // was set, so the resolver fell through to the built-in default. + StorageSourceDefault StorageSource = iota + StorageSourceOverride + StorageSourceEnvVar + StorageSourceConfig +) + +// Explicit reports whether the source came from a user-supplied input +// (override flag, env var, or config) rather than the built-in default. +func (s StorageSource) Explicit() bool { + return s != StorageSourceDefault +} + +// String returns a human-readable label for the source, matching the style +// used by the SDK's config.Source.String() (e.g. "DATABRICKS_HOST environment +// variable"). +// +// The label for StorageSourceConfig intentionally does not name a specific +// config file: callers that know the resolved path (e.g. auth describe) +// should append it themselves to match the SDK's "from config file" +// convention. The label for StorageSourceOverride is generic because no +// CLI command currently exposes a storage-mode flag; if one is added in +// the future, that command can replace the label at the call site. +func (s StorageSource) String() string { + switch s { + case StorageSourceOverride: + return "command-line override" + case StorageSourceEnvVar: + return EnvVar + " environment variable" + case StorageSourceConfig: + return "auth_storage in [__settings__] section of config file" + default: + return "default" + } +} + // ParseMode parses raw as a StorageMode. Whitespace is trimmed and matching // is case-insensitive. Empty or unrecognized input returns StorageModeUnknown; // callers decide whether that is an error (user-supplied value) or a @@ -57,7 +105,7 @@ func ParseMode(raw string) StorageMode { // 1. override (typically from a command-level flag such as --secure-storage). // 2. DATABRICKS_AUTH_STORAGE env var. // 3. [__settings__].auth_storage in .databrickscfg. -// 4. StorageModePlaintext. +// 4. StorageModeSecure. // // StorageModeUnknown as override means "no flag set; fall through." The // override is trusted to be a valid StorageMode: callers that parse user @@ -65,24 +113,36 @@ func ParseMode(raw string) StorageMode { // unrecognized env or config value is reported as an error wrapped with // the source name. func ResolveStorageMode(ctx context.Context, override StorageMode) (StorageMode, error) { + mode, _, err := ResolveStorageModeWithSource(ctx, override) + return mode, err +} + +// ResolveStorageModeWithSource is like ResolveStorageMode but also reports +// which precedence level produced the resolved mode. Callers use the source +// both to honor "I want secure" strictly (when source.Explicit() is true and +// secure cannot be provided, error out instead of silently downgrading) and +// to surface where the choice came from in user-facing output. +func ResolveStorageModeWithSource(ctx context.Context, override StorageMode) (StorageMode, StorageSource, error) { if override != StorageModeUnknown { - return override, nil + return override, StorageSourceOverride, nil } if raw := env.Get(ctx, EnvVar); raw != "" { - return parseFromSource(raw, EnvVar) + mode, err := parseFromSource(raw, EnvVar) + return mode, StorageSourceEnvVar, err } configPath := env.Get(ctx, "DATABRICKS_CONFIG_FILE") raw, err := databrickscfg.GetConfiguredAuthStorage(ctx, configPath) if err != nil { - return "", fmt.Errorf("read auth_storage setting: %w", err) + return "", StorageSourceDefault, fmt.Errorf("read auth_storage setting: %w", err) } if raw != "" { - return parseFromSource(raw, "auth_storage") + mode, err := parseFromSource(raw, "auth_storage") + return mode, StorageSourceConfig, err } - return StorageModePlaintext, nil + return StorageModeSecure, StorageSourceDefault, nil } func parseFromSource(raw, source string) (StorageMode, error) { diff --git a/libs/auth/storage/mode_test.go b/libs/auth/storage/mode_test.go index d932d2253a2..431cc903801 100644 --- a/libs/auth/storage/mode_test.go +++ b/libs/auth/storage/mode_test.go @@ -41,7 +41,7 @@ func TestResolveStorageMode(t *testing.T) { }{ { name: "default when nothing is set", - want: StorageModePlaintext, + want: StorageModeSecure, }, { name: "override wins over env and config", @@ -63,8 +63,8 @@ func TestResolveStorageMode(t *testing.T) { }, { name: "config sets mode when env and override unset", - configBody: "[__settings__]\nauth_storage = secure\n", - want: StorageModeSecure, + configBody: "[__settings__]\nauth_storage = plaintext\n", + want: StorageModePlaintext, }, { name: "env value is case-insensitive and trimmed", @@ -128,3 +128,84 @@ func TestResolveStorageMode_SkipsConfigReadWhenOverrideOrEnvSet(t *testing.T) { assert.Equal(t, StorageModeSecure, got) }) } + +func TestResolveStorageModeWithSource(t *testing.T) { + cases := []struct { + name string + override StorageMode + envValue string + configBody string + wantMode StorageMode + wantSource StorageSource + wantErrSub string + }{ + { + name: "default", + wantMode: StorageModeSecure, + wantSource: StorageSourceDefault, + }, + { + name: "override", + override: StorageModePlaintext, + wantMode: StorageModePlaintext, + wantSource: StorageSourceOverride, + }, + { + name: "env", + envValue: "plaintext", + wantMode: StorageModePlaintext, + wantSource: StorageSourceEnvVar, + }, + { + name: "config", + configBody: "[__settings__]\nauth_storage = secure\n", + wantMode: StorageModeSecure, + wantSource: StorageSourceConfig, + }, + { + name: "invalid env is rejected", + envValue: "bogus", + wantErrSub: "DATABRICKS_AUTH_STORAGE", + }, + { + name: "invalid config value is rejected", + configBody: "[__settings__]\nauth_storage = bogus\n", + wantErrSub: "auth_storage", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + cfgPath := filepath.Join(t.TempDir(), ".databrickscfg") + if tc.configBody != "" { + require.NoError(t, os.WriteFile(cfgPath, []byte(tc.configBody), 0o600)) + } + t.Setenv("DATABRICKS_CONFIG_FILE", cfgPath) + t.Setenv(EnvVar, tc.envValue) + + mode, source, err := ResolveStorageModeWithSource(t.Context(), tc.override) + if tc.wantErrSub != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantErrSub) + return + } + require.NoError(t, err) + assert.Equal(t, tc.wantMode, mode) + assert.Equal(t, tc.wantSource, source) + }) + } +} + +func TestStorageSource_Explicit(t *testing.T) { + assert.False(t, StorageSourceDefault.Explicit()) + assert.True(t, StorageSourceOverride.Explicit()) + assert.True(t, StorageSourceEnvVar.Explicit()) + assert.True(t, StorageSourceConfig.Explicit()) +} + +func TestStorageSource_String(t *testing.T) { + assert.Equal(t, "default", StorageSourceDefault.String()) + assert.Equal(t, "command-line override", StorageSourceOverride.String()) + assert.Equal(t, "DATABRICKS_AUTH_STORAGE environment variable", StorageSourceEnvVar.String()) + assert.Equal(t, "auth_storage in [__settings__] section of config file", StorageSourceConfig.String()) +} diff --git a/libs/auth/storage/not_found_hint.go b/libs/auth/storage/not_found_hint.go new file mode 100644 index 00000000000..d86615535b5 --- /dev/null +++ b/libs/auth/storage/not_found_hint.go @@ -0,0 +1,120 @@ +package storage + +import ( + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + + "github.com/databricks/cli/libs/env" + "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" + "golang.org/x/oauth2" +) + +// notFoundHintCache wraps a TokenCache so Lookup returns ErrNotFound with +// a hint pointing the user at `databricks auth login`. When mode is secure +// and the legacy file-backed cache has entries, the hint uses the upgrade- +// specific copy so users who logged in with an older CLI version know why +// their cached credentials are no longer being read. +// +// errors.Is(err, cache.ErrNotFound) continues to return true because the +// wrap uses %w; the SDK's branches on ErrNotFound still fire. +// +// Store is delegated unchanged; only Lookup needs the message polish. +type notFoundHintCache struct { + inner cache.TokenCache + mode StorageMode + legacyCachePath string +} + +func (c *notFoundHintCache) Store(key string, t *oauth2.Token) error { + return c.inner.Store(key, t) +} + +func (c *notFoundHintCache) Lookup(key string) (*oauth2.Token, error) { + tok, err := c.inner.Lookup(key) + if err == nil || !errors.Is(err, cache.ErrNotFound) { + return tok, err + } + if c.mode == StorageModeSecure && legacyCacheHasTokens(c.legacyCachePath) { + return nil, ¬FoundHint{msg: "stored credentials from older CLI versions are no longer used; run `databricks auth login` to sign in again, or set DATABRICKS_AUTH_STORAGE=plaintext to keep using the file cache"} + } + return nil, ¬FoundHint{msg: "no cached credentials; run `databricks auth login` to sign in"} +} + +// notFoundHint replaces cache.ErrNotFound's terse "token not found" string +// with an actionable message while still satisfying errors.Is(err, +// cache.ErrNotFound). The SDK's loadToken wraps every cache error with +// "cache: %w", and fmt.Errorf("...: %w", ErrNotFound) would tack the +// original "token not found" onto the end of our hint, producing +// "cache: : token not found". A custom type lets us own the +// rendered message while still unwrapping to ErrNotFound for callers +// that branch on it. +type notFoundHint struct { + msg string +} + +func (e *notFoundHint) Error() string { return e.msg } +func (e *notFoundHint) Unwrap() error { return cache.ErrNotFound } + +// HintForNotFound extracts the actionable hint message from an error +// chain produced by notFoundHintCache. Returns the empty string if the +// chain does not contain a notFoundHint (e.g. an unwrapped +// cache.ErrNotFound from a plain TokenCache). +// +// Used by call sites like `auth token` that rewrite the SDK error for +// backwards-compatibility (the "databricks OAuth is not configured for +// this host" substring is load-bearing for older SDK fall-through +// logic) but want to surface the actionable hint to the user instead of +// dropping it. +func HintForNotFound(err error) string { + var hint *notFoundHint + if errors.As(err, &hint) { + return hint.msg + } + return "" +} + +// NewNotFoundHint returns an error that renders as msg but unwraps to +// cache.ErrNotFound, mirroring what notFoundHintCache produces in +// production. Exported so tests in other packages (e.g. cmd/auth) can +// construct a hint-wrapped error without going through the full +// resolver setup. +func NewNotFoundHint(msg string) error { + return ¬FoundHint{msg: msg} +} + +// withNotFoundHint wraps inner so ErrNotFound from Lookup carries an +// actionable hint. The legacy file path is resolved up front (where ctx +// is available) so Lookup can do its check without needing a context. +// +// Resolution failures for the home directory are not fatal: an empty +// legacyCachePath simply disables the upgrade-specific message, which +// falls back to the generic "run auth login" hint. +func withNotFoundHint(ctx context.Context, inner cache.TokenCache, mode StorageMode) cache.TokenCache { + var legacyCachePath string + if home, err := env.UserHomeDir(ctx); err == nil { + legacyCachePath = filepath.Join(home, tokenCacheFilePath) + } + return ¬FoundHintCache{inner: inner, mode: mode, legacyCachePath: legacyCachePath} +} + +// legacyCacheHasTokens reports whether the file at path is a valid token +// cache with at least one entry. Best-effort and read-only: any I/O or +// parse error returns false so we never claim "you have legacy tokens" +// when we cannot actually tell. +func legacyCacheHasTokens(path string) bool { + if path == "" { + return false + } + raw, err := os.ReadFile(path) + if err != nil { + return false + } + var f tokenCacheFile + if err := json.Unmarshal(raw, &f); err != nil { + return false + } + return len(f.Tokens) > 0 +} diff --git a/libs/auth/storage/not_found_hint_test.go b/libs/auth/storage/not_found_hint_test.go new file mode 100644 index 00000000000..b262aeb2d9f --- /dev/null +++ b/libs/auth/storage/not_found_hint_test.go @@ -0,0 +1,180 @@ +package storage + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/databricks/databricks-sdk-go/credentials/u2m/cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/oauth2" +) + +// missingCache always returns ErrNotFound on Lookup. Lets us drive the +// wrapper without going through the real file or keyring cache. +type missingCache struct{} + +func (missingCache) Store(string, *oauth2.Token) error { return nil } +func (missingCache) Lookup(string) (*oauth2.Token, error) { return nil, cache.ErrNotFound } + +// foundCache always returns a token. Used to confirm the wrapper passes +// successful lookups through unchanged. +type foundCache struct{ tok *oauth2.Token } + +func (c foundCache) Store(string, *oauth2.Token) error { return nil } +func (c foundCache) Lookup(string) (*oauth2.Token, error) { return c.tok, nil } + +// boomCache returns a non-ErrNotFound error. The wrapper must not add a +// "run auth login" hint here; the error is about something else. +type boomCache struct{ err error } + +func (c boomCache) Store(string, *oauth2.Token) error { return nil } +func (c boomCache) Lookup(string) (*oauth2.Token, error) { return nil, c.err } + +func writeLegacyCache(t *testing.T, path string, hasEntries bool) { + t.Helper() + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + tokens := map[string]*oauth2.Token{} + if hasEntries { + tokens["my-profile"] = &oauth2.Token{AccessToken: "abc"} + } + body, err := json.Marshal(tokenCacheFile{Version: tokenCacheVersion, Tokens: tokens}) + require.NoError(t, err) + require.NoError(t, os.WriteFile(path, body, 0o600)) +} + +func TestNotFoundHintCache_SecureWithLegacyEntries_UsesUpgradeMessage(t *testing.T) { + tmp := t.TempDir() + legacyPath := filepath.Join(tmp, tokenCacheFilePath) + writeLegacyCache(t, legacyPath, true) + + c := ¬FoundHintCache{inner: missingCache{}, mode: StorageModeSecure, legacyCachePath: legacyPath} + _, err := c.Lookup("anything") + require.Error(t, err) + assert.ErrorIs(t, err, cache.ErrNotFound) + assert.Contains(t, err.Error(), "stored credentials from older CLI versions") + assert.Contains(t, err.Error(), "databricks auth login") + assert.Contains(t, err.Error(), "DATABRICKS_AUTH_STORAGE=plaintext") +} + +func TestNotFoundHintCache_SecureWithEmptyLegacyFile_UsesGenericMessage(t *testing.T) { + tmp := t.TempDir() + legacyPath := filepath.Join(tmp, tokenCacheFilePath) + writeLegacyCache(t, legacyPath, false) + + c := ¬FoundHintCache{inner: missingCache{}, mode: StorageModeSecure, legacyCachePath: legacyPath} + _, err := c.Lookup("anything") + require.Error(t, err) + assert.ErrorIs(t, err, cache.ErrNotFound) + assert.Contains(t, err.Error(), "no cached credentials") + assert.NotContains(t, err.Error(), "stored credentials from older CLI versions") +} + +func TestNotFoundHintCache_SecureNoLegacyFile_UsesGenericMessage(t *testing.T) { + c := ¬FoundHintCache{inner: missingCache{}, mode: StorageModeSecure, legacyCachePath: filepath.Join(t.TempDir(), "missing.json")} + _, err := c.Lookup("anything") + require.Error(t, err) + assert.ErrorIs(t, err, cache.ErrNotFound) + assert.Contains(t, err.Error(), "no cached credentials") +} + +func TestNotFoundHintCache_Plaintext_AlwaysGenericMessage(t *testing.T) { + tmp := t.TempDir() + legacyPath := filepath.Join(tmp, tokenCacheFilePath) + writeLegacyCache(t, legacyPath, true) + + // Even with a populated legacy file present, plaintext mode reads from + // that same file, so the upgrade copy would be misleading. + c := ¬FoundHintCache{inner: missingCache{}, mode: StorageModePlaintext, legacyCachePath: legacyPath} + _, err := c.Lookup("anything") + require.Error(t, err) + assert.ErrorIs(t, err, cache.ErrNotFound) + assert.Contains(t, err.Error(), "no cached credentials") + assert.NotContains(t, err.Error(), "stored credentials from older CLI versions") +} + +func TestNotFoundHintCache_NonErrNotFound_PassesThrough(t *testing.T) { + boom := errors.New("backend blew up") + c := ¬FoundHintCache{inner: boomCache{err: boom}, mode: StorageModeSecure, legacyCachePath: ""} + _, err := c.Lookup("anything") + require.Error(t, err) + assert.ErrorIs(t, err, boom) + assert.NotContains(t, err.Error(), "no cached credentials") +} + +func TestNotFoundHintCache_SuccessfulLookupUnchanged(t *testing.T) { + tok := &oauth2.Token{AccessToken: "abc"} + c := ¬FoundHintCache{inner: foundCache{tok: tok}, mode: StorageModeSecure, legacyCachePath: ""} + got, err := c.Lookup("anything") + require.NoError(t, err) + assert.Equal(t, tok, got) +} + +func TestNotFoundHintCache_StoreIsDelegated(t *testing.T) { + c := ¬FoundHintCache{inner: missingCache{}, mode: StorageModeSecure, legacyCachePath: ""} + require.NoError(t, c.Store("k", &oauth2.Token{AccessToken: "abc"})) +} + +func TestHintForNotFound(t *testing.T) { + t.Run("nil returns empty", func(t *testing.T) { + assert.Empty(t, HintForNotFound(nil)) + }) + + t.Run("plain ErrNotFound returns empty", func(t *testing.T) { + // An unwrapped cache.ErrNotFound carries no hint, so the caller + // (e.g. `auth token`) falls back to its default error path. + assert.Empty(t, HintForNotFound(cache.ErrNotFound)) + }) + + t.Run("unrelated error returns empty", func(t *testing.T) { + assert.Empty(t, HintForNotFound(errors.New("something else"))) + }) + + t.Run("notFoundHint returns the hint message", func(t *testing.T) { + h := ¬FoundHint{msg: "do the thing"} + assert.Equal(t, "do the thing", HintForNotFound(h)) + }) + + t.Run("notFoundHint behind an fmt.Errorf wrap returns the hint", func(t *testing.T) { + // The SDK wraps every cache error with `cache: %w`, so the hint + // is one Unwrap away when it surfaces in callers. errors.As must + // still find it. + h := ¬FoundHint{msg: "do the thing"} + wrapped := fmt.Errorf("cache: %w", h) + assert.Equal(t, "do the thing", HintForNotFound(wrapped)) + }) +} + +func TestLegacyCacheHasTokens(t *testing.T) { + tmp := t.TempDir() + + t.Run("empty path returns false", func(t *testing.T) { + assert.False(t, legacyCacheHasTokens("")) + }) + + t.Run("missing file returns false", func(t *testing.T) { + assert.False(t, legacyCacheHasTokens(filepath.Join(tmp, "missing.json"))) + }) + + t.Run("garbage file returns false", func(t *testing.T) { + p := filepath.Join(tmp, "garbage.json") + require.NoError(t, os.WriteFile(p, []byte("not json"), 0o600)) + assert.False(t, legacyCacheHasTokens(p)) + }) + + t.Run("empty token map returns false", func(t *testing.T) { + p := filepath.Join(tmp, "empty.json") + writeLegacyCache(t, p, false) + assert.False(t, legacyCacheHasTokens(p)) + }) + + t.Run("populated token map returns true", func(t *testing.T) { + p := filepath.Join(tmp, "populated.json") + writeLegacyCache(t, p, true) + assert.True(t, legacyCacheHasTokens(p)) + }) +} diff --git a/libs/calladapt/calladapt.go b/libs/calladapt/calladapt.go index db7e2733d42..b6fc3ce046e 100644 --- a/libs/calladapt/calladapt.go +++ b/libs/calladapt/calladapt.go @@ -46,7 +46,7 @@ func (c *BoundCaller) call(args ...any) ([]reflect.Value, error) { it := c.InTypes[i] if a == nil { // Allow untyped nil for pointer types, converting to typed nil - if it.Kind() == reflect.Ptr { + if it.Kind() == reflect.Pointer { in[i+1] = reflect.Zero(it) continue } diff --git a/libs/calladapt/validate.go b/libs/calladapt/validate.go index 8613d42d906..e33697a79f4 100644 --- a/libs/calladapt/validate.go +++ b/libs/calladapt/validate.go @@ -11,13 +11,12 @@ func EnsureNoExtraMethods(receiver any, ifaceTypes ...reflect.Type) error { allowed := make(map[string]struct{}) for _, ifaceType := range ifaceTypes { - for i := range ifaceType.NumMethod() { - allowed[ifaceType.Method(i).Name] = struct{}{} + for method := range ifaceType.Methods() { + allowed[method.Name] = struct{}{} } } - for i := range rt.NumMethod() { - m := rt.Method(i) + for m := range rt.Methods() { if _, ok := allowed[m.Name]; !ok { return fmt.Errorf("unexpected method %s on %v; only methods from %v are allowed", m.Name, rt, ifaceTypes) } diff --git a/libs/clicompat/clicompat.go b/libs/clicompat/clicompat.go new file mode 100644 index 00000000000..8d982db132e --- /dev/null +++ b/libs/clicompat/clicompat.go @@ -0,0 +1,450 @@ +package clicompat + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "slices" + "strings" + "sync" + "time" + + "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/env" + "github.com/databricks/cli/libs/log" + "golang.org/x/mod/semver" +) + +// ErrNotFound indicates that a requested resource (tag, branch, manifest) +// does not exist at the remote. +var ErrNotFound = errors.New("not found") + +// HTTPStatusError captures a non-200 HTTP status code from a manifest fetch. +type HTTPStatusError struct { + StatusCode int +} + +// Error implements the error interface. +func (e *HTTPStatusError) Error() string { + return fmt.Sprintf("HTTP %d fetching manifest", e.StatusCode) +} + +const ( + // manifestURL is the raw GitHub URL for the compatibility manifest. + manifestURL = "https://raw.githubusercontent.com/databricks/cli/main/internal/build/cli-compat.json" + + // fetchTimeout is the HTTP timeout for fetching the manifest at runtime. + fetchTimeout = 3 * time.Second + + // cacheTTL is how long a locally cached manifest is considered fresh. + cacheTTL = 1 * time.Hour + + // localManifestFile is the filename for the locally cached manifest. + localManifestFile = "compat-manifest.json" + + // devVersionPrefix identifies dev builds whose semver (0.0.0) is lower than + // all real CLI versions. These are treated as bleeding-edge and resolve to + // the highest versioned entry. + devVersionPrefix = "0.0.0-dev" + + maxFetchAttempts = 3 + fetchRetryBackoff = 300 * time.Millisecond +) + +// Entry maps a CLI version to compatible AppKit and Agent Skills versions. +type Entry struct { + AppKit string `json:"appkit"` + AgentSkills string `json:"skills"` +} + +// Manifest is the compatibility manifest: a map of CLI version strings to entries. +type Manifest map[string]Entry + +// cachedManifest holds a parsed manifest together with its on-disk mod time. +type cachedManifest struct { + manifest Manifest + modTime time.Time +} + +// isFresh reports whether the cached manifest is younger than maxAge. +func (c cachedManifest) isFresh(maxAge time.Duration) bool { + return time.Since(c.modTime) < maxAge +} + +// httpClient is the HTTP client used for manifest fetches. Package-level var +// so tests can replace it. Not safe for parallel tests; the clicompat test +// suite does not use t.Parallel(). +var httpClient = &http.Client{Timeout: fetchTimeout} + +// FetchManifest returns the compatibility manifest using a 4-tier fallback: +// 1. Local cached file (if fresh, < 1 hour old) +// 2. Remote fetch from GitHub (with retry) +// 3. Stale local file (if remote fails but a previously cached file exists) +// 4. Embedded manifest compiled into the binary +// +// Set DATABRICKS_FORCE_EMBEDDED_COMPAT=true to skip all tiers and use only +// the embedded manifest. Useful for local development when testing with a +// locally compiled binary. +// +// Set DATABRICKS_CACHE_ENABLED=false to bypass the local cache (tiers 1 and 3a), +// which is useful to recover from a bad cached manifest. +func FetchManifest(ctx context.Context) (Manifest, error) { + if force, ok := env.GetBool(ctx, "DATABRICKS_FORCE_EMBEDDED_COMPAT"); ok && force { + log.Debugf(ctx, "Using embedded manifest (DATABRICKS_FORCE_EMBEDDED_COMPAT=true)") + return parseEmbeddedManifest() + } + + cacheEnabled := true + if enabled, ok := env.GetBool(ctx, "DATABRICKS_CACHE_ENABLED"); ok { + cacheEnabled = enabled + } + + localPath := manifestLocalPath(ctx) + + // Read local file once — reuse across tiers. + var local cachedManifest + var localErr error + if cacheEnabled { + local, localErr = readLocalManifest(localPath) + } else { + localErr = errors.New("cache disabled") + } + + // Tier 1: local file is fresh. + if localErr == nil && local.isFresh(cacheTTL) { + log.Debugf(ctx, "Using cached manifest from %s", localPath) + return local.manifest, nil + } + + // Tier 2: fetch from remote (local file missing or stale). + m, fetchErr := fetchRemoteWithRetry(ctx) + if fetchErr == nil { + if cacheEnabled { + writeLocalManifest(ctx, localPath, m) + } + return m, nil + } + + // Tier 3a: local file exists but stale — use it anyway. + if localErr == nil { + log.Debugf(ctx, "Using stale cached manifest (remote failed: %v)", fetchErr) + return local.manifest, nil + } + + // Tier 3b: embedded manifest. + m, embeddedErr := parseEmbeddedManifest() + if embeddedErr == nil { + log.Debugf(ctx, "Using embedded manifest (remote and local cache failed)") + return m, nil + } + + return nil, fmt.Errorf("all manifest sources failed (remote: %w, embedded: %w)", fetchErr, embeddedErr) +} + +// parseEmbeddedManifest parses the embedded manifest once and caches the result. +var parseEmbeddedManifest = sync.OnceValues(func() (Manifest, error) { + return parseManifest(build.CLICompatManifestJSON) +}) + +// ResolveEmbeddedAppKitVersion resolves the AppKit version from only the +// embedded manifest for the current CLI version. Used as a fallback when the +// primary version (from remote or cached manifest) points to a non-existent tag, +// and for help text defaults where a network call is not appropriate. +func ResolveEmbeddedAppKitVersion() (string, error) { + m, err := parseEmbeddedManifest() + if err != nil { + return "", fmt.Errorf("embedded manifest: %w", err) + } + entry, err := Resolve(m, build.GetInfo().Version) + if err != nil { + return "", fmt.Errorf("embedded manifest resolve: %w", err) + } + return entry.AppKit, nil +} + +// ResolveEmbeddedAgentSkillsVersion resolves the Agent Skills version from only +// the embedded manifest for the current CLI version. Used as a fallback when the +// primary version points to a non-existent tag. +func ResolveEmbeddedAgentSkillsVersion() (string, error) { + m, err := parseEmbeddedManifest() + if err != nil { + return "", fmt.Errorf("embedded manifest: %w", err) + } + entry, err := Resolve(m, build.GetInfo().Version) + if err != nil { + return "", fmt.Errorf("embedded manifest resolve: %w", err) + } + return entry.AgentSkills, nil +} + +// IsNotFoundError reports whether the error indicates a "not found" condition +// (e.g. HTTP 404, missing git branch/tag). Used by consumers to decide whether +// to fall back to the embedded manifest. +func IsNotFoundError(err error) bool { + if err == nil { + return false + } + if errors.Is(err, ErrNotFound) { + return true + } + var httpErr *HTTPStatusError + if errors.As(err, &httpErr) && httpErr.StatusCode == http.StatusNotFound { + return true + } + // Git clone errors include "not found" in stderr when a branch/tag does not + // exist (e.g. "Remote branch X not found in upstream origin"). This is a + // pragmatic fallback until git.Clone wraps a typed error. + msg := strings.ToLower(err.Error()) + return strings.Contains(msg, "not found") +} + +// Resolve returns the manifest entry for the given CLI version. +// +// Each versioned entry defines a range floor: it applies to that CLI version +// and all versions above it, up to (but not including) the next entry. +// +// Resolution order: +// 1. Dev builds (version starts with "0.0.0-dev") use the highest versioned entry. +// 2. Exact match on CLI version. +// 3. Nearest lower version (semver-sorted). This also handles CLI versions +// newer than all entries, returning the highest known entry. +// 4. If CLI is older than all entries, use the lowest (oldest) entry. +func Resolve(m Manifest, cliVersion string) (Entry, error) { + if len(m) == 0 { + return Entry{}, errors.New("empty compatibility manifest") + } + + // Collect and sort versioned keys descending. + versions := sortedVersions(m) + if len(versions) == 0 { + return Entry{}, errors.New("compatibility manifest has no versioned entries") + } + + // Dev builds (0.0.0-dev*) have semver lower than all real CLI versions, + // so they would incorrectly resolve to the lowest entry. Use the highest + // versioned entry instead, since dev builds represent the bleeding edge. + if strings.HasPrefix(cliVersion, devVersionPrefix) { + return m[versions[0]], nil + } + + // Exact match. + if entry, ok := m[cliVersion]; ok { + return entry, nil + } + + // Find the nearest lower version. + vCLI := "v" + cliVersion + for _, v := range versions { + if semver.Compare("v"+v, vCLI) <= 0 { + return m[v], nil + } + } + + // CLI is older than all entries — use the lowest (oldest) entry. + return m[versions[len(versions)-1]], nil +} + +// sortedVersions returns manifest keys sorted descending by semver. +func sortedVersions(m Manifest) []string { + versions := make([]string, 0, len(m)) + for k := range m { + versions = append(versions, k) + } + slices.SortFunc(versions, func(a, b string) int { + return semver.Compare("v"+b, "v"+a) + }) + return versions +} + +// resolveEntry fetches the manifest, resolves for the given CLI version. +func resolveEntry(ctx context.Context, cliVersion string) (Entry, error) { + m, err := FetchManifest(ctx) + if err != nil { + return Entry{}, err + } + return Resolve(m, cliVersion) +} + +// ResolveAppKitVersion resolves the AppKit template version for the current CLI. +func ResolveAppKitVersion(ctx context.Context) (string, error) { + entry, err := resolveEntry(ctx, build.GetInfo().Version) + if err != nil { + return "", err + } + return entry.AppKit, nil +} + +// ResolveAgentSkillsVersion resolves the Agent Skills version for the current CLI. +func ResolveAgentSkillsVersion(ctx context.Context) (string, error) { + entry, err := resolveEntry(ctx, build.GetInfo().Version) + if err != nil { + return "", err + } + return entry.AgentSkills, nil +} + +// --- Local manifest cache --- + +// manifestLocalPath returns the path to the locally cached manifest file. +func manifestLocalPath(ctx context.Context) string { + if dir := env.Get(ctx, "DATABRICKS_CACHE_DIR"); dir != "" { + return filepath.Join(dir, localManifestFile) + } + home, err := os.UserCacheDir() + if err != nil { + log.Debugf(ctx, "Could not determine user cache directory: %v", err) + return "" + } + return filepath.Join(home, "databricks", localManifestFile) +} + +// readLocalManifest reads and parses the locally cached manifest file. +func readLocalManifest(path string) (cachedManifest, error) { + if path == "" { + return cachedManifest{}, errors.New("no cache path") + } + info, err := os.Stat(path) + if err != nil { + return cachedManifest{}, err + } + data, err := os.ReadFile(path) + if err != nil { + return cachedManifest{}, err + } + m, err := parseManifest(data) + if err != nil { + return cachedManifest{}, err + } + return cachedManifest{manifest: m, modTime: info.ModTime()}, nil +} + +// writeLocalManifest writes the manifest to the local cache path using a +// temp-file-then-rename pattern for atomicity. +func writeLocalManifest(ctx context.Context, path string, m Manifest) { + if path == "" { + return + } + data, err := json.Marshal(m) + if err != nil { + log.Debugf(ctx, "Failed to marshal manifest for cache: %v", err) + return + } + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0o700); err != nil { + log.Warnf(ctx, "Failed to create cache directory %s: %v", dir, err) + return + } + tmp, err := os.CreateTemp(dir, ".compat-manifest-*.tmp") + if err != nil { + log.Warnf(ctx, "Failed to create temp cache file: %v", err) + return + } + tmpPath := tmp.Name() + defer func() { + _ = tmp.Close() + _ = os.Remove(tmpPath) + }() + if _, err := tmp.Write(data); err != nil { + log.Debugf(ctx, "Failed to write temp cache file: %v", err) + return + } + if err := tmp.Close(); err != nil { + log.Debugf(ctx, "Failed to close temp cache file: %v", err) + return + } + if err := os.Rename(tmpPath, path); err != nil { + log.Warnf(ctx, "Failed to rename temp cache file: %v", err) + } +} + +// --- Remote fetch --- + +// fetchRemoteWithRetry wraps fetchRemote with retries on transient errors. +// Client errors (4xx) are not retried since they will not recover. +func fetchRemoteWithRetry(ctx context.Context) (Manifest, error) { + var lastErr error + for attempt := range maxFetchAttempts { + if attempt > 0 { + log.Debugf(ctx, "Retrying manifest fetch (attempt %d/%d)", attempt+1, maxFetchAttempts) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(fetchRetryBackoff): + } + } + m, err := fetchRemote(ctx) + if err == nil { + return m, nil + } + lastErr = err + + // Do not retry client errors (4xx) — they won't resolve on retry. + var httpErr *HTTPStatusError + if errors.As(err, &httpErr) && httpErr.StatusCode >= 400 && httpErr.StatusCode < 500 { + return nil, lastErr + } + } + return nil, lastErr +} + +func fetchRemote(ctx context.Context) (Manifest, error) { + log.Debugf(ctx, "Fetching compatibility manifest from %s", manifestURL) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, manifestURL, nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", "databricks-cli/"+build.GetInfo().Version) + + resp, err := httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, &HTTPStatusError{StatusCode: resp.StatusCode} + } + + // Cap response size to guard against corrupted or malicious responses. + body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + if err != nil { + return nil, err + } + + return parseManifest(body) +} + +func parseManifest(data []byte) (Manifest, error) { + var m Manifest + if err := json.Unmarshal(data, &m); err != nil { + return nil, fmt.Errorf("invalid manifest JSON: %w", err) + } + if len(m) == 0 { + return nil, errors.New("empty compatibility manifest") + } + for k := range m { + if !semver.IsValid("v" + k) { + return nil, fmt.Errorf("invalid semver key %q in compatibility manifest", k) + } + } + for k, entry := range m { + if entry.AppKit == "" { + return nil, fmt.Errorf("manifest entry %q has empty appkit version", k) + } + if entry.AgentSkills == "" { + return nil, fmt.Errorf("manifest entry %q has empty skills version", k) + } + if !semver.IsValid("v" + entry.AppKit) { + return nil, fmt.Errorf("manifest entry %q has invalid appkit version %q", k, entry.AppKit) + } + if !semver.IsValid("v" + entry.AgentSkills) { + return nil, fmt.Errorf("manifest entry %q has invalid skills version %q", k, entry.AgentSkills) + } + } + return m, nil +} diff --git a/libs/clicompat/clicompat_test.go b/libs/clicompat/clicompat_test.go new file mode 100644 index 00000000000..87d29c6dfe9 --- /dev/null +++ b/libs/clicompat/clicompat_test.go @@ -0,0 +1,498 @@ +package clicompat + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io/fs" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "slices" + "sync/atomic" + "testing" + "time" + + "github.com/databricks/cli/internal/build" + "github.com/databricks/cli/libs/env" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/mod/semver" +) + +// roundTripFunc adapts a function into an http.RoundTripper. +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req) +} + +// redirectToServer replaces the package-level httpClient with one whose +// transport rewrites every request URL to point at srv. +func redirectToServer(t *testing.T, srv *httptest.Server) { + t.Helper() + orig := httpClient + httpClient = &http.Client{ + Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) { + target, _ := url.Parse(srv.URL) + req.URL.Scheme = target.Scheme + req.URL.Host = target.Host + return http.DefaultTransport.RoundTrip(req) + }), + } + t.Cleanup(func() { httpClient = orig }) +} + +// testContext returns a context with an isolated cache directory so tests don't +// share cached manifests. +func testContext(t *testing.T) context.Context { + t.Helper() + return env.Set(t.Context(), "DATABRICKS_CACHE_DIR", t.TempDir()) +} + +// --- Resolve tests --- + +// TestResolve_Ranges verifies range-based resolution. Each versioned entry +// defines a range floor: it applies to that CLI version and all versions above +// it up to (but not including) the next entry. Dev builds use the highest +// versioned entry. The manifest uses distinct appkit values so assertions are +// unambiguous. +func TestResolve_Ranges(t *testing.T) { + m := Manifest{ + "0.296.0": {AppKit: "0.27.0", AgentSkills: "0.1.5"}, + "0.290.0": {AppKit: "0.24.0", AgentSkills: "0.1.4"}, + "0.280.0": {AppKit: "0.20.0", AgentSkills: "0.1.0"}, + } + + tests := []struct { + name string + cliVersion string + wantAppKit string + wantSkills string + }{ + {"exact match at range floor", "0.280.0", "0.20.0", "0.1.0"}, + {"mid-range", "0.285.0", "0.20.0", "0.1.0"}, + {"just below next range", "0.289.9", "0.20.0", "0.1.0"}, + {"exact match mid entry", "0.290.0", "0.24.0", "0.1.4"}, + {"between mid and top", "0.293.0", "0.24.0", "0.1.4"}, + {"exact match highest", "0.296.0", "0.27.0", "0.1.5"}, + {"newer than all entries uses highest", "0.300.0", "0.27.0", "0.1.5"}, + {"older than all entries uses lowest", "0.270.0", "0.20.0", "0.1.0"}, + {"dev build uses highest", "0.0.0-dev+abc123", "0.27.0", "0.1.5"}, + {"bare dev uses highest", "0.0.0-dev", "0.27.0", "0.1.5"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + entry, err := Resolve(m, tc.cliVersion) + require.NoError(t, err) + assert.Equal(t, tc.wantAppKit, entry.AppKit) + assert.Equal(t, tc.wantSkills, entry.AgentSkills) + }) + } +} + +func TestResolve_EmptyManifest(t *testing.T) { + m := Manifest{} + _, err := Resolve(m, "0.296.0") + assert.Error(t, err) + assert.Contains(t, err.Error(), "empty compatibility manifest") +} + +// --- FetchManifest tests --- + +func TestFetchManifest_RemoteSuccess(t *testing.T) { + ctx := testContext(t) + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + var called bool + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + result, err := FetchManifest(ctx) + require.NoError(t, err) + assert.True(t, called, "test server should have been called") + assert.Equal(t, "0.99.0", result["0.296.0"].AppKit) +} + +func TestFetchManifest_RemoteFailFallsBackToEmbedded(t *testing.T) { + ctx := testContext(t) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + redirectToServer(t, srv) + + // No local cache exists, so should fall back to embedded manifest. + result, err := FetchManifest(ctx) + require.NoError(t, err) + + // Verify it returned the embedded manifest values. + embedded, _ := parseManifest(build.CLICompatManifestJSON) + assert.Equal(t, embedded["0.300.0"].AppKit, result["0.300.0"].AppKit) +} + +func TestFetchManifest_RemoteFailFallsBackToStaleCache(t *testing.T) { + ctx := testContext(t) + + // Pre-populate the local cache with a stale manifest. + staleManifest := Manifest{ + "0.296.0": {AppKit: "0.88.0", AgentSkills: "0.8.8"}, + } + localPath := manifestLocalPath(ctx) + writeLocalManifest(ctx, localPath, staleManifest) + // Make it stale by setting mod time to 2 hours ago. + past := time.Now().Add(-2 * time.Hour) + require.NoError(t, os.Chtimes(localPath, past, past)) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + redirectToServer(t, srv) + + result, err := FetchManifest(ctx) + require.NoError(t, err) + // Should return the stale cached manifest, not the embedded one. + assert.Equal(t, "0.88.0", result["0.296.0"].AppKit) +} + +func TestFetchManifest_RemoteSuccessWritesLocalCache(t *testing.T) { + ctx := testContext(t) + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + _, err := FetchManifest(ctx) + require.NoError(t, err) + + // Verify the local cache was written. + localPath := manifestLocalPath(ctx) + _, statErr := os.Stat(localPath) + assert.NoError(t, statErr, "local cache file should exist after successful fetch") +} + +func TestFetchManifest_CacheHit(t *testing.T) { + ctx := testContext(t) + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + var callCount atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount.Add(1) + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + // First call: populates cache. + result1, err := FetchManifest(ctx) + require.NoError(t, err) + assert.Equal(t, "0.99.0", result1["0.296.0"].AppKit) + + // Second call: should come from cache, not hitting the server again. + result2, err := FetchManifest(ctx) + require.NoError(t, err) + assert.Equal(t, "0.99.0", result2["0.296.0"].AppKit) + + assert.Equal(t, int32(1), callCount.Load(), "server should only be called once; second call should be a cache hit") +} + +func TestFetchManifest_RetryOnTransientError(t *testing.T) { + ctx := testContext(t) + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + var callCount atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n := callCount.Add(1) + if n == 1 { + w.WriteHeader(http.StatusInternalServerError) + return + } + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + result, err := FetchManifest(ctx) + require.NoError(t, err) + assert.Equal(t, "0.99.0", result["0.296.0"].AppKit) + assert.Equal(t, int32(2), callCount.Load(), "should have retried after first failure") +} + +// --- parseManifest tests --- + +func TestParseManifest_Valid(t *testing.T) { + data := `{"0.296.0":{"appkit":"0.27.0","skills":"0.1.5"}}` + m, err := parseManifest([]byte(data)) + require.NoError(t, err) + assert.Equal(t, "0.27.0", m["0.296.0"].AppKit) +} + +func TestParseManifest_InvalidJSON(t *testing.T) { + _, err := parseManifest([]byte("not json")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid manifest JSON") +} + +func TestParseManifest_Empty(t *testing.T) { + _, err := parseManifest([]byte("{}")) + assert.Error(t, err) + assert.Contains(t, err.Error(), "empty compatibility manifest") +} + +// --- resolveEntry tests --- + +func TestResolveEntry_RemoteSuccess(t *testing.T) { + ctx := testContext(t) + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + entry, err := resolveEntry(ctx, "0.296.0") + require.NoError(t, err) + assert.Equal(t, "0.99.0", entry.AppKit) + assert.Equal(t, "0.9.9", entry.AgentSkills) +} + +func TestResolveEntry_RemoteFailUsesEmbedded(t *testing.T) { + ctx := testContext(t) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer srv.Close() + redirectToServer(t, srv) + + // Should succeed via the embedded manifest fallback. + entry, err := resolveEntry(ctx, "0.0.0-dev+test") + require.NoError(t, err) + assert.NotEmpty(t, entry.AppKit) +} + +// --- ResolveEmbeddedAppKitVersion --- + +func TestResolveEmbeddedAppKitVersion(t *testing.T) { + v, err := ResolveEmbeddedAppKitVersion() + require.NoError(t, err) + assert.NotEmpty(t, v, "embedded manifest should resolve an appkit version") + assert.True(t, semver.IsValid("v"+v), "embedded resolved version should be valid semver") +} + +// --- Embedded manifest validation (replaces AppKit TS validator) --- + +func TestEmbeddedManifest_IsWellFormed(t *testing.T) { + m, err := parseManifest(build.CLICompatManifestJSON) + require.NoError(t, err, "embedded manifest must be valid JSON") + require.NotEmpty(t, m, "embedded manifest must have at least one entry") + + // All keys must be valid semver. + var keys []string + for k := range m { + assert.True(t, semver.IsValid("v"+k), "key %q must be valid semver", k) + keys = append(keys, k) + } + + // Sort to get deterministic order from map iteration. + slices.SortFunc(keys, func(a, b string) int { + return semver.Compare("v"+a, "v"+b) + }) + + // Keys must be in ascending semver order. + for i := 1; i < len(keys); i++ { + cmp := semver.Compare("v"+keys[i-1], "v"+keys[i]) + assert.LessOrEqual(t, cmp, 0, + "keys must be in ascending order: %s should come before %s", + keys[i-1], keys[i]) + } +} + +// --- Local cache helpers --- + +func TestManifestLocalPath(t *testing.T) { + ctx := env.Set(t.Context(), "DATABRICKS_CACHE_DIR", "/tmp/test-cache") + path := manifestLocalPath(ctx) + assert.Equal(t, filepath.Join("/tmp/test-cache", localManifestFile), path) +} + +func TestReadWriteLocalManifest(t *testing.T) { + ctx := testContext(t) + m := Manifest{ + "0.300.0": {AppKit: "0.50.0", AgentSkills: "0.5.0"}, + } + + path := manifestLocalPath(ctx) + writeLocalManifest(ctx, path, m) + + cached, err := readLocalManifest(path) + require.NoError(t, err) + assert.Equal(t, "0.50.0", cached.manifest["0.300.0"].AppKit) + assert.True(t, cached.isFresh(cacheTTL), "just-written file should be fresh") +} + +// --- IsNotFoundError tests --- + +func TestIsNotFoundError(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {name: "nil", err: nil, want: false}, + {name: "unrelated error", err: errors.New("connection refused"), want: false}, + {name: "sentinel ErrNotFound", err: ErrNotFound, want: true}, + {name: "wrapped ErrNotFound", err: fmt.Errorf("fetching: %w", ErrNotFound), want: true}, + {name: "HTTPStatusError 404", err: &HTTPStatusError{StatusCode: 404}, want: true}, + {name: "wrapped HTTPStatusError 404", err: fmt.Errorf("fetch failed: %w", &HTTPStatusError{StatusCode: 404}), want: true}, + {name: "HTTPStatusError 500", err: &HTTPStatusError{StatusCode: 500}, want: false}, + {name: "HTTPStatusError 403", err: &HTTPStatusError{StatusCode: 403}, want: false}, + {name: "git not found", err: errors.New("Remote branch template-v99 not found in upstream origin"), want: true}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.want, IsNotFoundError(tc.err)) + }) + } +} + +// --- parseManifest entry validation tests --- + +func TestParseManifest_EmptyAppKit(t *testing.T) { + data := `{"0.296.0":{"appkit":"","skills":"0.1.5"}}` + _, err := parseManifest([]byte(data)) + require.Error(t, err) + assert.Contains(t, err.Error(), "empty appkit version") +} + +func TestParseManifest_EmptySkills(t *testing.T) { + data := `{"0.296.0":{"appkit":"0.27.0","skills":""}}` + _, err := parseManifest([]byte(data)) + require.Error(t, err) + assert.Contains(t, err.Error(), "empty skills version") +} + +func TestParseManifest_InvalidAppKitSemver(t *testing.T) { + data := `{"0.296.0":{"appkit":"not-semver","skills":"0.1.5"}}` + _, err := parseManifest([]byte(data)) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid appkit version") +} + +func TestParseManifest_InvalidSkillsSemver(t *testing.T) { + data := `{"0.296.0":{"appkit":"0.27.0","skills":"abc"}}` + _, err := parseManifest([]byte(data)) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid skills version") +} + +func TestParseManifest_InvalidKey(t *testing.T) { + data := `{"not-semver":{"appkit":"0.27.0","skills":"0.1.5"}}` + _, err := parseManifest([]byte(data)) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid semver key") +} + +// --- FetchManifest no-retry-on-404 test --- + +func TestFetchManifest_NoRetryOn404(t *testing.T) { + ctx := testContext(t) + var callCount atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount.Add(1) + w.WriteHeader(http.StatusNotFound) + })) + defer srv.Close() + redirectToServer(t, srv) + + // Should fall back to embedded manifest (since 404 is not retried). + result, err := FetchManifest(ctx) + require.NoError(t, err) + + embedded, _ := parseEmbeddedManifest() + assert.Equal(t, embedded["0.300.0"].AppKit, result["0.300.0"].AppKit) + assert.Equal(t, int32(1), callCount.Load(), "404 should not be retried") +} + +// --- FetchManifest cache-disabled test --- + +func TestFetchManifest_CacheDisabled(t *testing.T) { + ctx := testContext(t) + ctx = env.Set(ctx, "DATABRICKS_CACHE_ENABLED", "false") + + want := Manifest{ + "0.296.0": {AppKit: "0.99.0", AgentSkills: "0.9.9"}, + } + body, _ := json.Marshal(want) + + var callCount atomic.Int32 + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount.Add(1) + _, _ = w.Write(body) + })) + defer srv.Close() + redirectToServer(t, srv) + + // First call fetches from remote. + result1, err := FetchManifest(ctx) + require.NoError(t, err) + assert.Equal(t, "0.99.0", result1["0.296.0"].AppKit) + + // Second call should also fetch from remote (cache is disabled). + result2, err := FetchManifest(ctx) + require.NoError(t, err) + assert.Equal(t, "0.99.0", result2["0.296.0"].AppKit) + + assert.Equal(t, int32(2), callCount.Load(), "with cache disabled, both calls should hit the server") + + // Verify no cache file was written. + localPath := manifestLocalPath(ctx) + _, statErr := os.Stat(localPath) + assert.ErrorIs(t, statErr, fs.ErrNotExist, "cache file should not exist when cache is disabled") +} + +func TestFetchManifest_ForceEmbedded(t *testing.T) { + ctx := testContext(t) + ctx = env.Set(ctx, "DATABRICKS_FORCE_EMBEDDED_COMPAT", "true") + + var called bool + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + called = true + w.WriteHeader(http.StatusOK) + })) + defer srv.Close() + redirectToServer(t, srv) + + result, err := FetchManifest(ctx) + require.NoError(t, err) + assert.False(t, called, "server should not be called when DATABRICKS_FORCE_EMBEDDED_COMPAT=true") + + embedded, _ := parseManifest(build.CLICompatManifestJSON) + assert.Equal(t, embedded["0.300.0"].AppKit, result["0.300.0"].AppKit) +} diff --git a/libs/cmdctx/config_used_test.go b/libs/cmdctx/config_used_test.go index 4a8ea02cbb0..f99febf8046 100644 --- a/libs/cmdctx/config_used_test.go +++ b/libs/cmdctx/config_used_test.go @@ -9,7 +9,7 @@ import ( func TestCommandConfigUsed(t *testing.T) { cfg := &config.Config{ - Host: "https://test.com", + Host: "https://test.test", } ctx := t.Context() @@ -25,12 +25,12 @@ func TestCommandConfigUsed(t *testing.T) { assert.Same(t, c, ConfigUsed(ctx)) // The config should have the correct configuration. - assert.Equal(t, "https://test.com", ConfigUsed(ctx).Host) + assert.Equal(t, "https://test.test", ConfigUsed(ctx).Host) // Second call should update the config used. cfg2 := &config.Config{ - Host: "https://test2.com", + Host: "https://test2.test", } ctx = SetConfigUsed(ctx, cfg2) - assert.Equal(t, "https://test2.com", ConfigUsed(ctx).Host) + assert.Equal(t, "https://test2.test", ConfigUsed(ctx).Host) } diff --git a/libs/cmdctx/workspace_client_test.go b/libs/cmdctx/workspace_client_test.go index 03ef9631862..cd3959602f5 100644 --- a/libs/cmdctx/workspace_client_test.go +++ b/libs/cmdctx/workspace_client_test.go @@ -13,7 +13,7 @@ func TestCommandWorkspaceClient(t *testing.T) { ctx := t.Context() client := &databricks.WorkspaceClient{ Config: &config.Config{ - Host: "https://test.com", + Host: "https://test.test", }, } @@ -29,7 +29,7 @@ func TestCommandWorkspaceClient(t *testing.T) { assert.Same(t, w, cmdctx.WorkspaceClient(ctx)) // The client should have the correct configuration. - assert.Equal(t, "https://test.com", cmdctx.WorkspaceClient(ctx).Config.Host) + assert.Equal(t, "https://test.test", cmdctx.WorkspaceClient(ctx).Config.Host) // Second call should panic. assert.Panics(t, func() { diff --git a/libs/cmdio/ask.go b/libs/cmdio/ask.go new file mode 100644 index 00000000000..909076d5e60 --- /dev/null +++ b/libs/cmdio/ask.go @@ -0,0 +1,78 @@ +package cmdio + +import ( + "context" + "fmt" + "io" + "strings" +) + +// readLine reads a line from the reader and returns it without the trailing newline characters. +// It is unbuffered because cmdio's stdin is also unbuffered. +// If we were to add a [bufio.Reader] to the mix, we would need to update the other uses of the reader. +// Once cmdio's stdio is made to be buffered, this function can be removed. +func readLine(r io.Reader) (string, error) { + var b strings.Builder + buf := make([]byte, 1) + for { + n, err := r.Read(buf) + if n > 0 { + if buf[0] == '\n' { + break + } + if buf[0] != '\r' { + b.WriteByte(buf[0]) + } + } + if err != nil { + if b.Len() == 0 { + return "", err + } + break + } + } + return b.String(), nil +} + +// Ask prompts the user with a question and returns the entered answer. +// If the user just presses enter, defaultVal is returned. +func Ask(ctx context.Context, question, defaultVal string) (string, error) { + c := fromContext(ctx) + + // Add default value to question prompt. + if defaultVal != "" { + question += fmt.Sprintf(` [%s]`, defaultVal) + } + question += `: ` + + // Print prompt. + _, err := io.WriteString(c.err, question) + if err != nil { + return "", err + } + + // Read user input. Trim new line characters. + ans, err := readLine(c.in) + if err != nil { + return "", err + } + + // Return default value if user just presses enter. + if ans == "" { + return defaultVal, nil + } + + return ans, nil +} + +// AskYesOrNo prompts the user with a question and returns true if the answer +// is "y" or "yes" (case-insensitive). Any other answer, including an empty +// one, returns false. +func AskYesOrNo(ctx context.Context, question string) (bool, error) { + ans, err := Ask(ctx, question+" [y/N]", "") + if err != nil { + return false, err + } + ans = strings.ToLower(strings.TrimSpace(ans)) + return ans == "y" || ans == "yes", nil +} diff --git a/libs/cmdio/compat_test.go b/libs/cmdio/ask_test.go similarity index 79% rename from libs/cmdio/compat_test.go rename to libs/cmdio/ask_test.go index 3323556e8bf..75fc520f8d5 100644 --- a/libs/cmdio/compat_test.go +++ b/libs/cmdio/ask_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestCompat_readLine(t *testing.T) { +func TestReadLine(t *testing.T) { tests := []struct { name string reader io.Reader @@ -147,55 +147,7 @@ func (e *errorAfterNReader) Read(p []byte) (n int, err error) { return 0, e.err } -func TestCompat_splitAtLastNewLine(t *testing.T) { - tests := []struct { - name string - input string - wantFirst string - wantLast string - }{ - { - name: "LF newline in middle", - input: "hello\nworld", - wantFirst: "hello\n", - wantLast: "world", - }, - { - name: "CRLF newline in middle", - input: "hello\r\nworld", - wantFirst: "hello\r\n", - wantLast: "world", - }, - { - name: "no newline", - input: "hello world", - wantFirst: "", - wantLast: "hello world", - }, - { - name: "newline at end", - input: "hello\nworld\n", - wantFirst: "hello\nworld\n", - wantLast: "", - }, - { - name: "newline at start", - input: "\nhello world", - wantFirst: "\n", - wantLast: "hello world", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - first, last := splitAtLastNewLine(tt.input) - assert.Equal(t, tt.wantFirst, first) - assert.Equal(t, tt.wantLast, last) - }) - } -} - -func TestCompat_AskYesOrNo(t *testing.T) { +func TestAskYesOrNo(t *testing.T) { tests := []struct { name string input string diff --git a/libs/cmdio/capabilities.go b/libs/cmdio/capabilities.go index 62ac4b6ae91..47017b53acc 100644 --- a/libs/cmdio/capabilities.go +++ b/libs/cmdio/capabilities.go @@ -48,6 +48,13 @@ func (c Capabilities) SupportsColor(w io.Writer) bool { return isTTY(w) && c.color } +// SupportsStdoutColor returns true if stdout supports colored output. +// Use this when emitting colored bytes to a writer that wraps stdout (e.g. +// a buffered flusher) where SupportsColor's isTTY check would be misled. +func (c Capabilities) SupportsStdoutColor() bool { + return c.stdoutIsTTY && c.color +} + // SupportsPager returns true when we can drive an interactive pager. // It builds on SupportsPrompt (stderr+stdin TTY, not Git Bash) and // additionally requires stdout to be a TTY so rendered rows land on @@ -56,13 +63,11 @@ func (c Capabilities) SupportsPager() bool { return c.SupportsPrompt() && c.stdoutIsTTY } -// detectGitBash returns true if running in Git Bash on Windows (has broken promptui support). -// We do not allow prompting in Git Bash on Windows. -// Likely due to fact that Git Bash does not correctly support ANSI escape sequences, -// we cannot use promptui package there. -// See known issues: -// - https://github.com/manifoldco/promptui/issues/208 -// - https://github.com/chzyer/readline/issues/191 +// detectGitBash returns true if running under a Cygwin/MSYS2 environment on +// Windows (Git Bash is the common case). +// +// We disable prompting there because bubbletea is not compatible with the +// Cygwin/MSYS2 pty emulation; making it work is a follow-up. func detectGitBash(ctx context.Context) bool { // Check if the MSYSTEM environment variable is set to "MINGW64" msystem := env.Get(ctx, "MSYSTEM") diff --git a/libs/cmdio/cmdiotest/prompt_alt_key_noop_baseline_test.go b/libs/cmdio/cmdiotest/prompt_alt_key_noop_baseline_test.go new file mode 100644 index 00000000000..005f815605d --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_alt_key_noop_baseline_test.go @@ -0,0 +1,43 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_AltKeyNoop pins that Alt-prefixed keys are silent +// no-ops in [cmdio.RunPrompt]. Specifically, Alt+f (the readline binding +// for "move forward by word") must neither move the cursor nor insert a +// literal 'f' into the buffer. The same shape applies to Alt+b, Alt+d, +// Alt+Backspace, and any other modified key the prompt model doesn't +// handle; pinning Alt+f covers the class. +func TestPromptBaseline_AltKeyNoop(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + + // Type "hello" and move cursor two places left so it sits mid-word. + // If Alt+f moved the cursor (or inserted), goldens 01 and 02 would + // diverge. + tm.Type("hello") + tm.Type(termtest.KeyLeft) + tm.Type(termtest.KeyLeft) + tm.Golden("01-cursor-mid") + + tm.Type("\x1bf") + tm.Golden("02-after-alt-f") + + tm.Type(termtest.KeyEnter) + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + // Final guard: the returned value must be exactly "hello". A literal + // 'f' insertion would surface here even if the goldens above somehow + // missed it. + assert.Equal(t, "hello", v) +} diff --git a/libs/cmdio/cmdiotest/prompt_ctrl_c_baseline_test.go b/libs/cmdio/cmdiotest/prompt_ctrl_c_baseline_test.go new file mode 100644 index 00000000000..a818d1ba937 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_ctrl_c_baseline_test.go @@ -0,0 +1,32 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_CtrlC pins Ctrl+C cancellation for RunPrompt. Mirrors +// the equivalent Secret test: error is returned, value is empty, snapshot +// captures any "^C" that the terminal echoed. +func TestPromptBaseline_CtrlC(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Golden("01-empty") + + tm.Type("partial input") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyCtrlC) + + v, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, v) +} diff --git a/libs/cmdio/cmdiotest/prompt_ctrl_fb_baseline_test.go b/libs/cmdio/cmdiotest/prompt_ctrl_fb_baseline_test.go new file mode 100644 index 00000000000..90f1b35579b --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_ctrl_fb_baseline_test.go @@ -0,0 +1,36 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_CtrlFCtrlB pins that Ctrl+F and Ctrl+B move the cursor +// one character forward and backward in [cmdio.RunPrompt], the same as the +// right and left arrow keys. The emacs-style bindings are de-facto aliases +// for the arrow keys; this test pins that equivalence. +func TestPromptBaseline_CtrlFCtrlB(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Type("hello") + tm.Golden("01-cursor-end") + + tm.Type(termtest.KeyCtrlB) + tm.Type(termtest.KeyCtrlB) + tm.Golden("02-after-ctrl-b-twice") + + tm.Type(termtest.KeyCtrlF) + tm.Golden("03-after-ctrl-f") + + tm.Type(termtest.KeyEnter) + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hello", v) +} diff --git a/libs/cmdio/cmdiotest/prompt_ctrl_h_baseline_test.go b/libs/cmdio/cmdiotest/prompt_ctrl_h_baseline_test.go new file mode 100644 index 00000000000..eee56de4616 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_ctrl_h_baseline_test.go @@ -0,0 +1,35 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_CtrlH pins that Ctrl+H deletes the character to the +// left of the cursor in [cmdio.RunPrompt] — the same as the Backspace key. +// Ctrl+H sends BS (0x08) and Backspace sends DEL (0x7f); the prompt model +// handles both as backspace, making the control-character form a de-facto +// alias. This test pins that equivalence so a future change can't silently +// drop it. +func TestPromptBaseline_CtrlH(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Type("hello") + tm.Golden("01-typed-hello") + + tm.Type(termtest.KeyCtrlH) + tm.Type(termtest.KeyCtrlH) + tm.Golden("02-after-ctrl-h-twice") + + tm.Type(termtest.KeyEnter) + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hel", v) +} diff --git a/libs/cmdio/cmdiotest/prompt_ctrl_j_baseline_test.go b/libs/cmdio/cmdiotest/prompt_ctrl_j_baseline_test.go new file mode 100644 index 00000000000..3961f4bd5bb --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_ctrl_j_baseline_test.go @@ -0,0 +1,30 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_CtrlJ pins that Ctrl+J submits the prompt in +// [cmdio.RunPrompt] — the same as the Enter (Return) key. Enter sends CR +// (0x0d) and Ctrl+J sends LF (0x0a); the prompt model treats both as +// submit. A future change that only reacts to CR would silently swallow +// Ctrl+J; this test pins the parity. +func TestPromptBaseline_CtrlJ(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Type("hello") + tm.Golden("01-typed-hello") + + tm.Type(termtest.KeyCtrlJ) + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hello", v) +} diff --git a/libs/cmdio/cmdiotest/prompt_cursor_editing_baseline_test.go b/libs/cmdio/cmdiotest/prompt_cursor_editing_baseline_test.go new file mode 100644 index 00000000000..7f6bbc5abf0 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_cursor_editing_baseline_test.go @@ -0,0 +1,59 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_CursorEditing pins how RunPrompt responds to cursor +// movement and line-editing keys: ←/→, Home/End, Backspace, Ctrl+W, Ctrl+U. +// The prompt model handles ←/→ and Backspace; Home/End/Ctrl+W/Ctrl+U are +// no-ops, so the goldens after them are intentionally identical to the +// post-Backspace one. The Delete key (\x1b[3~) is *not* covered here +// because it exits the prompt with EOF; that behavior is pinned separately +// by TestPromptBaseline_DeleteKeyExits. +func TestPromptBaseline_CursorEditing(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Golden("01-empty") + + tm.Type("hello world") + tm.Golden("02-typed") + + tm.Type(termtest.KeyHome) + tm.Type("X") + tm.Golden("03-insert-at-start") + + tm.Type(termtest.KeyEnd) + tm.Type("!") + tm.Golden("04-insert-at-end") + + tm.Type(termtest.KeyLeft) + tm.Type(termtest.KeyLeft) + tm.Type("Y") + tm.Golden("05-insert-mid") + + tm.Type(termtest.KeyBackspace) + tm.Golden("06-after-backspace") + + tm.Type(termtest.KeyCtrlW) + tm.Golden("07-after-ctrl-w") + + tm.Type(termtest.KeyCtrlU) + tm.Golden("08-after-ctrl-u") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + // The goldens above show the visible buffer is "hello worldX!" when + // Enter fires; that's what the prompt returns. + assert.Equal(t, "hello worldX!", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/prompt_delete_key_exits_baseline_test.go b/libs/cmdio/cmdiotest/prompt_delete_key_exits_baseline_test.go new file mode 100644 index 00000000000..be9334d9d32 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_delete_key_exits_baseline_test.go @@ -0,0 +1,36 @@ +package cmdiotest_test + +import ( + "io" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_DeleteKeyExits pins a surprising behavior of +// [cmdio.RunPrompt]: pressing the Delete key (\x1b[3~) exits the prompt with +// io.EOF, just like Ctrl+D would on an empty line — and discards any input +// the user had already typed. The prompt model collapses both keys into the +// same EOF path; see prompt.go for the rationale. Pinning the behavior here +// makes sure a future change that splits the two keys is intentional. +func TestPromptBaseline_DeleteKeyExits(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + + // Type some content first to prove the buffer is non-empty from the user's + // perspective. This is what makes the behavior surprising: the prompt + // still exits even though the user has typed input. + tm.Type("hello") + tm.Type(termtest.KeyDelete) + + v, err := tm.Result() + require.Error(t, err, "raw output: %q", tm.Raw()) + assert.ErrorIs(t, err, io.EOF) + assert.Empty(t, v, "Delete-as-EOF discards typed input") +} diff --git a/libs/cmdio/cmdiotest/prompt_hide_entered_baseline_test.go b/libs/cmdio/cmdiotest/prompt_hide_entered_baseline_test.go new file mode 100644 index 00000000000..ab555a2d5f5 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_hide_entered_baseline_test.go @@ -0,0 +1,50 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_HideEnteredFalse pins the default post-Enter rendering +// of [cmdio.RunPrompt]: with HideEntered=false (the default), the entered +// value is shown alongside the label after the prompt closes. +func TestPromptBaseline_HideEnteredFalse(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + HideEntered: false, + }) + tm.WaitFor("Workspace name") + tm.Type("hello") + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hello", v, "snapshot:\n%s", tm.Snapshot()) + + tm.Golden("01-after-enter") +} + +// TestPromptBaseline_HideEnteredTrue pins that HideEntered=true clears the +// prompt frame after the user submits, leaving no trace of the entered value +// on screen. This is the path used by [cmdio.Secret]. +func TestPromptBaseline_HideEnteredTrue(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + HideEntered: true, + }) + tm.WaitFor("Workspace name") + tm.Type("hello") + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hello", v, "snapshot:\n%s", tm.Snapshot()) + + tm.Golden("01-after-enter") +} diff --git a/libs/cmdio/cmdiotest/prompt_mask_baseline_test.go b/libs/cmdio/cmdiotest/prompt_mask_baseline_test.go new file mode 100644 index 00000000000..3db492592b2 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_mask_baseline_test.go @@ -0,0 +1,37 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_Mask pins runprompts behavior +// when configured with Mask='*'. This is the shape used by `databricks +// configure` for personal access token entry (cmd/configure/configure.go:46). +func TestPromptBaseline_Mask(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Personal access token", + Mask: '*', + }) + tm.WaitFor("Personal access token") + tm.Golden("01-empty") + + tm.Type("dapi-secret") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Golden("03-after-backspace") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "dapi-sec", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/prompt_plain_baseline_test.go b/libs/cmdio/cmdiotest/prompt_plain_baseline_test.go new file mode 100644 index 00000000000..2798d7d5a58 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_plain_baseline_test.go @@ -0,0 +1,36 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_Plain pins prompts behavior when configured with only a +// Label (no Validate, no Mask). This is the most common shape used across +// cmd/auth and cmd/configure. +func TestPromptBaseline_Plain(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace name", + }) + tm.WaitFor("Workspace name") + tm.Golden("01-empty") + + tm.Type("hello") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Type("p there") + tm.Golden("03-after-edit") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "help there", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/prompt_utf8_baseline_test.go b/libs/cmdio/cmdiotest/prompt_utf8_baseline_test.go new file mode 100644 index 00000000000..184ebd5cb52 --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_utf8_baseline_test.go @@ -0,0 +1,50 @@ +package cmdiotest_test + +import ( + "runtime" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_UTF8 pins multi-byte rune handling: typing "café" +// (4 runes, 5 bytes) renders as 4 cells, one Backspace deletes one rune +// not one byte, and the returned value preserves the original code points. +// An implementation that counts bytes instead of runes would silently +// corrupt non-ASCII input even with ASCII tests passing. +func TestPromptBaseline_UTF8(t *testing.T) { + // On Windows, bubbletea wraps non-console input in + // github.com/mattn/go-localereader (see key_windows.go), which decodes + // each incoming byte ≥0x80 as the system ANSI code page (CP1252 on + // English Windows). Our pipe-based harness feeds raw UTF-8, so the + // c3 a9 bytes for "é" get re-read as Latin-1 Ã + © and never reach + // the prompt model as a single rune. The model itself handles UTF-8 + // correctly in production (where bytes come from the real console). + if runtime.GOOS == "windows" { + t.Skip("bubbletea localereader mangles UTF-8 over non-console input on Windows") + } + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Name", + }) + tm.WaitFor("Name") + tm.Golden("01-empty") + + tm.Type("café") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyBackspace) + tm.Golden("03-after-backspace") + + tm.Type("é") + tm.Golden("04-restored") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "café", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/prompt_validate_baseline_test.go b/libs/cmdio/cmdiotest/prompt_validate_baseline_test.go new file mode 100644 index 00000000000..5f703a5865d --- /dev/null +++ b/libs/cmdio/cmdiotest/prompt_validate_baseline_test.go @@ -0,0 +1,47 @@ +package cmdiotest_test + +import ( + "errors" + "strings" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestPromptBaseline_Validate pins Prompt's behavior when a Validate +// callback is configured: validation re-runs on every keystroke, the +// indicator glyph reflects the result, and Enter is blocked while invalid. +func TestPromptBaseline_Validate(t *testing.T) { + t.Parallel() + tm := termtest.NewPrompt(t, cmdio.PromptOptions{ + Label: "Workspace host", + Validate: func(s string) error { + if !strings.Contains(s, "://") { + return errors.New("must contain ://") + } + return nil + }, + }) + tm.WaitFor("Workspace host") + tm.Golden("01-empty") + + tm.Type("abc") + tm.Golden("02-invalid-typing") + + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Golden("03-cleared") + + tm.Type("https://example.com") + tm.Golden("04-valid") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "https://example.com", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/secret_baseline_test.go b/libs/cmdio/cmdiotest/secret_baseline_test.go new file mode 100644 index 00000000000..003f04c6d37 --- /dev/null +++ b/libs/cmdio/cmdiotest/secret_baseline_test.go @@ -0,0 +1,31 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSecretBaseline_Typing pins secrets behavior: +// each typed character should render as the configured mask ('*'), backspace +// should erase one mask char, and Enter should return the typed value. +func TestSecretBaseline_Typing(t *testing.T) { + t.Parallel() + tm := termtest.NewSecret(t, "Enter password") + tm.WaitFor("Enter password") + tm.Golden("01-empty") + + tm.Type("hunter2") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyBackspace) + tm.Golden("03-after-backspace") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "hunter", v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/secret_ctrl_c_baseline_test.go b/libs/cmdio/cmdiotest/secret_ctrl_c_baseline_test.go new file mode 100644 index 00000000000..d547df0d77c --- /dev/null +++ b/libs/cmdio/cmdiotest/secret_ctrl_c_baseline_test.go @@ -0,0 +1,28 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSecretBaseline_CtrlC pins Secret's behavior when the user cancels +// with Ctrl+C after typing a few characters. +func TestSecretBaseline_CtrlC(t *testing.T) { + t.Parallel() + tm := termtest.NewSecret(t, "Personal access token") + tm.WaitFor("Personal access token") + tm.Golden("01-empty") + + tm.Type("abc") + tm.Golden("02-after-typing") + + tm.Type(termtest.KeyCtrlC) + + v, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, v, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/secret_empty_enter_baseline_test.go b/libs/cmdio/cmdiotest/secret_empty_enter_baseline_test.go new file mode 100644 index 00000000000..0e4eeeda4bb --- /dev/null +++ b/libs/cmdio/cmdiotest/secret_empty_enter_baseline_test.go @@ -0,0 +1,24 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSecretBaseline_EmptyEnter pins Secret's behavior when the user +// presses Enter immediately without typing anything. +func TestSecretBaseline_EmptyEnter(t *testing.T) { + t.Parallel() + tm := termtest.NewSecret(t, "Personal access token") + tm.WaitFor("Personal access token") + tm.Golden("01-empty") + + tm.Type(termtest.KeyEnter) + + v, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Empty(t, v) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_arrow_page_nav_test.go b/libs/cmdio/cmdiotest/select_baseline_arrow_page_nav_test.go new file mode 100644 index 00000000000..4328bb6984f --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_arrow_page_nav_test.go @@ -0,0 +1,46 @@ +package cmdiotest_test + +import ( + "fmt" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_ArrowPageNav pins that the right and left arrow +// keys page through the selection list — the same as Ctrl+F / Ctrl+B +// (covered by TestSelectBaseline_CtrlFCtrlB). The select model treats +// both pairs as page-down / page-up rather than item-by-item movement. +func TestSelectBaseline_ArrowPageNav(t *testing.T) { + t.Parallel() + items := make([]cmdio.Tuple, 0, 12) + for i := 1; i <= 12; i++ { + items = append(items, cmdio.Tuple{ + Name: fmt.Sprintf("item-%02d", i), + Id: fmt.Sprintf("id%02d", i), + }) + } + + tm := termtest.NewSelectOrdered(t, items, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("item-01") + tm.Golden("01-initial") + + tm.Type(termtest.KeyRight) + tm.Golden("02-after-right") + + tm.Type(termtest.KeyRight) + tm.Golden("03-after-right-twice") + + tm.Type(termtest.KeyLeft) + tm.Golden("04-after-left") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "id03", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_c_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_c_test.go new file mode 100644 index 00000000000..a9fb6fc9e6a --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_c_test.go @@ -0,0 +1,31 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlC pins Select's behavior when the user cancels +// the prompt with Ctrl+C without making a selection. +func TestSelectBaseline_CtrlC(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyCtrlC) + + id, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, id) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_c_with_filter_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_c_with_filter_test.go new file mode 100644 index 00000000000..a1b141aa250 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_c_with_filter_test.go @@ -0,0 +1,36 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlCWithFilter pins the cancel path when the search +// filter is non-empty. Readline interprets Ctrl+C globally as interrupt; a +// naive replacement could rebind it to "clear input" first and only cancel on +// the second press. The error sentinel and an empty returned id must match +// the no-filter case. +func TestSelectBaseline_CtrlCWithFilter(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + + tm.Type("xyz") + tm.Golden("01-no-results-with-filter") + + tm.Type(termtest.KeyCtrlC) + + id, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, id) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_f_b_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_f_b_test.go new file mode 100644 index 00000000000..da17f764289 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_f_b_test.go @@ -0,0 +1,47 @@ +package cmdiotest_test + +import ( + "fmt" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlFCtrlB pins that Ctrl+F and Ctrl+B page through +// the selection list — distinct from Ctrl+N / Ctrl+P which move by one +// item. The list has 12 items against the default 5-row viewport, so a +// single Ctrl+F should advance the highlighted item by roughly a page +// rather than a single row, and Ctrl+B should walk it back. +func TestSelectBaseline_CtrlFCtrlB(t *testing.T) { + t.Parallel() + items := make([]cmdio.Tuple, 0, 12) + for i := 1; i <= 12; i++ { + items = append(items, cmdio.Tuple{ + Name: fmt.Sprintf("item-%02d", i), + Id: fmt.Sprintf("id%02d", i), + }) + } + + tm := termtest.NewSelectOrdered(t, items, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("item-01") + tm.Golden("01-initial") + + tm.Type(termtest.KeyCtrlF) + tm.Golden("02-after-ctrl-f") + + tm.Type(termtest.KeyCtrlF) + tm.Golden("03-after-ctrl-f-twice") + + tm.Type(termtest.KeyCtrlB) + tm.Golden("04-after-ctrl-b") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "id03", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_h_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_h_test.go new file mode 100644 index 00000000000..024827c790f --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_h_test.go @@ -0,0 +1,40 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlH pins that Ctrl+H deletes the last character +// from the search filter in [cmdio.Select] — the same as the Backspace +// key. Ctrl+H sends BS (0x08) and Backspace sends DEL (0x7f); the select +// model treats both as backspace inside the search buffer, and this test +// pins that equivalence for the filter editor. +func TestSelectBaseline_CtrlH(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type("alp") + tm.Golden("02-after-typing-alp") + + tm.Type(termtest.KeyCtrlH) + tm.Type(termtest.KeyCtrlH) + tm.Golden("03-after-ctrl-h-twice") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_j_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_j_test.go new file mode 100644 index 00000000000..c4ae0533a4b --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_j_test.go @@ -0,0 +1,37 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlJ pins that Ctrl+J submits the Select prompt +// cleanly. Ctrl+J sends LF (0x0a) and Enter sends CR (0x0d); the bubbletea +// model treats both as submit, so Ctrl+J ends the prompt the same way Enter +// does. After one KeyDown the highlight is on "b" and that's what gets +// returned — pin the exact value so a future change can't silently return a +// different index while still rendering the same screen. +func TestSelectBaseline_CtrlJ(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-after-down") + + tm.Type(termtest.KeyCtrlJ) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "b", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_ctrl_n_p_test.go b/libs/cmdio/cmdiotest/select_baseline_ctrl_n_p_test.go new file mode 100644 index 00000000000..caa869928f2 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_ctrl_n_p_test.go @@ -0,0 +1,38 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_CtrlNCtrlP pins that Ctrl+N and Ctrl+P move the +// selection down and up by one item — the same as the down and up arrow +// keys. This test pins that equivalence. +func TestSelectBaseline_CtrlNCtrlP(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "c"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyCtrlN) + tm.Type(termtest.KeyCtrlN) + tm.Golden("02-after-ctrl-n-twice") + + tm.Type(termtest.KeyCtrlP) + tm.Golden("03-after-ctrl-p") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "b", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_default_templates_test.go b/libs/cmdio/cmdiotest/select_baseline_default_templates_test.go new file mode 100644 index 00000000000..2d2ca0e8032 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_default_templates_test.go @@ -0,0 +1,50 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_DefaultTemplates pins the rendering of +// [cmdio.RunSelect] when no Label / Active / Inactive / Selected +// template is provided — the model falls back to its built-in defaults, +// which print {{.}} (Go's default formatting for the item struct rather +// than any specific field). +// +// This mirrors the `databricks selftest tui run-select` plain mode +// (cmd/selftest/tui/select.go: runSelectPlain) and exists so future +// changes to the defaults — or accidental loss of a custom template at +// a call site — produce a visible diff. +func TestSelectBaseline_DefaultTemplates(t *testing.T) { + t.Parallel() + // Same data shape as cmd/selftest/tui/fixtures.go buildItems(5). + items := []cmdio.Tuple{ + {Name: "unity-catalog", Id: "id-01"}, + {Name: "delta-lake", Id: "id-02"}, + {Name: "delta-sharing", Id: "id-03"}, + {Name: "photon", Id: "id-04"}, + {Name: "mlflow", Id: "id-05"}, + } + + tm := termtest.NewSelect(t, cmdio.SelectOptions{ + Label: "Pick an item", + Items: items, + }) + tm.WaitFor("Pick an item") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-after-down") + + tm.Type(termtest.KeyEnter) + + idx, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, 1, idx, "snapshot:\n%s", tm.Snapshot()) + + tm.Golden("03-after-enter") +} diff --git a/libs/cmdio/cmdiotest/select_baseline_esc_key_test.go b/libs/cmdio/cmdiotest/select_baseline_esc_key_test.go new file mode 100644 index 00000000000..7504a8ffd95 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_esc_key_test.go @@ -0,0 +1,44 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_EscKey pins Select's behavior when the user presses Esc +// at various states: the initial prompt, and after typing into the search +// filter. cmdio.Select uses StartInSearchMode: true, so the filter is active +// from the start. +func TestSelectBaseline_EscKey(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyEsc) + tm.Golden("02-esc-from-initial") + + tm.Type("a") + tm.Golden("03-after-typing-a") + + tm.Type(termtest.KeyEsc) + tm.Golden("04-esc-clears-filter-or-not") + + // Esc is inert in this Select model: it neither finalizes the prompt + // nor clears the filter. So the filter is still "a" here, which matches + // only "alpha"; Enter submits that match. + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_filter_cursor_editing_test.go b/libs/cmdio/cmdiotest/select_baseline_filter_cursor_editing_test.go new file mode 100644 index 00000000000..2c644b14e80 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_filter_cursor_editing_test.go @@ -0,0 +1,57 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_FilterCursorEditing pins how the search filter responds +// to cursor-editing keys: ←/→, Home/End, Delete, Ctrl+W. The goldens capture +// which keys actually edit the filter buffer in the current model. +func TestSelectBaseline_FilterCursorEditing(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + {Name: "delta", Id: "d"}, + {Name: "epsilon", Id: "e"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type("alp") + tm.Golden("02-after-typing-alp") + + tm.Type(termtest.KeyLeft) + tm.Type(termtest.KeyLeft) + tm.Type("X") + tm.Golden("03-after-insert-mid") + + tm.Type(termtest.KeyHome) + tm.Type("Y") + tm.Golden("04-after-insert-at-start") + + tm.Type(termtest.KeyEnd) + tm.Type("Z") + tm.Golden("05-after-insert-at-end") + + tm.Type(termtest.KeyCtrlU) + tm.Golden("06-after-ctrl-u") + + tm.Type("alpha") + tm.Type(termtest.KeyCtrlW) + tm.Golden("07-after-ctrl-w") + + tm.Type(termtest.KeyCtrlC) + + id, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, id) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_filter_cursor_test.go b/libs/cmdio/cmdiotest/select_baseline_filter_cursor_test.go new file mode 100644 index 00000000000..2bc59c3e178 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_filter_cursor_test.go @@ -0,0 +1,43 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_FilterCursor pins Select's behavior when the user has +// navigated to a non-first item, then types a filter query. Documents how +// the cursor moves into and out of filter mode. +func TestSelectBaseline_FilterCursor(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + {Name: "delta", Id: "d"}, + {Name: "epsilon", Id: "e"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Type(termtest.KeyDown) + tm.Golden("02-on-gamma") + + tm.Type("a") + tm.Golden("03-after-filter-a") + + tm.Type(termtest.KeyBackspace) + tm.Golden("04-after-clear-filter") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_filter_no_match_test.go b/libs/cmdio/cmdiotest/select_baseline_filter_no_match_test.go new file mode 100644 index 00000000000..f1942a2d7ab --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_filter_no_match_test.go @@ -0,0 +1,45 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_FilterNoMatch pins Select's behavior when the user +// types a filter query that matches none of the items, then backspaces it +// out and hits Enter. +func TestSelectBaseline_FilterNoMatch(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type("x") + tm.Golden("02-after-x") + + tm.Type("y") + tm.Golden("03-after-xy") + + tm.Type("z") + tm.Golden("04-after-xyz") + + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Golden("05-after-backspaces") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_filter_scroll_test.go b/libs/cmdio/cmdiotest/select_baseline_filter_scroll_test.go new file mode 100644 index 00000000000..ce5710ff7bf --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_filter_scroll_test.go @@ -0,0 +1,53 @@ +package cmdiotest_test + +import ( + "fmt" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_FilterScroll pins viewport behavior when a filter +// narrows a long list to a count still larger than the viewport. Combines +// FilterTyping (substring search) with Scroll (12+ items) — neither test +// alone exercises the recompute-then-scroll path. +// +// 20 items named item-01 .. item-20; the filter "item-1" matches item-01 +// plus item-10..item-19 = 11 items, more than the 5-row viewport. +func TestSelectBaseline_FilterScroll(t *testing.T) { + t.Parallel() + items := make([]cmdio.Tuple, 0, 20) + for i := 1; i <= 20; i++ { + items = append(items, cmdio.Tuple{ + Name: fmt.Sprintf("item-%02d", i), + Id: fmt.Sprintf("id%02d", i), + }) + } + + tm := termtest.NewSelectOrdered(t, items, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("item-01") + tm.Golden("01-initial") + + tm.Type("item-1") + tm.Golden("02-filtered-top") + + for range 5 { + tm.Type(termtest.KeyDown) + } + tm.Golden("03-filtered-mid") + + for range 10 { + tm.Type(termtest.KeyDown) + } + tm.Golden("04-filtered-bottom") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "id19", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_filter_typing_test.go b/libs/cmdio/cmdiotest/select_baseline_filter_typing_test.go new file mode 100644 index 00000000000..76cbeb9c670 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_filter_typing_test.go @@ -0,0 +1,43 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_FilterTyping pins Select's behavior when the user types +// letters that filter the list. cmdio.Select uses StartInSearchMode: true +// with a case-insensitive substring searcher on Name, so each keystroke +// immediately narrows the visible options. +func TestSelectBaseline_FilterTyping(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + {Name: "delta", Id: "d"}, + {Name: "epsilon", Id: "e"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type("a") + tm.Golden("02-after-a") + + tm.Type("l") + tm.Golden("03-after-al") + + tm.Type("p") + tm.Golden("04-after-alp") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_long_descriptions_test.go b/libs/cmdio/cmdiotest/select_baseline_long_descriptions_test.go new file mode 100644 index 00000000000..4689f78a59c --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_long_descriptions_test.go @@ -0,0 +1,40 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_LongDescriptions pins Select's behavior when item Ids +// are long enough to potentially overflow the terminal width. The active row +// uses the Tuple template "{{.Name | bold}} ({{.Id|faint}})", so the long Id +// is only rendered on the active line; non-active rows show only the Name. +func TestSelectBaseline_LongDescriptions(t *testing.T) { + t.Parallel() + items := []cmdio.Tuple{ + {Name: "short", Id: "this-is-a-very-long-resource-identifier-that-exceeds-typical-width-1234567890"}, + {Name: "medium-length-name", Id: "another-extremely-long-id-string-with-lots-of-content-aaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + {Name: "x", Id: "yet-another-long-identifier-with-quite-a-bit-of-text-bbbbbbbbbbbbbbbbbbbbbbbbbbb"}, + } + + tm := termtest.NewSelectOrdered(t, items, "Pick a resource") + tm.WaitFor("Pick a resource") + tm.WaitFor("short") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-second-active") + + tm.Type(termtest.KeyDown) + tm.Golden("03-third-active") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, items[2].Id, id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_scroll_test.go b/libs/cmdio/cmdiotest/select_baseline_scroll_test.go new file mode 100644 index 00000000000..4c75cdfa76a --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_scroll_test.go @@ -0,0 +1,47 @@ +package cmdiotest_test + +import ( + "fmt" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_Scroll pins Select's scrolling behavior for a list +// larger than the default visible window. It feeds enough KeyDown presses +// to reach the last item and then keeps pressing past it, so the goldens +// capture both the bottom-of-list state and the past-bottom state. +func TestSelectBaseline_Scroll(t *testing.T) { + t.Parallel() + items := make([]cmdio.Tuple, 0, 12) + for i := 1; i <= 12; i++ { + items = append(items, cmdio.Tuple{ + Name: fmt.Sprintf("item-%02d", i), + Id: fmt.Sprintf("id%02d", i), + }) + } + + tm := termtest.NewSelectOrdered(t, items, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("item-01") + tm.Golden("01-initial") + + for range 11 { + tm.Type(termtest.KeyDown) + } + tm.Golden("02-bottom") + + for range 5 { + tm.Type(termtest.KeyDown) + } + tm.Golden("03-past-bottom") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "id12", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_selected_template_test.go b/libs/cmdio/cmdiotest/select_baseline_selected_template_test.go new file mode 100644 index 00000000000..21970a3e8a1 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_selected_template_test.go @@ -0,0 +1,56 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_SelectedTemplate pins the post-Enter rendering of +// [cmdio.RunSelect] when a non-empty Selected template is provided. +// +// cmdio.Select / cmdio.SelectOrdered set HideSelected:true, so the Selected +// branch is only reachable via RunSelect. Real callers that hit it: +// cmd/auth/profile_picker.go, libs/databrickscfg/profile/select.go, +// libs/databrickscfg/cfgpickers/clusters.go. Without this test, breaking the +// post-submit render or the Selected template behavior goes undetected. +func TestSelectBaseline_SelectedTemplate(t *testing.T) { + t.Parallel() + type item struct { + Name string + Id string + } + items := []item{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "c"}, + } + + tm := termtest.NewSelect(t, cmdio.SelectOptions{ + Label: "Pick one", + Items: items, + Active: `> {{ .Name }} ({{ .Id }})`, + Inactive: ` {{ .Name }} ({{ .Id }})`, + Selected: `Chose: {{ .Name }} ({{ .Id }})`, + }) + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-after-down") + + tm.Type(termtest.KeyEnter) + + idx, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, 1, idx, "snapshot:\n%s", tm.Snapshot()) + + // Pin the rendered Selected template. This is the only test that asserts + // the post-Enter frame; if the Selected template stops rendering, or the + // trailing newline / cursor handling changes, this golden catches it. + tm.Golden("03-after-enter") +} diff --git a/libs/cmdio/cmdiotest/select_baseline_single_item_test.go b/libs/cmdio/cmdiotest/select_baseline_single_item_test.go new file mode 100644 index 00000000000..51c6517121f --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_single_item_test.go @@ -0,0 +1,32 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_SingleItem pins Select's behavior when the input list +// contains exactly one entry: whether a prompt renders, what KeyDown does +// (no-op), and what id Enter returns. +func TestSelectBaseline_SingleItem(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "only", Id: "o"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("only") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-after-down") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "o", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_slash_enters_search_test.go b/libs/cmdio/cmdiotest/select_baseline_slash_enters_search_test.go new file mode 100644 index 00000000000..f4b07015a03 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_slash_enters_search_test.go @@ -0,0 +1,58 @@ +package cmdiotest_test + +import ( + "strings" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_SlashEntersSearch pins that pressing "/" toggles a +// non-search-mode select prompt into search mode. The existing filter +// tests all use cmdio.SelectOrdered (which sets StartInSearchMode=true) +// so the toggle path is never exercised. Real callers that depend on it: +// cmd/auth/resolve.go and cmd/auth/profile_picker.go set +// StartInSearchMode based on len(items) > 5, so for small lists the +// only way to filter is to press "/". +func TestSelectBaseline_SlashEntersSearch(t *testing.T) { + t.Parallel() + type item struct { + Name string + Id string + } + items := []item{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "c"}, + } + + tm := termtest.NewSelect(t, cmdio.SelectOptions{ + Label: "Pick one", + Items: items, + Searcher: func(input string, idx int) bool { + return strings.Contains(strings.ToLower(items[idx].Name), strings.ToLower(input)) + }, + Active: `> {{ .Name }} ({{ .Id }})`, + Inactive: ` {{ .Name }} ({{ .Id }})`, + }) + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial-no-search") + + // Slash toggles into search mode: a "Search:" line appears and + // subsequent characters become the filter query. + tm.Type("/") + tm.Golden("02-after-slash") + + tm.Type("b") + tm.Golden("03-filtering-b") + + tm.Type(termtest.KeyEnter) + + idx, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, 1, idx, "expected to land on beta; snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_tab_key_test.go b/libs/cmdio/cmdiotest/select_baseline_tab_key_test.go new file mode 100644 index 00000000000..9176fe5ffb1 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_tab_key_test.go @@ -0,0 +1,43 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_TabKey pins Select's behavior when the user presses +// Tab. Tab is a common navigation key but in search-mode Select it gets +// typed into the filter; this test records that, plus how Enter behaves +// after the filter has no matches. +func TestSelectBaseline_TabKey(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyTab) + tm.Golden("02-after-tab") + + tm.Type(termtest.KeyTab) + tm.Golden("03-after-second-tab") + + // Enter does not terminate the prompt: in search mode with no matching + // items (the two Tab keystrokes typed two tab characters into the + // filter), the model treats Enter as inert. Ctrl+C cancels cleanly. + tm.Type(termtest.KeyEnter) + tm.Type(termtest.KeyCtrlC) + + id, err := tm.Result() + require.Error(t, err) + assert.EqualError(t, err, "^C") + assert.Empty(t, id) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_test.go b/libs/cmdio/cmdiotest/select_baseline_test.go new file mode 100644 index 00000000000..ef09bd52ae4 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_test.go @@ -0,0 +1,34 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_DownEnter pins Select's rendering end-to-end: the +// termtest emulator captures the rendered screen and we assert on the chosen +// item plus a snapshot of the prompt and visible options. +func TestSelectBaseline_DownEnter(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "c"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type(termtest.KeyDown) + tm.Golden("02-after-down") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "b", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_vim_keys_test.go b/libs/cmdio/cmdiotest/select_baseline_vim_keys_test.go new file mode 100644 index 00000000000..18d29392350 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_vim_keys_test.go @@ -0,0 +1,42 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_VimKeys pins how Select reacts to vim-style 'j' and 'k' +// keys. cmdio.SelectOrdered runs with StartInSearchMode: true, so letters +// flow into the filter rather than acting as navigation. +func TestSelectBaseline_VimKeys(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + {Name: "delta", Id: "d"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + tm.Type("j") + tm.Golden("02-after-j") + + tm.Type("k") + tm.Golden("03-after-jk") + + tm.Type(termtest.KeyBackspace) + tm.Type(termtest.KeyBackspace) + tm.Golden("04-after-backspaces") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "a", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_vim_nav_outside_search_test.go b/libs/cmdio/cmdiotest/select_baseline_vim_nav_outside_search_test.go new file mode 100644 index 00000000000..c9a5f165684 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_vim_nav_outside_search_test.go @@ -0,0 +1,69 @@ +package cmdiotest_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_VimNavOutsideSearch pins Select's vim-style navigation +// when the prompt opens outside search mode. With StartInSearchMode=false, +// j/k move the highlighted item by one and h/l page through the list; with +// search mode enabled (the default in cmdio.SelectOrdered) those letters +// would flow into the filter instead. Real callers that hit this branch: +// cmd/auth/resolve.go and cmd/auth/profile_picker.go both set +// StartInSearchMode based on len(items) > 5, so small lists open outside +// search mode. +func TestSelectBaseline_VimNavOutsideSearch(t *testing.T) { + t.Parallel() + type item struct { + Name string + Id string + } + items := make([]item, 0, 12) + for i := 1; i <= 12; i++ { + items = append(items, item{ + Name: fmt.Sprintf("item-%02d", i), + Id: fmt.Sprintf("id%02d", i), + }) + } + + tm := termtest.NewSelect(t, cmdio.SelectOptions{ + Label: "Pick one", + Items: items, + // StartInSearchMode defaults to false; setting a Searcher + // makes the / key toggle search mode but does not auto-enter. + Searcher: func(input string, idx int) bool { + return strings.Contains(strings.ToLower(items[idx].Name), strings.ToLower(input)) + }, + Active: `> {{ .Name }} ({{ .Id }})`, + Inactive: ` {{ .Name }} ({{ .Id }})`, + }) + tm.WaitFor("Pick one") + tm.WaitFor("item-01") + tm.Golden("01-initial") + + tm.Type("j") + tm.Type("j") + tm.Golden("02-after-jj") + + tm.Type("k") + tm.Golden("03-after-k") + + tm.Type("l") + tm.Golden("04-after-l-pagedown") + + tm.Type("h") + tm.Golden("05-after-h-pageup") + + tm.Type(termtest.KeyEnter) + + idx, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, 0, idx, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_baseline_wrap_around_test.go b/libs/cmdio/cmdiotest/select_baseline_wrap_around_test.go new file mode 100644 index 00000000000..b5e771f33aa --- /dev/null +++ b/libs/cmdio/cmdiotest/select_baseline_wrap_around_test.go @@ -0,0 +1,42 @@ +package cmdiotest_test + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/cmdio/cmdiotest/termtest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectBaseline_WrapAround pins Select's behavior at the list edges: +// pressing Up on the first item and Down past the last item. +func TestSelectBaseline_WrapAround(t *testing.T) { + t.Parallel() + tm := termtest.NewSelectOrdered(t, []cmdio.Tuple{ + {Name: "alpha", Id: "a"}, + {Name: "beta", Id: "b"}, + {Name: "gamma", Id: "g"}, + }, "Pick one") + tm.WaitFor("Pick one") + tm.WaitFor("alpha") + tm.Golden("01-initial") + + // Up from the top: does the model wrap to the last item, pin to alpha, + // or do nothing visible? + tm.Type(termtest.KeyUp) + tm.Golden("02-up-from-top") + + // Five Downs: with three items, this overshoots the bottom by two, + // exposing whether Down wraps, pins, or beeps past the last item. + for range 5 { + tm.Type(termtest.KeyDown) + } + tm.Golden("03-down-past-bottom") + + tm.Type(termtest.KeyEnter) + + id, err := tm.Result() + require.NoError(t, err, "raw output: %q", tm.Raw()) + assert.Equal(t, "g", id, "snapshot:\n%s", tm.Snapshot()) +} diff --git a/libs/cmdio/cmdiotest/select_no_prompt_support_test.go b/libs/cmdio/cmdiotest/select_no_prompt_support_test.go new file mode 100644 index 00000000000..0f7e18bf803 --- /dev/null +++ b/libs/cmdio/cmdiotest/select_no_prompt_support_test.go @@ -0,0 +1,29 @@ +package cmdiotest_test + +import ( + "bytes" + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/flags" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelect_NoPromptSupport pins the early-error path: when the cmdIO can't +// prompt (non-TTY streams, no DATABRICKS_OUTPUT_FORMAT override), +// cmdio.SelectOrdered returns "expected to have