Skip to content

Commit c46456d

Browse files
committed
Wait for initialized runtime databases
1 parent 863a654 commit c46456d

2 files changed

Lines changed: 24 additions & 0 deletions

File tree

packages/cli/src/runtime-services.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ async function provisionMysqlDockerService(service: WorkspaceRecipeRuntimeServic
172172
const { stdout } = await dependencies.execute("docker", ["port", container, "3306/tcp"], { signal, timeout: 10_000 })
173173
const port = parseLoopbackPort(stdout)
174174
await dependencies.waitForReady("127.0.0.1", port, 30_000, signal)
175+
await waitForMysqlDatabase(container, engine, password, dependencies, 30_000, signal)
175176
throwIfAborted(signal)
176177
evidence.readiness = "ready"
177178
evidence.lifecycle = "provisioned"
@@ -186,6 +187,23 @@ async function provisionMysqlDockerService(service: WorkspaceRecipeRuntimeServic
186187
}
187188
}
188189

190+
async function waitForMysqlDatabase(container: string, engine: keyof typeof MYSQL_IMAGES, password: string, dependencies: RuntimeServiceDependencies, timeoutMs: number, signal?: AbortSignal): Promise<void> {
191+
const deadline = Date.now() + timeoutMs
192+
const client = engine === "mariadb" ? "mariadb" : "mysql"
193+
const args = ["exec", "--env", "MYSQL_PWD", container, client, "--protocol=TCP", "--host=127.0.0.1", "--user=runtime", "--database=runtime", "--execute=SELECT 1"]
194+
while (Date.now() < deadline) {
195+
throwIfAborted(signal)
196+
try {
197+
await dependencies.execute("docker", args, { env: { ...process.env, MYSQL_PWD: password }, signal, timeout: 5_000 })
198+
return
199+
} catch (error) {
200+
if (signal?.aborted) throw error
201+
await abortableDelay(100, signal)
202+
}
203+
}
204+
throw new Error(`MySQL database readiness timed out after ${timeoutMs}ms`)
205+
}
206+
189207
async function ensureDockerImage(image: string, dependencies: RuntimeServiceDependencies, signal?: AbortSignal): Promise<void> {
190208
try {
191209
await dependencies.execute("docker", ["image", "inspect", image], { signal, timeout: 10_000 })

tests/runtime-services.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ assert.ok(runCall?.args.includes("127.0.0.1::3306"), "Docker publishes MySQL on
109109
assert.deepEqual(runCall?.args.slice(runCall.args.indexOf("--tmpfs"), runCall.args.indexOf("--tmpfs") + 2), ["--tmpfs", "/var/lib/mysql"])
110110
assert.equal(runCall?.args.includes("--volume") || runCall?.args.includes("--mount"), false, "Docker uses no persistent volume")
111111
assert.equal(runCall?.args.some((arg) => arg.includes(provisioned.env.DB_PASSWORD)), false, "credentials never enter Docker argv")
112+
const readinessCall = calls.find((call) => call.args[0] === "exec")
113+
assert.ok(readinessCall?.args.includes("mysql"), "MySQL readiness authenticates against the initialized database")
114+
assert.equal(readinessCall?.args.some((arg) => arg.includes(provisioned.env.DB_PASSWORD)), false, "readiness credentials never enter Docker argv")
115+
assert.equal(readinessCall?.env?.MYSQL_PWD, provisioned.env.DB_PASSWORD, "readiness credentials use the child environment")
112116
assert.equal(JSON.stringify(provisioned.evidence).includes(provisioned.env.DB_PASSWORD), false, "credentials never enter evidence")
113117
assert.equal(runCall?.env?.DOCKER_HOST, process.env.DOCKER_HOST, "Docker provider context is preserved")
114118
assert.equal(calls[0]?.args[0], "image", "the provider checks the image before starting the service")
@@ -155,7 +159,9 @@ const mariaDbDependencies: RuntimeServiceDependencies = {
155159
}
156160
const mariaDb = await provisionRuntimeServices([mariaDbService], { dependencies: mariaDbDependencies })
157161
const mariaDbRun = mariaDbCalls.find((call) => call.args[0] === "run")
162+
const mariaDbReadiness = mariaDbCalls.find((call) => call.args[0] === "exec")
158163
assert.ok(mariaDbRun?.args.includes("mariadb:11.4"))
164+
assert.ok(mariaDbReadiness?.args.includes("mariadb"), "MariaDB readiness uses the image client")
159165
assert.ok(mariaDbRun?.args.includes("MARIADB_ALLOW_EMPTY_ROOT_PASSWORD"))
160166
assert.equal(mariaDbRun?.env?.MARIADB_ALLOW_EMPTY_ROOT_PASSWORD, "yes")
161167
assert.equal(mariaDbRun?.env?.MYSQL_ALLOW_EMPTY_PASSWORD, undefined)

0 commit comments

Comments
 (0)