fresh auth token for each deploy

This commit is contained in:
nicktrn
2025-06-30 17:25:04 +01:00
parent e16c43ca9f
commit eb99545729
3 changed files with 106 additions and 9 deletions
@@ -5,6 +5,7 @@ import {
type Repository,
type Tag,
RepositoryNotFoundException,
GetAuthorizationTokenCommand,
} from "@aws-sdk/client-ecr";
import { tryCatch } from "@trigger.dev/core";
import { logger } from "~/services/logger.server";
@@ -58,7 +59,7 @@ export async function getDeploymentImageRef({
};
}
function isEcrRegistry(registryHost: string) {
export function isEcrRegistry(registryHost: string) {
return registryHost.includes("amazonaws.com");
}
@@ -196,3 +197,39 @@ async function ensureEcrRepositoryExists({
return newRepo;
}
export async function getEcrAuthToken({
registryHost,
registryId,
}: {
registryHost: string;
registryId?: string;
}): Promise<{ username: string; password: string }> {
const region = getEcrRegion(registryHost);
if (!region) {
logger.error("Invalid ECR registry host", { registryHost });
throw new Error("Invalid ECR registry host");
}
const ecr = new ECRClient({ region });
const response = await ecr.send(
new GetAuthorizationTokenCommand({
registryIds: registryId ? [registryId] : undefined,
})
);
if (!response.authorizationData) {
throw new Error("Failed to get ECR authorization token");
}
const authData = response.authorizationData[0];
if (!authData.authorizationToken) {
throw new Error("No authorization token returned from ECR");
}
const authToken = Buffer.from(authData.authorizationToken, "base64").toString();
const [username, password] = authToken.split(":");
return { username, password };
}
@@ -9,6 +9,8 @@ import { env } from "~/env.server";
import { depot as execDepot } from "@depot/cli";
import { FinalizeDeploymentService } from "./finalizeDeployment.server";
import { remoteBuildsEnabled } from "../remoteImageBuilder.server";
import { getEcrAuthToken, isEcrRegistry } from "../getDeploymentImageRef.server";
import { tryCatch } from "@trigger.dev/core";
export class FinalizeDeploymentV2Service extends BaseService {
public async call(
@@ -172,11 +174,27 @@ async function executePushToRegistry(
{ depot, registry, deployment }: ExecutePushToRegistryOptions,
writer?: WritableStreamDefaultWriter
): Promise<ExecutePushResult> {
// Step 1: We need to "login" to the digital ocean registry
const configDir = await ensureLoggedIntoDockerRegistry(registry.host, {
username: registry.username,
password: registry.password,
});
// Step 1: We need to "login" to the registry
const [loginError, configDir] = await tryCatch(
ensureLoggedIntoDockerRegistry(registry.host, {
username: registry.username,
password: registry.password,
})
);
if (loginError) {
logger.error("Failed to login to registry", {
deployment,
registryHost: registry.host,
error: loginError.message,
});
return {
ok: false as const,
error: "Failed to login to registry",
logs: "",
};
}
const imageTag = deployment.imageReference;
@@ -244,12 +262,18 @@ async function executePushToRegistry(
async function ensureLoggedIntoDockerRegistry(
registryHost: string,
auth: { username: string; password: string }
auth: { username: string; password: string } | undefined = undefined
) {
const tmpDir = await createTempDir();
// Read the current docker config
const dockerConfigPath = join(tmpDir, "config.json");
// If this is an ECR registry, get fresh credentials
if (isEcrRegistry(registryHost)) {
auth = await getEcrAuthToken({ registryHost, registryId: env.DEPLOY_REGISTRY_ID });
} else if (!auth) {
throw new Error("Authentication required for non-ECR registry");
}
await writeJSONFile(dockerConfigPath, {
auths: {
[registryHost]: {
+37 -1
View File
@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { getDeploymentImageRef, getEcrRegion } from "../app/v3/getDeploymentImageRef.server";
import {
getDeploymentImageRef,
getEcrAuthToken,
getEcrRegion,
} from "../app/v3/getDeploymentImageRef.server";
import { ECRClient, DeleteRepositoryCommand } from "@aws-sdk/client-ecr";
describe.skipIf(process.env.RUN_REGISTRY_TESTS !== "1")("getDeploymentImageRef", () => {
@@ -101,3 +105,35 @@ describe.skipIf(process.env.RUN_REGISTRY_TESTS !== "1")("getDeploymentImageRef",
).rejects.toThrow("Invalid ECR registry host: invalid.ecr.amazonaws.com");
});
});
describe.skipIf(process.env.RUN_REGISTRY_AUTH_TESTS !== "1")("getEcrAuthToken", () => {
const registryId = process.env.DEPLOY_REGISTRY_ID;
const testHost = "123456789012.dkr.ecr.us-east-1.amazonaws.com";
it("should return valid ECR credentials", async () => {
const auth = await getEcrAuthToken({
registryHost: testHost,
registryId,
});
// Check the structure and basic validation of the returned credentials
expect(auth).toHaveProperty("username");
expect(auth).toHaveProperty("password");
expect(auth.username).toBe("AWS");
expect(typeof auth.password).toBe("string");
expect(auth.password.length).toBeGreaterThan(0);
// Verify the token format (should be a base64-encoded string)
const base64Regex = /^[A-Za-z0-9+/=]+$/;
expect(base64Regex.test(auth.password)).toBe(true);
});
it("should throw error for invalid region", async () => {
await expect(
getEcrAuthToken({
registryHost: "invalid.ecr.amazonaws.com",
registryId,
})
).rejects.toThrow();
});
});