Skip to content

Commit 6a0cd78

Browse files
feat(bots): local install-bot-engine composite (PAT-free, egress-safe)
External consumers cannot `uses:` the internal engine's actions/reusable workflows cross-repo (GitHub: "not found"). This local composite instead pip-installs the pinned engine over HTTPS with a short-lived, engine-scoped GitHub App token (no stored BOT_ENGINE_PAT), routing pip/npm through the internal JFrog mirror for the egress-blocked protected runner. Shared by all four bot workflows. Co-authored-by: Isaac Signed-off-by: eric-wang-1990 <e.wang@databricks.com>
1 parent 614dd91 commit 6a0cd78

1 file changed

Lines changed: 115 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Install databricks-bot-engine (pinned REF) + the Claude Agent SDK/CLI.
2+
#
3+
# WHY THIS IS A LOCAL COMPOSITE (not `uses: databricks/databricks-bot-engine/...`):
4+
# databricks-sql-python cannot reference the engine's actions/reusable workflows
5+
# cross-repo — GitHub fails with "Unable to resolve action ..., not found" (the
6+
# internal engine repo's Actions aren't consumable from an external repo; adbc's
7+
# workflows note the same: "a cross-repo `uses:` of the hub's internal composite
8+
# action does NOT work"). So we do NOT `uses:` engine actions. Instead this local
9+
# action `pip install`s the engine over HTTPS with a token — a plain clone, which
10+
# an external repo CAN do — inlining what the engine's own install-bot-engine
11+
# action does in REF mode. Keep in sync with that action at the pinned engine SHA.
12+
#
13+
# Auth is PAT-FREE: the caller passes a short-lived, engine-scoped GitHub App
14+
# installation token as `engine-token` (minted from the bot App creds). It slots
15+
# into the same git auth path a PAT would; nothing stored/long-lived.
16+
name: Install bot engine (local)
17+
description: pip-install the pinned databricks-bot-engine + Claude Agent SDK/CLI, PAT-free (engine-scoped App token).
18+
19+
inputs:
20+
engine-ref:
21+
description: 'Engine commit SHA (full 40-char) to install.'
22+
required: true
23+
engine-repo:
24+
description: 'owner/name of the engine repo.'
25+
required: false
26+
default: 'databricks/databricks-bot-engine'
27+
engine-token:
28+
description: 'Short-lived engine-scoped GitHub App installation token (contents:read). Authenticates the private-engine clone; no stored PAT.'
29+
required: true
30+
use-jfrog:
31+
description: 'Route pip/npm through the internal JFrog mirror (egress-blocked runners). Requires id-token: write on the job.'
32+
required: false
33+
default: 'true'
34+
sdk-version:
35+
description: 'claude-agent-sdk version. Keep in sync with the engine at the pinned SHA.'
36+
required: false
37+
default: '0.2.102'
38+
cli-version:
39+
description: '@anthropic-ai/claude-code version. Keep in sync with the engine at the pinned SHA.'
40+
required: false
41+
default: '2.1.61'
42+
43+
runs:
44+
using: composite
45+
steps:
46+
- name: Configure JFrog mirror (pip + npm)
47+
if: inputs.use-jfrog == 'true'
48+
shell: bash
49+
run: |
50+
set -euo pipefail
51+
# Keyless: mint a GitHub OIDC token, exchange it for a short-lived JFrog
52+
# access token (the protected runner group is egress-blocked from
53+
# pypi.org / registry.npmjs.org). Requires the job to grant id-token: write.
54+
ID_TOKEN=$(curl -sLS \
55+
-H "User-Agent: actions/oidc-client" \
56+
-H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
57+
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=jfrog-github" | jq .value | tr -d '"')
58+
echo "::add-mask::${ID_TOKEN}"
59+
ACCESS_TOKEN=$(curl -sLS -XPOST -H "Content-Type: application/json" \
60+
"https://databricks.jfrog.io/access/api/v1/oidc/token" \
61+
-d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"${ID_TOKEN}\", \"provider_name\": \"github-actions\"}" | jq .access_token | tr -d '"')
62+
echo "::add-mask::${ACCESS_TOKEN}"
63+
if [ -z "$ACCESS_TOKEN" ] || [ "$ACCESS_TOKEN" = "null" ]; then
64+
echo "::error::Could not extract JFrog access token (is id-token:write granted?)"; exit 1
65+
fi
66+
echo "PIP_INDEX_URL=https://gha-service-account:${ACCESS_TOKEN}@databricks.jfrog.io/artifactory/api/pypi/db-pypi/simple" >> "$GITHUB_ENV"
67+
cat > ~/.npmrc << EOF
68+
registry=https://databricks.jfrog.io/artifactory/api/npm/db-npm/
69+
//databricks.jfrog.io/artifactory/api/npm/db-npm/:_authToken=${ACCESS_TOKEN}
70+
always-auth=true
71+
EOF
72+
echo "JFrog mirror configured for pip + npm"
73+
74+
- name: Install engine (pinned REF) + Claude SDK/CLI
75+
shell: bash
76+
env:
77+
ENGINE_REF: ${{ inputs.engine-ref }}
78+
ENGINE_REPO: ${{ inputs.engine-repo }}
79+
ENGINE_TOKEN: ${{ inputs.engine-token }}
80+
SDK_VERSION: ${{ inputs.sdk-version }}
81+
CLI_VERSION: ${{ inputs.cli-version }}
82+
run: |
83+
set -euo pipefail
84+
# Supply-chain: require a full 40-char commit SHA (immutable pin). `case`
85+
# globs match the whole string, so an embedded newline is rejected too.
86+
case "$ENGINE_REF" in
87+
*[!0-9a-f]* | "")
88+
echo "::error::engine-ref must be a full 40-char commit SHA, got '$ENGINE_REF'"; exit 1 ;;
89+
esac
90+
if [ "${#ENGINE_REF}" -ne 40 ]; then
91+
echo "::error::engine-ref must be a full 40-char commit SHA, got '$ENGINE_REF'"; exit 1
92+
fi
93+
if [ -z "$ENGINE_TOKEN" ]; then
94+
echo "::error::engine-token is required (engine repo is private)"; exit 1
95+
fi
96+
# Supply the token via a git extraheader, NOT in the URL handed to pip:
97+
# pip echoes the requirement spec (incl. any in-URL credential) in normal
98+
# and error output where Actions masking is not guaranteed. The header
99+
# lives only in a JOB-LOCAL git config for this job. Mask the token + its
100+
# base64 form defensively.
101+
echo "::add-mask::${ENGINE_TOKEN}"
102+
BASIC=$(printf 'x-access-token:%s' "$ENGINE_TOKEN" | base64 | tr -d '\n')
103+
echo "::add-mask::${BASIC}"
104+
export GIT_CONFIG_GLOBAL="${RUNNER_TEMP:-/tmp}/bot-engine-gitconfig-$$"
105+
: > "$GIT_CONFIG_GLOBAL"
106+
trap 'git config --global --unset-all "http.https://github.com/.extraheader" || true; rm -f "$GIT_CONFIG_GLOBAL"' EXIT
107+
git config --global "http.https://github.com/.extraheader" "Authorization: Basic ${BASIC}"
108+
python -m pip install --upgrade pip
109+
# REF mode resolves the published engine's deps unpinned + installs the
110+
# SDK/CLI by version (a consumer can't see the engine's hash-locked
111+
# requirements). SDK_VERSION/CLI_VERSION track the engine at ENGINE_REF.
112+
pip install "databricks-bot-engine @ git+https://github.com/${ENGINE_REPO}@${ENGINE_REF}"
113+
pip install "claude-agent-sdk==${SDK_VERSION}"
114+
npm install -g "@anthropic-ai/claude-code@${CLI_VERSION}"
115+
echo "$(npm prefix -g)/bin" >> "$GITHUB_PATH"

0 commit comments

Comments
 (0)