Skip to content

Commit e77282b

Browse files
committed
Merge branch 'jb/aitools-interface' into jb/aitools-list-json
2 parents d177710 + 771d066 commit e77282b

246 files changed

Lines changed: 5290 additions & 755 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agent/skills/pr-checklist/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ Before submitting a PR, run these commands to match what CI checks. CI uses the
1616
# 3. Tests (CI runs with both deployment engines)
1717
./task test
1818

19-
# 4. If you changed bundle config structs or schema-related code:
19+
# 4. If you changed bundle config structs, schema, or direct-engine resource code:
2020
./task generate-schema
21+
./task generate-direct
2122

2223
# 5. If you changed files in python/:
2324
./task pydabs-codegen pydabs-test pydabs-lint pydabs-docs

NEXT_CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
### Notable Changes
66

7-
### CLI
7+
* 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.
88

9-
* 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`).
9+
### CLI
10+
* 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.
1011
* `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.
1112

1213
### Bundles
1314
* Make sure warnings asking for approval are understood by agents ([#5239](https://github.com/databricks/cli/pull/5239))
15+
* 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.
16+
* 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)).
17+
* 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))
1418

1519
### Dependency updates
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
bundle:
2+
name: test-default-profile
3+
4+
# No workspace.host on purpose: this is the surface where
5+
# [__settings__].default_profile should be applied.

acceptance/cmd/auth/describe/u2m-plaintext-default/out.test.toml renamed to acceptance/auth/bundle_default_profile/out.test.toml

File renamed without changes.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
2+
=== Bundle without workspace.host: default_profile is honored
3+
4+
>>> [CLI] bundle validate -o json
5+
{
6+
"host": null,
7+
"profile": "default-target"
8+
}
9+
10+
=== --profile overrides default_profile (negative case)
11+
12+
>>> errcode [CLI] bundle validate -p other -o json
13+
Warn: [hostmetadata] failed to fetch host metadata for https://other.test, will skip for 1m0s
14+
Error: Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted)
15+
16+
17+
Exit code: 1
18+
{
19+
"host": null,
20+
"profile": "other"
21+
}
22+
23+
=== Bundle with workspace.host: default_profile is NOT applied
24+
25+
>>> errcode [CLI] bundle validate -o json
26+
{
27+
"host": "[DATABRICKS_URL]",
28+
"profile": null
29+
}
30+
31+
=== Bundle with workspace.profile: pinned profile wins over default_profile
32+
33+
>>> errcode [CLI] bundle validate -o json
34+
Error: Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted)
35+
36+
37+
Exit code: 1
38+
{
39+
"host": null,
40+
"profile": "other"
41+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
sethome "./home"
2+
3+
# Save the test server host so we can pin a bundle-with-host variant below.
4+
host_value="$DATABRICKS_HOST"
5+
6+
cat > "./home/.databrickscfg" <<EOF
7+
[default-target]
8+
host = $DATABRICKS_HOST
9+
token = $DATABRICKS_TOKEN
10+
11+
[other]
12+
host = https://other.test
13+
token = other-token
14+
15+
[__settings__]
16+
default_profile = default-target
17+
EOF
18+
19+
unset DATABRICKS_HOST
20+
unset DATABRICKS_TOKEN
21+
unset DATABRICKS_CONFIG_PROFILE
22+
23+
title "Bundle without workspace.host: default_profile is honored\n"
24+
trace $CLI bundle validate -o json | jq '{host: .workspace.host, profile: .workspace.profile}'
25+
26+
title "--profile overrides default_profile (negative case)\n"
27+
trace errcode $CLI bundle validate -p other -o json | jq '{host: .workspace.host, profile: .workspace.profile}'
28+
29+
# Switch to a bundle that pins workspace.host. The default_profile guard in
30+
# configureProfile must NOT apply default_profile here, because that would
31+
# silently route the user to a profile pointing at a different host than the
32+
# bundle declares.
33+
mkdir -p ./bundle-with-host
34+
cat > ./bundle-with-host/databricks.yml <<EOF
35+
bundle:
36+
name: bundle-with-host
37+
38+
workspace:
39+
host: $host_value
40+
EOF
41+
42+
title "Bundle with workspace.host: default_profile is NOT applied\n"
43+
(cd ./bundle-with-host && trace errcode $CLI bundle validate -o json | jq '{host: .workspace.host, profile: .workspace.profile}')
44+
45+
# Switch to a bundle that pins workspace.profile but no host. The pinned
46+
# profile must win over default_profile — configureProfile's guard skips
47+
# default_profile when workspace.profile is already set.
48+
mkdir -p ./bundle-with-profile
49+
cat > ./bundle-with-profile/databricks.yml <<EOF
50+
bundle:
51+
name: bundle-with-profile
52+
53+
workspace:
54+
profile: other
55+
EOF
56+
57+
title "Bundle with workspace.profile: pinned profile wins over default_profile\n"
58+
(cd ./bundle-with-profile && trace errcode $CLI bundle validate -o json | jq '{host: .workspace.host, profile: .workspace.profile}')
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Ignore = [
2+
"home",
3+
".databricks",
4+
"bundle-with-host",
5+
"bundle-with-profile",
6+
]
7+
8+
# Negative case: -p other tries to reach a non-existing host. Redact the
9+
# OS-/network-dependent suffix so the test is stable across runners.
10+
[[Repls]]
11+
Old = 'Get "https://other.test/api/2.0/preview/scim/v2/Me": .*'
12+
New = 'Get "https://other.test/api/2.0/preview/scim/v2/Me": (redacted)'

acceptance/bin/assert_exists.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env python3
2+
import os, sys
3+
4+
errors = 0
5+
6+
for filename in sys.argv[1:]:
7+
if not os.path.exists(filename):
8+
sys.stderr.write(f"Unexpected: {filename} does not exist.\n")
9+
errors += 1
10+
11+
if errors:
12+
sys.exit(1)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env python3
2+
import os, sys
3+
4+
errors = 0
5+
6+
for filename in sys.argv[1:]:
7+
if os.path.exists(filename):
8+
sys.stderr.write(f"Unexpected: {filename} exists.\n")
9+
errors += 1
10+
11+
if errors:
12+
sys.exit(1)

acceptance/bin/kill_after.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python3
2+
"""Set up a kill rule on the testserver for the current test token.
3+
4+
Usage: kill_after.py PATTERN OFFSET TIMES
5+
6+
PATTERN HTTP method and path, e.g. "POST /api/2.2/jobs/create"
7+
OFFSET number of requests to let through before killing starts
8+
TIMES number of times to kill the caller
9+
10+
The rule is scoped to the current DATABRICKS_TOKEN so it only affects
11+
the test that registers it, even when tests share a server.
12+
"""
13+
14+
import json
15+
import os
16+
import sys
17+
import urllib.request
18+
19+
host = os.environ.get("DATABRICKS_HOST", "")
20+
token = os.environ.get("DATABRICKS_TOKEN", "")
21+
22+
if not host:
23+
print("DATABRICKS_HOST not set", file=sys.stderr)
24+
sys.exit(1)
25+
26+
if len(sys.argv) != 4:
27+
print(f"usage: {sys.argv[0]} PATTERN OFFSET TIMES", file=sys.stderr)
28+
sys.exit(1)
29+
30+
pattern, offset, times = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
31+
32+
data = json.dumps({"pattern": pattern, "offset": offset, "times": times}).encode()
33+
req = urllib.request.Request(
34+
f"{host}/__testserver/kill",
35+
data=data,
36+
headers={"Content-Type": "application/json", "Authorization": f"Bearer {token}"},
37+
method="POST",
38+
)
39+
urllib.request.urlopen(req)

0 commit comments

Comments
 (0)