Skip to content

Commit 1228281

Browse files
committed
feat(cli): add TRIGGER_REGISTRY_AUTH=file registry auth mode
When TRIGGER_REGISTRY_AUTH=file is set, deploys write the registry auth entry into the docker config file directly instead of running docker login, and remove it where docker logout ran. Intended for the managed build server, where the config is job-scoped and helper-less; machines with a credential helper configured must keep the default login mode, since docker ignores inline auth entries when credsStore is set.
1 parent 83af2d2 commit 1228281

2 files changed

Lines changed: 88 additions & 25 deletions

File tree

‎.changeset/thick-otters-swim.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Adds TRIGGER_REGISTRY_AUTH=file: deploys authenticate to the registry by writing the docker config auth entry directly instead of running docker login.

‎packages/cli-v3/src/deploy/buildImage.ts‎

Lines changed: 83 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import type { BuildManifest, BuildRuntime } from "@trigger.dev/core/v3/schemas";
55
import { networkInterfaces } from "os";
66
import { join } from "path";
77
import { safeReadJSONFile } from "../utilities/fileSystem.js";
8+
import { mkdir, writeFile } from "node:fs/promises";
9+
import { homedir } from "os";
10+
import { dirname } from "path";
811
import { readFileSync } from "fs";
912

1013
import { isLinux } from "std-env";
@@ -474,6 +477,12 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
474477

475478
const errors: string[] = [];
476479

480+
const writeRegistryAuthFile = process.env.TRIGGER_REGISTRY_AUTH === "file";
481+
const dockerConfigPath = join(
482+
process.env.DOCKER_CONFIG ?? join(homedir(), ".docker"),
483+
"config.json"
484+
);
485+
477486
let cloudRegistryHost: string | undefined;
478487
if (push && options.authenticateToRegistry) {
479488
cloudRegistryHost =
@@ -499,32 +508,48 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
499508
};
500509
}
501510

502-
logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);
511+
if (writeRegistryAuthFile) {
512+
logger.debug(`Writing registry auth for ${cloudRegistryHost} to ${dockerConfigPath}`);
513+
514+
const [writeError] = await tryCatch(
515+
upsertRegistryAuth(dockerConfigPath, cloudRegistryHost, credentials)
516+
);
503517

504-
const loginProcess = x(
505-
"docker",
506-
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
507-
{
508-
nodeOptions: {
509-
cwd: options.cwd,
510-
},
518+
if (writeError) {
519+
return {
520+
ok: false as const,
521+
error: `Failed to write registry auth for ${cloudRegistryHost}: ${writeError.message}`,
522+
logs: "",
523+
};
511524
}
512-
);
525+
} else {
526+
logger.debug(`Logging in to docker registry: ${cloudRegistryHost}`);
527+
528+
const loginProcess = x(
529+
"docker",
530+
["login", "--username", credentials.username, "--password-stdin", cloudRegistryHost],
531+
{
532+
nodeOptions: {
533+
cwd: options.cwd,
534+
},
535+
}
536+
);
513537

514-
loginProcess.process?.stdin?.write(credentials.password);
515-
loginProcess.process?.stdin?.end();
538+
loginProcess.process?.stdin?.write(credentials.password);
539+
loginProcess.process?.stdin?.end();
516540

517-
for await (const line of loginProcess) {
518-
errors.push(line);
519-
logger.debug(line);
520-
}
541+
for await (const line of loginProcess) {
542+
errors.push(line);
543+
logger.debug(line);
544+
}
521545

522-
if (loginProcess.exitCode !== 0) {
523-
return {
524-
ok: false as const,
525-
error: `Failed to login to registry: ${cloudRegistryHost}`,
526-
logs: extractLogs(errors),
527-
};
546+
if (loginProcess.exitCode !== 0) {
547+
return {
548+
ok: false as const,
549+
error: `Failed to login to registry: ${cloudRegistryHost}`,
550+
logs: extractLogs(errors),
551+
};
552+
}
528553
}
529554

530555
options.onLog?.(`Successfully logged in to the remote registry`);
@@ -610,8 +635,7 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
610635

611636
if (buildProcess.exitCode !== 0) {
612637
if (cloudRegistryHost) {
613-
logger.debug(`Logging out from docker registry: ${cloudRegistryHost}`);
614-
await x("docker", ["logout", cloudRegistryHost]);
638+
await clearRegistryAuth(cloudRegistryHost, writeRegistryAuthFile, dockerConfigPath);
615639
}
616640

617641
return {
@@ -660,8 +684,7 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
660684
}
661685

662686
if (cloudRegistryHost) {
663-
logger.debug(`Logging out from docker registry: ${cloudRegistryHost}`);
664-
await x("docker", ["logout", cloudRegistryHost]);
687+
await clearRegistryAuth(cloudRegistryHost, writeRegistryAuthFile, dockerConfigPath);
665688
}
666689

667690
return {
@@ -672,6 +695,41 @@ async function localBuildImage(options: SelfHostedBuildImageOptions): Promise<Bu
672695
};
673696
}
674697

698+
async function upsertRegistryAuth(
699+
configPath: string,
700+
host: string,
701+
credentials: { username: string; password: string }
702+
) {
703+
const config = ((await safeReadJSONFile(configPath)) ?? {}) as {
704+
auths?: Record<string, { auth?: string }>;
705+
};
706+
config.auths = {
707+
...config.auths,
708+
[host]: {
709+
auth: Buffer.from(`${credentials.username}:${credentials.password}`).toString("base64"),
710+
},
711+
};
712+
await mkdir(dirname(configPath), { recursive: true });
713+
await writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
714+
}
715+
716+
async function clearRegistryAuth(host: string, viaAuthFile: boolean, configPath: string) {
717+
if (!viaAuthFile) {
718+
logger.debug(`Logging out from docker registry: ${host}`);
719+
await x("docker", ["logout", host]);
720+
return;
721+
}
722+
723+
const config = (await safeReadJSONFile(configPath)) as
724+
| { auths?: Record<string, unknown> }
725+
| undefined;
726+
if (!config?.auths?.[host]) {
727+
return;
728+
}
729+
delete config.auths[host];
730+
await writeFile(configPath, JSON.stringify(config), { mode: 0o600 });
731+
}
732+
675733
function extractLogs(outputs: string[]) {
676734
// Remove empty lines
677735
const cleanedOutputs = outputs.map((line) => line.trim()).filter((line) => line !== "");

0 commit comments

Comments
 (0)