@databricks-solutions/lakebase-scm-utils 0.2.48 → 0.2.50
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.
- package/dist/ci-secrets-CnGrdQV-.d.cts +84 -0
- package/dist/ci-secrets-CnGrdQV-.d.ts +84 -0
- package/dist/scripts/index.cjs +2184 -2054
- package/dist/scripts/index.d.cts +3 -2
- package/dist/scripts/index.d.ts +3 -2
- package/dist/scripts/index.js +259 -136
- package/dist/scripts/lakebase/ci-resolve-branch.cli.cjs +2 -2
- package/dist/scripts/lakebase/ci-resolve-branch.cli.js +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.cjs +2 -2
- package/dist/scripts/lakebase/collapse-heads.cli.js +2 -2
- package/dist/scripts/lakebase/detect-language.cli.cjs +2 -2
- package/dist/scripts/lakebase/detect-language.cli.js +2 -2
- package/dist/scripts/lakebase/doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/doctor.cli.js +2 -2
- package/dist/scripts/lakebase/get-connection.cli.cjs +2 -2
- package/dist/scripts/lakebase/get-connection.cli.js +2 -2
- package/dist/scripts/lakebase/index.cjs +2170 -2052
- package/dist/scripts/lakebase/index.d.cts +21 -2
- package/dist/scripts/lakebase/index.d.ts +21 -2
- package/dist/scripts/lakebase/index.js +249 -132
- package/dist/scripts/lakebase/infra-runner.cli.cjs +2 -2
- package/dist/scripts/lakebase/infra-runner.cli.js +2 -2
- package/dist/scripts/lakebase/new-migration.cli.cjs +2 -2
- package/dist/scripts/lakebase/new-migration.cli.js +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-diff.cli.js +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.cjs +2 -2
- package/dist/scripts/lakebase/schema-migrate.cli.js +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-claim-feature.cli.js +2 -2
- package/dist/scripts/lakebase/scm-cleanup.cli.cjs +5 -2
- package/dist/scripts/lakebase/scm-cleanup.cli.js +5 -2
- package/dist/scripts/lakebase/scm-doctor.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-doctor.cli.js +2 -2
- package/dist/scripts/lakebase/scm-merge.cli.cjs +2216 -1994
- package/dist/scripts/lakebase/scm-merge.cli.js +296 -74
- package/dist/scripts/lakebase/scm-prepare-pr.cli.cjs +479 -86
- package/dist/scripts/lakebase/scm-prepare-pr.cli.js +479 -86
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.cjs +2 -2
- package/dist/scripts/lakebase/scm-reconcile-tier.cli.js +2 -2
- package/dist/scripts/lakebase/sync-ci-secrets.cli.cjs +56 -30
- package/dist/scripts/lakebase/sync-ci-secrets.cli.js +56 -30
- package/dist/scripts/util/index.cjs +142 -25
- package/dist/scripts/util/index.d.cts +2 -27
- package/dist/scripts/util/index.d.ts +2 -27
- package/dist/scripts/util/index.js +136 -25
- package/package.json +1 -1
- package/scripts/lakebase/create-project.ts +2 -2
- package/scripts/lakebase/lakebase-project.ts +17 -3
- package/scripts/lakebase/scm-merge.ts +21 -0
- package/scripts/lakebase/scm-prepare-pr.ts +10 -1
- package/scripts/lakebase/sync-ci-secrets.cli.ts +2 -2
- package/scripts/util/ci-secrets.ts +212 -26
- package/templates/project/common/scripts/create-token-and-sync-secrets.sh +9 -86
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
// to the GitHub repo
|
|
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
|
|
5
|
-
//
|
|
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
|
|
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
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Mint
|
|
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
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
|
|
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 "$@"
|