|
11 | 11 | * |
12 | 12 | * Usage: |
13 | 13 | * DATABASE_URL=... bun run apps/sim/scripts/backfill-table-workflow-deployments.ts |
| 14 | + * AWS_PROFILE=sim-admin bun --no-env-file apps/sim/scripts/backfill-table-workflow-deployments.ts --environment=staging |
14 | 15 | */ |
15 | 16 |
|
16 | | -import { db } from '@sim/db' |
17 | 17 | import { createLogger } from '@sim/logger' |
| 18 | +import { loadRuntimeSecrets } from '@sim/runtime-secrets' |
18 | 19 | import { getErrorMessage } from '@sim/utils/errors' |
19 | 20 | import { sql } from 'drizzle-orm' |
20 | | -import { |
21 | | - type PerformFullDeployResult, |
22 | | - performFullDeploy, |
23 | | -} from '@/lib/workflows/orchestration/deploy' |
| 21 | +import type { PerformFullDeployResult } from '@/lib/workflows/orchestration/deploy' |
24 | 22 |
|
25 | 23 | const logger = createLogger('BackfillTableWorkflowDeployments') |
26 | 24 |
|
27 | 25 | export const TABLE_WORKFLOW_DEPLOYMENT_BATCH_SIZE = 25 |
28 | 26 | const BACKFILL_ACTOR_ID = 'table-workflow-deployment-backfill' |
29 | 27 | const BACKFILL_OPERATION_VERSION = 'v2' |
| 28 | +const STAGING_RUNTIME_SECRET_ID = '/staging/sim/env-vars' |
| 29 | + |
| 30 | +interface TableWorkflowDeploymentBackfillCliOptions { |
| 31 | + environment?: 'staging' |
| 32 | +} |
30 | 33 |
|
31 | 34 | export interface TableWorkflowDeploymentCandidate { |
32 | 35 | workflowId: string |
@@ -89,8 +92,69 @@ interface DeploymentStateRow extends Record<string, unknown> { |
89 | 92 | is_deployed: boolean |
90 | 93 | } |
91 | 94 |
|
| 95 | +/** Parses the deliberately small CLI surface for the backfill. */ |
| 96 | +export function parseTableWorkflowDeploymentBackfillArgs( |
| 97 | + args: readonly string[] |
| 98 | +): TableWorkflowDeploymentBackfillCliOptions { |
| 99 | + let environment: TableWorkflowDeploymentBackfillCliOptions['environment'] |
| 100 | + |
| 101 | + for (const arg of args) { |
| 102 | + if (!arg.startsWith('--environment=')) { |
| 103 | + throw new Error(`Unknown argument: ${arg}`) |
| 104 | + } |
| 105 | + if (environment) { |
| 106 | + throw new Error('The --environment argument can only be provided once') |
| 107 | + } |
| 108 | + |
| 109 | + const requestedEnvironment = arg.slice('--environment='.length) |
| 110 | + if (requestedEnvironment !== 'staging') { |
| 111 | + throw new Error(`Unsupported backfill environment: ${requestedEnvironment || '(empty)'}`) |
| 112 | + } |
| 113 | + environment = requestedEnvironment |
| 114 | + } |
| 115 | + |
| 116 | + return { environment } |
| 117 | +} |
| 118 | + |
| 119 | +/** Loads staging configuration before modules that read database settings are imported. */ |
| 120 | +export async function prepareTableWorkflowDeploymentBackfillEnvironment( |
| 121 | + args: readonly string[] |
| 122 | +): Promise<void> { |
| 123 | + const { environment } = parseTableWorkflowDeploymentBackfillArgs(args) |
| 124 | + if (!environment) return |
| 125 | + |
| 126 | + const configuredSecretId = process.env.SIM_ENV_SECRET_ID |
| 127 | + if (configuredSecretId && configuredSecretId !== STAGING_RUNTIME_SECRET_ID) { |
| 128 | + throw new Error( |
| 129 | + `SIM_ENV_SECRET_ID is already set to ${configuredSecretId}; expected ${STAGING_RUNTIME_SECRET_ID}` |
| 130 | + ) |
| 131 | + } |
| 132 | + |
| 133 | + const configuredDatabaseVariables = ['DATABASE_URL', 'DATABASE_URL_WEB'].filter( |
| 134 | + (key) => key in process.env |
| 135 | + ) |
| 136 | + if (configuredDatabaseVariables.length > 0) { |
| 137 | + throw new Error( |
| 138 | + `Unset ${configuredDatabaseVariables.join(', ')} before using --environment=staging so local configuration cannot override staging` |
| 139 | + ) |
| 140 | + } |
| 141 | + |
| 142 | + process.env.SIM_ENV_SECRET_ID = STAGING_RUNTIME_SECRET_ID |
| 143 | + await loadRuntimeSecrets() |
| 144 | + |
| 145 | + if (!process.env.DATABASE_URL && !process.env.DATABASE_URL_WEB) { |
| 146 | + throw new Error(`${STAGING_RUNTIME_SECRET_ID} did not provide a database URL`) |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +async function getDatabase() { |
| 151 | + const { db } = await import('@sim/db') |
| 152 | + return db |
| 153 | +} |
| 154 | + |
92 | 155 | /** Ensures the table group references can be traversed without silently dropping corrupt data. */ |
93 | 156 | async function assertTableWorkflowIntegrity(): Promise<void> { |
| 157 | + const db = await getDatabase() |
94 | 158 | const [invalidSchema] = await db.execute<InvalidTableSchemaRow>(sql` |
95 | 159 | SELECT id AS table_id |
96 | 160 | FROM user_table_definitions |
@@ -200,6 +264,7 @@ export const postgresTableWorkflowDeploymentStore: TableWorkflowDeploymentStore |
200 | 264 | assertIntegrity: assertTableWorkflowIntegrity, |
201 | 265 |
|
202 | 266 | async listCandidates(afterWorkflowId, limit) { |
| 267 | + const db = await getDatabase() |
203 | 268 | const rows = await db.execute<CandidateRow>(sql` |
204 | 269 | WITH referenced_workflow_ids AS ( |
205 | 270 | SELECT DISTINCT workflow_group.value->>'workflowId' AS workflow_id |
@@ -238,6 +303,7 @@ export const postgresTableWorkflowDeploymentStore: TableWorkflowDeploymentStore |
238 | 303 | }, |
239 | 304 |
|
240 | 305 | async isDeployed(workflowId) { |
| 306 | + const db = await getDatabase() |
241 | 307 | const [state] = await db.execute<DeploymentStateRow>(sql` |
242 | 308 | SELECT |
243 | 309 | workflow.is_deployed, |
@@ -265,6 +331,7 @@ export const postgresTableWorkflowDeploymentStore: TableWorkflowDeploymentStore |
265 | 331 | export async function deployTableWorkflow( |
266 | 332 | candidate: TableWorkflowDeploymentCandidate |
267 | 333 | ): Promise<PerformFullDeployResult> { |
| 334 | + const { performFullDeploy } = await import('@/lib/workflows/orchestration/deploy') |
268 | 335 | return performFullDeploy({ |
269 | 336 | workflowId: candidate.workflowId, |
270 | 337 | userId: candidate.userId, |
@@ -365,8 +432,13 @@ export async function runTableWorkflowDeploymentBackfill(): Promise<void> { |
365 | 432 | logger.info('Table workflow deployment backfill completed', summary) |
366 | 433 | } |
367 | 434 |
|
| 435 | +async function main(): Promise<void> { |
| 436 | + await prepareTableWorkflowDeploymentBackfillEnvironment(process.argv.slice(2)) |
| 437 | + await runTableWorkflowDeploymentBackfill() |
| 438 | +} |
| 439 | + |
368 | 440 | if ((import.meta as { main?: boolean }).main) { |
369 | | - runTableWorkflowDeploymentBackfill() |
| 441 | + main() |
370 | 442 | .then(() => process.exit(0)) |
371 | 443 | .catch((error: unknown) => { |
372 | 444 | logger.error('Table workflow deployment backfill failed', { |
|
0 commit comments