|
1 | | -import { execFile, spawn } from "child_process"; |
| 1 | +import { execFileSync, spawn } from "child_process"; |
2 | 2 | import { randomUUID } from "crypto"; |
3 | 3 | import { existsSync, statSync } from "fs"; |
4 | 4 | import path from "path"; |
@@ -61,22 +61,15 @@ export const runDockerCmd = ( |
61 | 61 | }; |
62 | 62 | }; |
63 | 63 |
|
64 | | -export const runDockerCmdWithOutput = async ( |
65 | | - dockerPath: string, |
66 | | - args: string[] |
67 | | -): Promise<string> => { |
68 | | - return new Promise((resolve, reject) => { |
69 | | - execFile(dockerPath, args, (error, stdout) => { |
70 | | - if (error) { |
71 | | - return reject( |
72 | | - new Error( |
73 | | - `Failed running docker command: ${error.message}. Command: ${dockerPath} ${args.join(" ")}` |
74 | | - ) |
75 | | - ); |
76 | | - } |
77 | | - return resolve(stdout.trim()); |
78 | | - }); |
79 | | - }); |
| 64 | +export const runDockerCmdWithOutput = (dockerPath: string, args: string[]) => { |
| 65 | + try { |
| 66 | + const stdout = execFileSync(dockerPath, args, { encoding: "utf8" }); |
| 67 | + return stdout.trim(); |
| 68 | + } catch (error) { |
| 69 | + throw new Error( |
| 70 | + `Failed running docker command: ${(error as Error).message}. Command: ${dockerPath} ${args.join(" ")}` |
| 71 | + ); |
| 72 | + } |
80 | 73 | }; |
81 | 74 |
|
82 | 75 | /** throws when docker is not installed */ |
@@ -209,7 +202,7 @@ export const getContainerIdsFromImage = async ( |
209 | 202 | dockerPath: string, |
210 | 203 | ancestorImage: string |
211 | 204 | ) => { |
212 | | - const output = await runDockerCmdWithOutput(dockerPath, [ |
| 205 | + const output = runDockerCmdWithOutput(dockerPath, [ |
213 | 206 | "ps", |
214 | 207 | "-a", |
215 | 208 | "--filter", |
@@ -250,3 +243,85 @@ export async function checkExposedPorts( |
250 | 243 | export function generateContainerBuildId() { |
251 | 244 | return randomUUID().slice(0, 8); |
252 | 245 | } |
| 246 | + |
| 247 | +/** |
| 248 | + * Output of docker context ls --format json |
| 249 | + */ |
| 250 | +type DockerContext = { |
| 251 | + Current: boolean; |
| 252 | + Description: string; |
| 253 | + DockerEndpoint: string; |
| 254 | + Error: string; |
| 255 | + Name: string; |
| 256 | +}; |
| 257 | + |
| 258 | +/** |
| 259 | + * Run `docker context ls` to get the socket from the currently active Docker context |
| 260 | + * @returns The socket path or null if we are not able to determine it |
| 261 | + */ |
| 262 | +export function getDockerSocketFromContext(dockerPath: string): string | null { |
| 263 | + try { |
| 264 | + const output = runDockerCmdWithOutput(dockerPath, [ |
| 265 | + "context", |
| 266 | + "ls", |
| 267 | + "--format", |
| 268 | + "json", |
| 269 | + ]); |
| 270 | + |
| 271 | + // Parse each line as a separate JSON object |
| 272 | + const lines = output.trim().split("\n"); |
| 273 | + const contexts: DockerContext[] = lines.map((line) => JSON.parse(line)); |
| 274 | + |
| 275 | + // Find the current context |
| 276 | + const currentContext = contexts.find((context) => context.Current === true); |
| 277 | + |
| 278 | + if (currentContext && currentContext.DockerEndpoint) { |
| 279 | + return currentContext.DockerEndpoint; |
| 280 | + } |
| 281 | + } catch { |
| 282 | + // Fall back to null if docker context inspection fails so that we can use platform defaults |
| 283 | + } |
| 284 | + return null; |
| 285 | +} |
| 286 | +/** |
| 287 | + * Resolve Docker host as follows: |
| 288 | + * 1. Check WRANGLER_DOCKER_HOST environment variable |
| 289 | + * 2. Check DOCKER_HOST environment variable |
| 290 | + * 3. Try to get socket from active Docker context |
| 291 | + * 4. Fall back to platform-specific defaults |
| 292 | + */ |
| 293 | +export function resolveDockerHost(dockerPath: string): string { |
| 294 | + if (process.env.WRANGLER_DOCKER_HOST) { |
| 295 | + return process.env.WRANGLER_DOCKER_HOST; |
| 296 | + } |
| 297 | + |
| 298 | + if (process.env.DOCKER_HOST) { |
| 299 | + return process.env.DOCKER_HOST; |
| 300 | + } |
| 301 | + |
| 302 | + // 3. Try to get socket from by running `docker context ls` |
| 303 | + |
| 304 | + const contextSocket = getDockerSocketFromContext(dockerPath); |
| 305 | + if (contextSocket) { |
| 306 | + return contextSocket; |
| 307 | + } |
| 308 | + |
| 309 | + // 4. Fall back to platform-specific defaults |
| 310 | + // (note windows doesn't work yet due to a runtime limitation) |
| 311 | + return process.platform === "win32" |
| 312 | + ? "//./pipe/docker_engine" |
| 313 | + : "unix:///var/run/docker.sock"; |
| 314 | +} |
| 315 | + |
| 316 | +/** |
| 317 | + * |
| 318 | + * Get docker host from environment variables or platform defaults. |
| 319 | + * Does not use the docker context ls command, so we |
| 320 | + */ |
| 321 | +export const getDockerHostFromEnv = (): string => { |
| 322 | + const fromEnv = process.env.WRANGLER_DOCKER_HOST ?? process.env.DOCKER_HOST; |
| 323 | + |
| 324 | + return fromEnv ?? process.platform === "win32" |
| 325 | + ? "//./pipe/docker_engine" |
| 326 | + : "unix:///var/run/docker.sock"; |
| 327 | +}; |
0 commit comments