Skip to content

Commit 07a3988

Browse files
committed
supervisor: docker api version lock, auth, multi-platform
1 parent c51c7f1 commit 07a3988

File tree

3 files changed

+97
-24
lines changed

3 files changed

+97
-24
lines changed

apps/supervisor/src/env.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ const Env = z.object({
4141
*/
4242
RUNNER_DOCKER_NETWORKS: z.string().default("host"),
4343

44+
// Docker settings
45+
DOCKER_API_VERSION: z.string().default("v1.41"),
46+
DOCKER_PLATFORM: z.string().optional(), // e.g. linux/amd64, linux/arm64
47+
DOCKER_REGISTRY_USERNAME: z.string().optional(),
48+
DOCKER_REGISTRY_PASSWORD: z.string().optional(),
49+
DOCKER_REGISTRY_URL: z.string().optional(), // e.g. https://index.docker.io/v1
50+
4451
// Dequeue settings (provider mode)
4552
TRIGGER_DEQUEUE_ENABLED: BoolEnv.default("true"),
4653
TRIGGER_DEQUEUE_INTERVAL_MS: z.coerce.number().int().default(250),

apps/supervisor/src/util.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
export function getDockerHostDomain() {
2-
const isMacOs = process.platform === "darwin";
3-
const isWindows = process.platform === "win32";
1+
import { isMacOS, isWindows } from "std-env";
42

5-
return isMacOs || isWindows ? "host.docker.internal" : "localhost";
3+
export function getDockerHostDomain() {
4+
return isMacOS || isWindows ? "host.docker.internal" : "localhost";
65
}
76

87
export function getRunnerId(runId: string, attemptNumber?: number) {

apps/supervisor/src/workloadManager/docker.ts

Lines changed: 87 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ export class DockerWorkloadManager implements WorkloadManager {
1414
private readonly docker: Docker;
1515

1616
private readonly runnerNetworks: string[];
17+
private readonly auth?: Docker.AuthConfig;
18+
private readonly platform?: string;
1719

1820
constructor(private opts: WorkloadManagerOptions) {
19-
this.docker = new Docker();
21+
this.docker = new Docker({
22+
version: env.DOCKER_API_VERSION,
23+
});
2024

2125
if (opts.workloadApiDomain) {
2226
this.logger.warn("⚠️ Custom workload API domain", {
@@ -25,6 +29,29 @@ export class DockerWorkloadManager implements WorkloadManager {
2529
}
2630

2731
this.runnerNetworks = env.RUNNER_DOCKER_NETWORKS.split(",");
32+
33+
this.platform = env.DOCKER_PLATFORM;
34+
if (this.platform) {
35+
this.logger.info("🖥️ Platform override", {
36+
targetPlatform: this.platform,
37+
hostPlatform: process.arch,
38+
});
39+
}
40+
41+
if (env.DOCKER_REGISTRY_USERNAME && env.DOCKER_REGISTRY_PASSWORD && env.DOCKER_REGISTRY_URL) {
42+
this.logger.info("🐋 Using Docker registry credentials", {
43+
username: env.DOCKER_REGISTRY_USERNAME,
44+
url: env.DOCKER_REGISTRY_URL,
45+
});
46+
47+
this.auth = {
48+
username: env.DOCKER_REGISTRY_USERNAME,
49+
password: env.DOCKER_REGISTRY_PASSWORD,
50+
serveraddress: env.DOCKER_REGISTRY_URL,
51+
};
52+
} else {
53+
this.logger.warn("🐋 No Docker registry credentials provided, skipping auth");
54+
}
2855
}
2956

3057
async create(opts: WorkloadManagerCreateOptions) {
@@ -91,7 +118,6 @@ export class DockerWorkloadManager implements WorkloadManager {
91118
}
92119

93120
const containerCreateOpts: Docker.ContainerCreateOptions = {
94-
Env: envVars,
95121
name: runnerId,
96122
Hostname: runnerId,
97123
HostConfig: hostConfig,
@@ -101,30 +127,63 @@ export class DockerWorkloadManager implements WorkloadManager {
101127
AttachStdin: false,
102128
};
103129

104-
try {
105-
// Create container
106-
const container = await this.docker.createContainer(containerCreateOpts);
130+
if (this.platform) {
131+
containerCreateOpts.platform = this.platform;
132+
}
133+
134+
const logger = this.logger.child({ opts, containerCreateOpts });
107135

108-
// If there are multiple networks to attach to we need to attach the remaining ones after creation
109-
if (remainingNetworks.length > 0) {
110-
await this.attachContainerToNetworks({
111-
containerId: container.id,
112-
networkNames: remainingNetworks,
113-
});
114-
}
136+
// Ensure the image is present
137+
const [createImageError, imageResponseReader] = await tryCatch(
138+
this.docker.createImage(this.auth, {
139+
fromImage: opts.image,
140+
...(this.platform ? { platform: this.platform } : {}),
141+
})
142+
);
143+
if (createImageError) {
144+
logger.error("Failed to pull image", { error: createImageError });
145+
return;
146+
}
115147

116-
// Start container
117-
const startResult = await container.start();
148+
const [imageReadError, imageResponse] = await tryCatch(readAllChunks(imageResponseReader));
149+
if (imageReadError) {
150+
logger.error("failed to read image response", { error: imageReadError });
151+
return;
152+
}
118153

119-
this.logger.debug("create succeeded", {
120-
opts,
121-
startResult,
154+
logger.debug("pulled image", { image: opts.image, imageResponse });
155+
156+
// Create container
157+
const [createContainerError, container] = await tryCatch(
158+
this.docker.createContainer({
159+
...containerCreateOpts,
160+
// Add env vars here so they're not logged
161+
Env: envVars,
162+
})
163+
);
164+
165+
if (createContainerError) {
166+
logger.error("Failed to create container", { error: createContainerError });
167+
return;
168+
}
169+
170+
// If there are multiple networks to attach to we need to attach the remaining ones after creation
171+
if (remainingNetworks.length > 0) {
172+
await this.attachContainerToNetworks({
122173
containerId: container.id,
123-
containerCreateOpts,
174+
networkNames: remainingNetworks,
124175
});
125-
} catch (error) {
126-
this.logger.error("create failed:", { opts, error, containerCreateOpts });
127176
}
177+
178+
// Start container
179+
const [startError, startResult] = await tryCatch(container.start());
180+
181+
if (startError) {
182+
logger.error("Failed to start container", { error: startError, containerId: container.id });
183+
return;
184+
}
185+
186+
logger.debug("create succeeded", { startResult, containerId: container.id });
128187
}
129188

130189
private async attachContainerToNetworks({
@@ -173,3 +232,11 @@ export class DockerWorkloadManager implements WorkloadManager {
173232
});
174233
}
175234
}
235+
236+
async function readAllChunks(reader: NodeJS.ReadableStream) {
237+
const chunks = [];
238+
for await (const chunk of reader) {
239+
chunks.push(chunk.toString());
240+
}
241+
return chunks;
242+
}

0 commit comments

Comments
 (0)