@databricks-solutions/lakebase-scm-utils 0.2.47 → 0.2.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/ci-secrets-CnGrdQV-.d.cts +84 -0
  2. package/dist/ci-secrets-CnGrdQV-.d.ts +84 -0
  3. package/dist/scripts/index.cjs +2230 -2052
  4. package/dist/scripts/index.d.cts +3 -2
  5. package/dist/scripts/index.d.ts +3 -2
  6. package/dist/scripts/index.js +305 -134
  7. package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
  8. package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
  9. package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
  10. package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
  11. package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
  12. package/dist/scripts/lakebase/detect-language.cli.js +2 -2
  13. package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
  14. package/dist/scripts/lakebase/doctor.cli.js +2 -2
  15. package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
  16. package/dist/scripts/lakebase/get-connection.cli.js +2 -2
  17. package/dist/scripts/lakebase/index.cjs +2216 -2050
  18. package/dist/scripts/lakebase/index.d.cts +37 -11
  19. package/dist/scripts/lakebase/index.d.ts +37 -11
  20. package/dist/scripts/lakebase/index.js +295 -130
  21. package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
  22. package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
  23. package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
  24. package/dist/scripts/lakebase/new-migration.cli.js +2 -2
  25. package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
  26. package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
  27. package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
  28. package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
  29. package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
  30. package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
  31. package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
  32. package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
  33. package/dist/scripts/lakebase/scm-merge.cli.cjs +2345 -1994
  34. package/dist/scripts/lakebase/scm-merge.cli.js +425 -74
  35. package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +479 -86
  36. package/dist/scripts/lakebase/scm-prepare-pr.cli.js +479 -86
  37. package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
  38. package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
  39. package/dist/scripts/lakebase/sync-ci-secrets.cli.cjs +56 -30
  40. package/dist/scripts/lakebase/sync-ci-secrets.cli.js +56 -30
  41. package/dist/scripts/util/index.cjs +142 -25
  42. package/dist/scripts/util/index.d.cts +2 -27
  43. package/dist/scripts/util/index.d.ts +2 -27
  44. package/dist/scripts/util/index.js +136 -25
  45. package/package.json +1 -1
  46. package/scripts/lakebase/create-project.ts +2 -2
  47. package/scripts/lakebase/scm-merge.ts +112 -0
  48. package/scripts/lakebase/scm-prepare-pr.ts +10 -1
  49. package/scripts/lakebase/sync-ci-secrets.cli.ts +2 -2
  50. package/scripts/util/ci-secrets.ts +212 -26
  51. package/templates/project/common/.gitignore.base +4 -6
  52. package/templates/project/common/scripts/create-token-and-sync-secrets.sh +9 -86
@@ -1,12 +1,19 @@
1
- // Sync CI secrets (DATABRICKS_HOST, LAKEBASE_PROJECT_ID, DATABRICKS_TOKEN)
2
- // to the GitHub repo. Mints a fresh Databricks PAT.
1
+ // The ONE home for the repo's CI auth: mint/refresh the Databricks credential and sync
2
+ // DATABRICKS_HOST + LAKEBASE_PROJECT_ID + DATABRICKS_TOKEN to the GitHub repo's Actions
3
+ // secrets. create-project, the lakebase-sync-ci-secrets repair CLI, the seed rehydrate
4
+ // (setup.sh Step 7g), the scaffolded pre-push hook, and the prepare-pr / scm-merge expiry
5
+ // preflight all funnel through here, so there is exactly ONE CI-token identity:
6
+ // comment = `GitHub Actions (<repo>)` (project-scoped, matchable in `databricks tokens list`)
7
+ // lifetime = 90 days (durable: a CI rerun / downstream migrate that fires
8
+ // hours after the push still authenticates — FEIP-8020)
9
+ // A workspace with PATs disabled falls back to a short-lived (~1h) OAuth session token.
3
10
  //
4
- // Caller passes the values directly. Previously this read .env from disk,
5
- // but since createProject no longer writes .env (option 3 – the only on-disk
6
- // .env is created later by the post-checkout hook, after CI secrets need to
7
- // be set), the values have to come from the create-project caller's scope.
11
+ // Caller passes host + project-id directly (create-project no longer writes .env; the only
12
+ // on-disk .env is created later by the post-checkout hook, after CI secrets need to be set).
8
13
 
14
+ import * as path from "node:path";
9
15
  import { runDatabricks } from "../lakebase/databricks-cli.js";
16
+ import { readEnvVar } from "../lakebase/env-file.js";
10
17
  import { setRepoSecrets, listSecretNames } from "../github/secrets.js";
11
18
  import { getOwnerRepo } from "../git/remote.js";
12
19
 
@@ -15,9 +22,27 @@ import { getOwnerRepo } from "../git/remote.js";
15
22
  * that otherwise only surfaces at promotion, an entire feature after creation. */
16
23
  export const REQUIRED_CI_SECRETS = ["DATABRICKS_HOST", "LAKEBASE_PROJECT_ID", "DATABRICKS_TOKEN"] as const;
17
24
 
25
+ /** The canonical CI-token lifetime: 90 days. Durable so a CI rerun / downstream migrate that
26
+ * fires long after the push still authenticates (the ~1h OAuth session expiry bug, FEIP-8020). */
27
+ export const CI_TOKEN_LIFETIME_SECONDS = 7_776_000;
28
+
29
+ /** Default margin: re-mint when the live CI token expires within this window of now, so the
30
+ * about-to-fire CI run gets a token that comfortably outlives it (and a 24h bootstrap token
31
+ * is upgraded to the durable one at the first preflight). */
32
+ export const CI_TOKEN_REMINT_MARGIN_SECONDS = 86_400;
33
+
34
+ /** The ONE CI-token comment, project-scoped so it is matchable in `databricks tokens list`
35
+ * (the expiry probe) and distinct per repo. `ownerRepo` is `owner/name`; the repo NAME is used
36
+ * (matching the scaffolded create-token-and-sync-secrets.sh convention). */
37
+ export function ciTokenComment(ownerRepo: string): string {
38
+ const repoName = ownerRepo.includes("/") ? ownerRepo.slice(ownerRepo.lastIndexOf("/") + 1) : ownerRepo;
39
+ return `GitHub Actions (${repoName})`;
40
+ }
41
+
18
42
  /** Which required CI secrets are NOT present on the repo (empty = fully provisioned). Used to
19
43
  * VERIFY a sync landed — a `databricks tokens create` or `gh secret set` that fails must be
20
- * caught here, not swallowed, so CI-auth is never silently absent. */
44
+ * caught here, not swallowed, so CI-auth is never silently absent. NOTE: this checks the secret
45
+ * NAME exists, not whether the token behind it is still valid — expiry is {@link ciTokenExpiry}. */
21
46
  export async function missingCiSecrets(ownerRepo: string): Promise<string[]> {
22
47
  const present = new Set(await listSecretNames(ownerRepo));
23
48
  return REQUIRED_CI_SECRETS.filter((n) => !present.has(n));
@@ -31,19 +56,54 @@ export interface SyncCiSecretsArgs {
31
56
  databricksHost: string;
32
57
  /** Lakebase project id (LAKEBASE_PROJECT_ID secret). Required. */
33
58
  lakebaseProjectId: string;
34
- /** Token comment for `databricks tokens create`. */
59
+ /** Token comment for `databricks tokens create`. Default: the canonical {@link ciTokenComment}. */
35
60
  comment?: string;
36
- /** Token lifetime in seconds (default: 24h). */
61
+ /** Token lifetime in seconds. Default: {@link CI_TOKEN_LIFETIME_SECONDS} (90 days). */
37
62
  lifetimeSeconds?: number;
38
63
  /** Override the auto-detected ownerRepo (defaults to origin remote). */
39
64
  ownerRepo?: string;
40
65
  }
41
66
 
42
- /** Synchronize Databricks + Lakebase CI secrets to the repo's Actions secrets. */
43
- export async function syncCiSecrets(args: SyncCiSecretsArgs): Promise<void> {
44
- const lifetime = args.lifetimeSeconds ?? 86_400;
45
- const comment = args.comment ?? "GitHub Actions CI";
67
+ /** Mint the CI credential: a durable PAT with the canonical comment + lifetime, falling back to
68
+ * a short-lived (~1h) OAuth session token where the workspace disables PATs. Returns the token
69
+ * value, or "" if neither path produced one (caller then ships HOST/PROJECT_ID only). */
70
+ async function mintCiToken(args: {
71
+ databricksHost: string;
72
+ projectDir: string;
73
+ comment: string;
74
+ lifetimeSeconds: number;
75
+ }): Promise<string> {
76
+ // Prefer a durable PAT so reruns past the ~1h OAuth session don't silently fail auth.
77
+ try {
78
+ const raw = await runDatabricks(
79
+ ["tokens", "create", "--comment", args.comment, "--lifetime-seconds", String(args.lifetimeSeconds), "-o", "json"],
80
+ { host: args.databricksHost, cwd: args.projectDir, timeout: 30_000 },
81
+ );
82
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
83
+ const token = parsed.token_value || parsed.token || "";
84
+ if (token) return token;
85
+ } catch {
86
+ // PAT mint failed (often: workspace disables PAT creation) — fall through to OAuth.
87
+ }
88
+ // Fallback: OAuth session token (~1h). The pre-push hook re-mints on every push; a rerun that
89
+ // fires >1h after the last push will fail — PATs are strongly preferred where allowed.
90
+ try {
91
+ const raw = await runDatabricks(["auth", "token", "-o", "json"], {
92
+ host: args.databricksHost,
93
+ cwd: args.projectDir,
94
+ timeout: 30_000,
95
+ });
96
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("{"))));
97
+ return parsed.access_token || "";
98
+ } catch {
99
+ return "";
100
+ }
101
+ }
46
102
 
103
+ /** Synchronize Databricks + Lakebase CI secrets to the repo's Actions secrets. Mints a fresh
104
+ * canonical CI credential; a dropped mint is fail-soft (HOST/PROJECT_ID still ship) — the caller
105
+ * verifies via {@link missingCiSecrets} and surfaces the gap. */
106
+ export async function syncCiSecrets(args: SyncCiSecretsArgs): Promise<void> {
47
107
  const ownerRepo = args.ownerRepo ?? (await getOwnerRepo(args.projectDir));
48
108
  if (!ownerRepo) {
49
109
  throw new Error("Could not resolve GitHub repository from git remote");
@@ -55,26 +115,152 @@ export async function syncCiSecrets(args: SyncCiSecretsArgs): Promise<void> {
55
115
  throw new Error("syncCiSecrets: lakebaseProjectId is required");
56
116
  }
57
117
 
118
+ const lifetime = args.lifetimeSeconds ?? CI_TOKEN_LIFETIME_SECONDS;
119
+ const comment = args.comment ?? ciTokenComment(ownerRepo);
120
+
58
121
  const secrets: Record<string, string> = {
59
122
  DATABRICKS_HOST: args.databricksHost,
60
123
  LAKEBASE_PROJECT_ID: args.lakebaseProjectId,
61
124
  };
62
125
 
63
- // Mint a fresh PAT for CI. If this fails, ship the non-secret pair anyway –
64
- // a partially-configured repo is still better than nothing (the user can
65
- // re-mint via the in-project refresh-token script), and the caller logs the
66
- // warning. Auth workflows will fail loudly with a clear message until then.
126
+ const token = await mintCiToken({
127
+ databricksHost: args.databricksHost,
128
+ projectDir: args.projectDir,
129
+ comment,
130
+ lifetimeSeconds: lifetime,
131
+ });
132
+ if (token) secrets.DATABRICKS_TOKEN = token;
133
+
134
+ await setRepoSecrets(ownerRepo, secrets);
135
+ }
136
+
137
+ export interface CiTokenExpiryArgs {
138
+ /** Project root (cwd for the CLI call + .env-based profile resolution). */
139
+ projectDir: string;
140
+ /** Workspace host the tokens live on. */
141
+ databricksHost: string;
142
+ /** ownerRepo (`owner/name`) — selects the canonical comment to match. */
143
+ ownerRepo: string;
144
+ }
145
+
146
+ /** The latest expiry (epoch ms) of a LIVE canonical CI token for this repo, or null if none.
147
+ * `databricks tokens list` returns ONLY valid (unexpired) tokens, so an expired one simply does
148
+ * not appear → null. A no-expiry token (expiry_time -1) reads as Infinity. Fail-soft: any CLI /
149
+ * parse error returns null (→ the preflight re-mints, the safe direction). */
150
+ export async function ciTokenExpiry(args: CiTokenExpiryArgs): Promise<number | null> {
151
+ const comment = ciTokenComment(args.ownerRepo);
152
+ let raw: string;
67
153
  try {
68
- const tokenRaw = await runDatabricks(
69
- ["tokens", "create", "--comment", comment, "--lifetime-seconds", String(lifetime), "-o", "json"],
70
- { host: args.databricksHost, cwd: args.projectDir, timeout: 30_000 },
71
- );
72
- const parsed = JSON.parse(tokenRaw);
73
- const token = parsed.token_value || parsed.token || "";
74
- if (token) secrets.DATABRICKS_TOKEN = token;
154
+ raw = await runDatabricks(["tokens", "list", "-o", "json"], {
155
+ host: args.databricksHost,
156
+ cwd: args.projectDir,
157
+ timeout: 30_000,
158
+ });
75
159
  } catch {
76
- // PAT mint failed – proceed with HOST/PROJECT_ID only.
160
+ return null;
77
161
  }
162
+ let infos: Array<{ comment?: string; expiry_time?: number }>;
163
+ try {
164
+ const parsed = JSON.parse(raw.slice(Math.max(0, raw.indexOf("["))));
165
+ infos = Array.isArray(parsed) ? parsed : [];
166
+ } catch {
167
+ return null;
168
+ }
169
+ const expiries = infos
170
+ .filter((t) => t.comment === comment)
171
+ .map((t) => (t.expiry_time === -1 ? Number.POSITIVE_INFINITY : Number(t.expiry_time)))
172
+ .filter((n) => Number.isFinite(n) || n === Number.POSITIVE_INFINITY);
173
+ if (expiries.length === 0) return null;
174
+ return Math.max(...expiries);
175
+ }
78
176
 
79
- await setRepoSecrets(ownerRepo, secrets);
177
+ export interface EnsureCiSecretsFreshArgs {
178
+ projectDir: string;
179
+ databricksHost: string;
180
+ lakebaseProjectId: string;
181
+ /** Override the auto-detected ownerRepo (defaults to origin remote). */
182
+ ownerRepo?: string;
183
+ /** Re-mint when the live token expires within this window. Default: {@link CI_TOKEN_REMINT_MARGIN_SECONDS}. */
184
+ marginSeconds?: number;
185
+ /** Injected clock (ms) for tests. Defaults to Date.now. */
186
+ now?: () => number;
187
+ }
188
+
189
+ export interface CiFreshnessResult {
190
+ action: "ok" | "provisioned" | "reminted" | "skipped" | "failed";
191
+ reason: string;
192
+ }
193
+
194
+ /** The expiry-aware preflight: ensure the repo's CI auth is CURRENT before a CI-triggering action
195
+ * (prepare-pr, scm-merge). Re-mints the canonical token when a required secret is missing, when
196
+ * no live canonical token exists (expired / never minted), or when the live token expires within
197
+ * the margin. A fresh token is left untouched. Throws only if a needed re-mint's sync throws;
198
+ * the caller runs it fail-soft (CI would fail anyway, so a mint failure is a loud warning). */
199
+ export async function ensureCiSecretsFresh(args: EnsureCiSecretsFreshArgs): Promise<CiFreshnessResult> {
200
+ const now = args.now ?? Date.now;
201
+ const marginMs = (args.marginSeconds ?? CI_TOKEN_REMINT_MARGIN_SECONDS) * 1000;
202
+ const ownerRepo = args.ownerRepo ?? (await getOwnerRepo(args.projectDir));
203
+ if (!ownerRepo) {
204
+ throw new Error("Could not resolve GitHub repository from git remote");
205
+ }
206
+
207
+ const sync = () =>
208
+ syncCiSecrets({
209
+ projectDir: args.projectDir,
210
+ databricksHost: args.databricksHost,
211
+ lakebaseProjectId: args.lakebaseProjectId,
212
+ ownerRepo,
213
+ });
214
+
215
+ const missing = await missingCiSecrets(ownerRepo);
216
+ if (missing.length > 0) {
217
+ await sync();
218
+ return { action: "provisioned", reason: `CI secret(s) were missing (${missing.join(", ")}); provisioned.` };
219
+ }
220
+
221
+ const expiry = await ciTokenExpiry({ projectDir: args.projectDir, databricksHost: args.databricksHost, ownerRepo });
222
+ if (expiry === null) {
223
+ await sync();
224
+ return { action: "reminted", reason: "no live CI token found (expired or minted under a different identity); re-minted." };
225
+ }
226
+ if (expiry !== Number.POSITIVE_INFINITY && expiry < now() + marginMs) {
227
+ await sync();
228
+ const hrs = Math.max(0, Math.round((expiry - now()) / 3_600_000));
229
+ return { action: "reminted", reason: `CI token expires in ~${hrs}h (within the re-mint margin); re-minted.` };
230
+ }
231
+ return { action: "ok", reason: "CI token is current." };
232
+ }
233
+
234
+ /** The fail-soft entry both CI-dispatch seams (prepare-pr, scm-merge) use: resolve host +
235
+ * project-id from the project's `.env`, run {@link ensureCiSecretsFresh}, and NEVER throw —
236
+ * CI auth must never block opening a PR or promoting. Returns a `skipped` result when `.env`
237
+ * lacks the inputs (e.g. the pre-push hook will still refresh on the push) and a `failed` result
238
+ * when a needed re-mint's sync throws (surfaced as a loud note, not a block). */
239
+ export async function ensureCiSecretsFreshFromEnv(
240
+ projectDir: string,
241
+ opts?: { ownerRepo?: string; marginSeconds?: number },
242
+ ): Promise<CiFreshnessResult> {
243
+ const envPath = path.join(projectDir, ".env");
244
+ const databricksHost = readEnvVar(envPath, "DATABRICKS_HOST");
245
+ const lakebaseProjectId = readEnvVar(envPath, "LAKEBASE_PROJECT_ID");
246
+ if (!databricksHost || !lakebaseProjectId) {
247
+ return {
248
+ action: "skipped",
249
+ reason: "CI-auth preflight skipped: .env is missing DATABRICKS_HOST / LAKEBASE_PROJECT_ID.",
250
+ };
251
+ }
252
+ try {
253
+ return await ensureCiSecretsFresh({
254
+ projectDir,
255
+ databricksHost,
256
+ lakebaseProjectId,
257
+ ownerRepo: opts?.ownerRepo,
258
+ marginSeconds: opts?.marginSeconds,
259
+ });
260
+ } catch (err) {
261
+ return {
262
+ action: "failed",
263
+ reason: `CI-auth preflight could not re-mint (${err instanceof Error ? err.message : String(err)}); if CI fails on auth, run lakebase-sync-ci-secrets.`,
264
+ };
265
+ }
80
266
  }
@@ -29,13 +29,11 @@ application-local.properties
29
29
  # it on the feature branch at each cut so the spec history survives for later stories).
30
30
  .consort/cycles/
31
31
  .consort/experiments/
32
- # The live build-pipeline pointer. It lives PER-FEATURE (.consort/features/<id>/pipeline.json);
33
- # the bare `.consort/pipeline.json` is kept for older layouts. Matching only the top-level path
34
- # left the per-feature file TRACKED — so it travelled onto staging/main and collided at the
35
- # promotion merge (two branches editing one run's gate-bookkeeping), the exact "pipeline.json
36
- # conflict blocks promotion" defect. Both forms are run-state, never the design corpus.
37
32
  .consort/pipeline.json
38
- .consort/features/*/pipeline.json
33
+ # NOTE: the PER-FEATURE ledger .consort/features/<id>/pipeline.json is deliberately NOT ignored —
34
+ # it is the durable per-story pipeline state (persisted on the feature branch so it survives across
35
+ # sessions). Ignoring it lost the ledger. A promotion-merge conflict on it is resolved at the MERGE
36
+ # layer (scm-merge reconciles it to the run side), never by untracking it.
39
37
  # The turn recorder's per-run observability: its internal file-hash map
40
38
  # (.recorder-state.json, used for delta computation), the routing-decision
41
39
  # diagnostic stream (routing-decisions.jsonl), and the per-turn record dirs
@@ -1,11 +1,14 @@
1
1
  #!/usr/bin/env bash
2
- # Mint a Databricks PAT and sync it (plus DATABRICKS_HOST + LAKEBASE_PROJECT_ID)
2
+ # Mint the canonical CI credential and sync it (plus DATABRICKS_HOST + LAKEBASE_PROJECT_ID)
3
3
  # to GitHub repo secrets for CI.
4
4
  #
5
- # Prefers a long-lived PAT (90 days) over the OAuth session token so that
6
- # GitHub Actions reruns – which can fire hours after the last push – do not
7
- # silently fail when the short-lived (~1h) OAuth session expires. Falls back
8
- # to OAuth only if the workspace disables PAT creation.
5
+ # Thin delegator to the ONE helper — `lakebase-sync-ci-secrets` — so the CI-token identity
6
+ # (a durable 90-day PAT, comment "GitHub Actions (<repo>)", with a short-lived OAuth fallback
7
+ # where PATs are disabled) lives in exactly one place (scm-utils ci-secrets.ts), shared by
8
+ # create-project, the repair CLI, the seed rehydrate, this pre-push hook, and the prepare-pr /
9
+ # scm-merge expiry preflight. The helper resolves DATABRICKS_HOST + LAKEBASE_PROJECT_ID from
10
+ # .env and the target repo from the origin remote, mints the token, sets the three secrets, and
11
+ # verifies them (non-zero exit names the gap — the pre-push hook WARNS without blocking).
9
12
  #
10
13
  # The pre-push hook calls this on every push. Run it manually before:
11
14
  # - `gh pr create` / `gh run rerun` / manual workflow_dispatch triggers
@@ -13,87 +16,7 @@
13
16
  #
14
17
  # Prereq: run `databricks auth login` first. LAKEBASE_PROJECT_ID must be set (e.g. in .env).
15
18
  # Usage: ./scripts/create-token-and-sync-secrets.sh
16
-
17
19
  set -e
18
20
  REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || true
19
21
  cd "${REPO_ROOT:-.}"
20
- SCRIPT_DIR="$(dirname "$0")"
21
-
22
- # Load .env so LAKEBASE_PROJECT_ID (and optionally DATABRICKS_HOST) are available
23
- if [ -f .env ]; then
24
- set -a
25
- # shellcheck source=/dev/null
26
- source .env
27
- set +a
28
- fi
29
-
30
- # Require jq and databricks
31
- if ! command -v jq >/dev/null 2>&1; then
32
- echo "jq is required. Install with: brew install jq (or apt-get install jq)"
33
- exit 1
34
- fi
35
- if ! command -v databricks >/dev/null 2>&1; then
36
- echo "databricks CLI is required. Install and run: databricks auth login"
37
- exit 1
38
- fi
39
-
40
- # Resolve DATABRICKS_HOST from auth env if not set
41
- if [ -z "${DATABRICKS_HOST:-}" ]; then
42
- AUTH_JSON="$(databricks auth env -o json 2>/dev/null)" || true
43
- DATABRICKS_HOST="$(echo "${AUTH_JSON:-}" | jq -r '.env.DATABRICKS_HOST // empty' 2>/dev/null)" || true
44
- fi
45
- if [ -z "${DATABRICKS_HOST:-}" ]; then
46
- echo "DATABRICKS_HOST not set and could not be read from 'databricks auth env'. Run 'databricks auth login' or set DATABRICKS_HOST in .env"
47
- exit 1
48
- fi
49
-
50
- # LAKEBASE_PROJECT_ID must be set (e.g. in .env)
51
- if [ -z "${LAKEBASE_PROJECT_ID:-}" ]; then
52
- echo "LAKEBASE_PROJECT_ID is required. Set it in .env or export it, then re-run this script."
53
- exit 1
54
- fi
55
-
56
- # Resolve CLI profile for this workspace
57
- PROFILE=""
58
- if [ -n "${DATABRICKS_CONFIG_PROFILE:-}" ]; then
59
- PROFILE="--profile ${DATABRICKS_CONFIG_PROFILE}"
60
- elif grep -q "$(echo "$DATABRICKS_HOST" | sed 's|https://||; s|/$||')" ~/.databrickscfg 2>/dev/null; then
61
- PROFILE="--profile $(grep -B1 "$(echo "$DATABRICKS_HOST" | sed 's|https://||; s|/$||')" ~/.databrickscfg 2>/dev/null | head -1 | tr -d '[]')"
62
- fi
63
-
64
- # Prefer a long-lived PAT (90 days by default) so CI reruns past the ~1h
65
- # OAuth session lifetime don't silently fail auth. Override via
66
- # TOKEN_LIFETIME_SECONDS; the API caps workspace-token lifetimes per tenant.
67
- TOKEN_KIND=""
68
- REPO_NAME="$(basename "$(git remote get-url origin 2>/dev/null)" 2>/dev/null | sed 's/\.git$//')" || REPO_NAME="repo"
69
- COMMENT="GitHub Actions ($REPO_NAME)"
70
- LIFETIME_SECONDS="${TOKEN_LIFETIME_SECONDS:-7776000}" # 90 days
71
-
72
- echo "Minting PAT (${LIFETIME_SECONDS}s lifetime)..."
73
- CREATE_OUT="$(databricks tokens create --comment "$COMMENT" --lifetime-seconds "$LIFETIME_SECONDS" -o json 2>&1)" || true
74
- TOKEN_VALUE="$(echo "$CREATE_OUT" | jq -r '.token_value // .token // empty' 2>/dev/null)" || true
75
- if [ -n "$TOKEN_VALUE" ]; then
76
- TOKEN_KIND="PAT (${LIFETIME_SECONDS}s)"
77
- fi
78
-
79
- # Fallback: OAuth session token (short-lived, ~1h) for workspaces where PATs
80
- # are disabled. The pre-push hook will re-mint on every push; reruns that
81
- # fire >1h after the last push will fail – prefer PAT if you can.
82
- if [ -z "$TOKEN_VALUE" ]; then
83
- echo "PAT creation failed – workspace may have PATs disabled. Falling back to OAuth token..."
84
- TOKEN_VALUE="$(databricks auth token $PROFILE -o json 2>/dev/null | jq -r '.access_token // empty')" || true
85
- if [ -n "$TOKEN_VALUE" ]; then
86
- TOKEN_KIND="OAuth session (~1h)"
87
- fi
88
- fi
89
-
90
- if [ -z "$TOKEN_VALUE" ]; then
91
- echo "Failed to mint PAT or get OAuth token. Run 'databricks auth login' first."
92
- exit 1
93
- fi
94
-
95
- export DATABRICKS_HOST
96
- export DATABRICKS_TOKEN="$TOKEN_VALUE"
97
- export LAKEBASE_PROJECT_ID
98
- echo "Token: $TOKEN_KIND. Syncing DATABRICKS_HOST, DATABRICKS_TOKEN, LAKEBASE_PROJECT_ID to GitHub repo secrets..."
99
- exec "$SCRIPT_DIR/set-repo-secrets.sh"
22
+ exec ./scripts/lk lakebase-sync-ci-secrets "$@"