Skip to content

Commit 4b08597

Browse files
authored
feat: optional v4 deploy registry overrides (#2370)
* feat: optional v4 deploy registry overrides * only require credentials for non-ecr registries
1 parent 9ce4f57 commit 4b08597

File tree

7 files changed

+378
-79
lines changed

7 files changed

+378
-79
lines changed

apps/webapp/app/env.server.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,14 +230,47 @@ const EnvironmentSchema = z.object({
230230
DEPOT_ORG_ID: z.string().optional(),
231231
DEPOT_REGION: z.string().default("us-east-1"),
232232

233-
// Deployment registry
233+
// Deployment registry (v3)
234234
DEPLOY_REGISTRY_HOST: z.string().min(1),
235235
DEPLOY_REGISTRY_USERNAME: z.string().optional(),
236236
DEPLOY_REGISTRY_PASSWORD: z.string().optional(),
237237
DEPLOY_REGISTRY_NAMESPACE: z.string().min(1).default("trigger"),
238238
DEPLOY_REGISTRY_ECR_TAGS: z.string().optional(), // csv, for example: "key1=value1,key2=value2"
239239
DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z.string().optional(),
240240
DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z.string().optional(),
241+
242+
// Deployment registry (v4) - falls back to v3 registry if not specified
243+
V4_DEPLOY_REGISTRY_HOST: z
244+
.string()
245+
.optional()
246+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_HOST)
247+
.pipe(z.string().min(1)), // Ensure final type is required string
248+
V4_DEPLOY_REGISTRY_USERNAME: z
249+
.string()
250+
.optional()
251+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_USERNAME),
252+
V4_DEPLOY_REGISTRY_PASSWORD: z
253+
.string()
254+
.optional()
255+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_PASSWORD),
256+
V4_DEPLOY_REGISTRY_NAMESPACE: z
257+
.string()
258+
.optional()
259+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_NAMESPACE)
260+
.pipe(z.string().min(1).default("trigger")), // Ensure final type is required string
261+
V4_DEPLOY_REGISTRY_ECR_TAGS: z
262+
.string()
263+
.optional()
264+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_TAGS),
265+
V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN: z
266+
.string()
267+
.optional()
268+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN),
269+
V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID: z
270+
.string()
271+
.optional()
272+
.transform((v) => v ?? process.env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID),
273+
241274
DEPLOY_IMAGE_PLATFORM: z.string().default("linux/amd64"),
242275
DEPLOY_TIMEOUT_MS: z.coerce
243276
.number()

apps/webapp/app/v3/getDeploymentImageRef.server.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
1111
import { tryCatch } from "@trigger.dev/core";
1212
import { logger } from "~/services/logger.server";
13+
import { type RegistryConfig } from "./registryConfig.server";
1314

1415
// Optional configuration for cross-account access
1516
export type AssumeRoleConfig = {
@@ -97,30 +98,24 @@ export async function createEcrClient({
9798
}
9899

99100
export async function getDeploymentImageRef({
100-
host,
101-
namespace,
101+
registry,
102102
projectRef,
103103
nextVersion,
104104
environmentSlug,
105-
registryTags,
106-
assumeRole,
107105
}: {
108-
host: string;
109-
namespace: string;
106+
registry: RegistryConfig;
110107
projectRef: string;
111108
nextVersion: string;
112109
environmentSlug: string;
113-
registryTags?: string;
114-
assumeRole?: AssumeRoleConfig;
115110
}): Promise<{
116111
imageRef: string;
117112
isEcr: boolean;
118113
repoCreated: boolean;
119114
}> {
120-
const repositoryName = `${namespace}/${projectRef}`;
121-
const imageRef = `${host}/${repositoryName}:${nextVersion}.${environmentSlug}`;
115+
const repositoryName = `${registry.namespace}/${projectRef}`;
116+
const imageRef = `${registry.host}/${repositoryName}:${nextVersion}.${environmentSlug}`;
122117

123-
if (!isEcrRegistry(host)) {
118+
if (!isEcrRegistry(registry.host)) {
124119
return {
125120
imageRef,
126121
isEcr: false,
@@ -131,16 +126,19 @@ export async function getDeploymentImageRef({
131126
const [ecrRepoError, ecrData] = await tryCatch(
132127
ensureEcrRepositoryExists({
133128
repositoryName,
134-
registryHost: host,
135-
registryTags,
136-
assumeRole,
129+
registryHost: registry.host,
130+
registryTags: registry.ecrTags,
131+
assumeRole: {
132+
roleArn: registry.ecrAssumeRoleArn,
133+
externalId: registry.ecrAssumeRoleExternalId,
134+
},
137135
})
138136
);
139137

140138
if (ecrRepoError) {
141139
logger.error("Failed to ensure ECR repository exists", {
142140
repositoryName,
143-
host,
141+
host: registry.host,
144142
ecrRepoError: ecrRepoError.message,
145143
});
146144
throw ecrRepoError;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { env } from "~/env.server";
2+
3+
export type RegistryConfig = {
4+
host: string;
5+
username?: string;
6+
password?: string;
7+
namespace: string;
8+
ecrTags?: string;
9+
ecrAssumeRoleArn?: string;
10+
ecrAssumeRoleExternalId?: string;
11+
};
12+
13+
export function getRegistryConfig(isV4Deployment: boolean): RegistryConfig {
14+
if (isV4Deployment) {
15+
return {
16+
host: env.V4_DEPLOY_REGISTRY_HOST,
17+
username: env.V4_DEPLOY_REGISTRY_USERNAME,
18+
password: env.V4_DEPLOY_REGISTRY_PASSWORD,
19+
namespace: env.V4_DEPLOY_REGISTRY_NAMESPACE,
20+
ecrTags: env.V4_DEPLOY_REGISTRY_ECR_TAGS,
21+
ecrAssumeRoleArn: env.V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN,
22+
ecrAssumeRoleExternalId: env.V4_DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID,
23+
};
24+
}
25+
26+
return {
27+
host: env.DEPLOY_REGISTRY_HOST,
28+
username: env.DEPLOY_REGISTRY_USERNAME,
29+
password: env.DEPLOY_REGISTRY_PASSWORD,
30+
namespace: env.DEPLOY_REGISTRY_NAMESPACE,
31+
ecrTags: env.DEPLOY_REGISTRY_ECR_TAGS,
32+
ecrAssumeRoleArn: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN,
33+
ecrAssumeRoleExternalId: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID,
34+
};
35+
}

apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import { ExternalBuildData, FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3/schemas";
2-
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
1+
import {
2+
ExternalBuildData,
3+
type FinalizeDeploymentRequestBody,
4+
} from "@trigger.dev/core/v3/schemas";
5+
import type { AuthenticatedEnvironment } from "~/services/apiAuth.server";
36
import { logger } from "~/services/logger.server";
47
import { BaseService, ServiceValidationError } from "./baseService.server";
58
import { join } from "node:path";
@@ -11,6 +14,7 @@ import { FinalizeDeploymentService } from "./finalizeDeployment.server";
1114
import { remoteBuildsEnabled } from "../remoteImageBuilder.server";
1215
import { getEcrAuthToken, isEcrRegistry } from "../getDeploymentImageRef.server";
1316
import { tryCatch } from "@trigger.dev/core";
17+
import { getRegistryConfig, type RegistryConfig } from "../registryConfig.server";
1418

1519
export class FinalizeDeploymentV2Service extends BaseService {
1620
public async call(
@@ -37,6 +41,7 @@ export class FinalizeDeploymentV2Service extends BaseService {
3741
externalBuildData: true,
3842
environment: true,
3943
imageReference: true,
44+
type: true,
4045
worker: {
4146
select: {
4247
project: true,
@@ -78,10 +83,13 @@ export class FinalizeDeploymentV2Service extends BaseService {
7883
throw new ServiceValidationError("External build data is invalid");
7984
}
8085

86+
const isV4Deployment = deployment.type === "MANAGED";
87+
const registryConfig = getRegistryConfig(isV4Deployment);
88+
89+
// For non-ECR registries, username and password are required upfront
8190
if (
82-
!env.DEPLOY_REGISTRY_HOST ||
83-
!env.DEPLOY_REGISTRY_USERNAME ||
84-
!env.DEPLOY_REGISTRY_PASSWORD
91+
!isEcrRegistry(registryConfig.host) &&
92+
(!registryConfig.username || !registryConfig.password)
8593
) {
8694
throw new ServiceValidationError("Missing deployment registry credentials");
8795
}
@@ -104,12 +112,7 @@ export class FinalizeDeploymentV2Service extends BaseService {
104112
orgToken: env.DEPOT_TOKEN,
105113
projectId: externalBuildData.data.projectId,
106114
},
107-
registry: {
108-
host: env.DEPLOY_REGISTRY_HOST,
109-
namespace: env.DEPLOY_REGISTRY_NAMESPACE,
110-
username: env.DEPLOY_REGISTRY_USERNAME,
111-
password: env.DEPLOY_REGISTRY_PASSWORD,
112-
},
115+
registry: registryConfig,
113116
deployment: {
114117
version: deployment.version,
115118
environmentSlug: deployment.environment.slug,
@@ -144,12 +147,7 @@ type ExecutePushToRegistryOptions = {
144147
orgToken: string;
145148
projectId: string;
146149
};
147-
registry: {
148-
host: string;
149-
namespace: string;
150-
username: string;
151-
password: string;
152-
};
150+
registry: RegistryConfig;
153151
deployment: {
154152
version: string;
155153
environmentSlug: string;
@@ -175,12 +173,7 @@ async function executePushToRegistry(
175173
writer?: WritableStreamDefaultWriter
176174
): Promise<ExecutePushResult> {
177175
// Step 1: We need to "login" to the registry
178-
const [loginError, configDir] = await tryCatch(
179-
ensureLoggedIntoDockerRegistry(registry.host, {
180-
username: registry.username,
181-
password: registry.password,
182-
})
183-
);
176+
const [loginError, configDir] = await tryCatch(ensureLoggedIntoDockerRegistry(registry));
184177

185178
if (loginError) {
186179
logger.error("Failed to login to registry", {
@@ -260,31 +253,35 @@ async function executePushToRegistry(
260253
}
261254
}
262255

263-
async function ensureLoggedIntoDockerRegistry(
264-
registryHost: string,
265-
auth: { username: string; password: string } | undefined = undefined
266-
) {
256+
async function ensureLoggedIntoDockerRegistry(registryConfig: RegistryConfig) {
267257
const tmpDir = await createTempDir();
268258
const dockerConfigPath = join(tmpDir, "config.json");
269259

260+
let auth: { username: string; password: string };
261+
270262
// If this is an ECR registry, get fresh credentials
271-
if (isEcrRegistry(registryHost)) {
263+
if (isEcrRegistry(registryConfig.host)) {
272264
auth = await getEcrAuthToken({
273-
registryHost,
274-
assumeRole: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN
265+
registryHost: registryConfig.host,
266+
assumeRole: registryConfig.ecrAssumeRoleArn
275267
? {
276-
roleArn: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN,
277-
externalId: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID,
268+
roleArn: registryConfig.ecrAssumeRoleArn,
269+
externalId: registryConfig.ecrAssumeRoleExternalId,
278270
}
279271
: undefined,
280272
});
281-
} else if (!auth) {
273+
} else if (!registryConfig.username || !registryConfig.password) {
282274
throw new Error("Authentication required for non-ECR registry");
275+
} else {
276+
auth = {
277+
username: registryConfig.username,
278+
password: registryConfig.password,
279+
};
283280
}
284281

285282
await writeJSONFile(dockerConfigPath, {
286283
auths: {
287-
[registryHost]: {
284+
[registryConfig.host]: {
288285
auth: Buffer.from(`${auth.username}:${auth.password}`).toString("base64"),
289286
},
290287
},

apps/webapp/app/v3/services/initializeDeployment.server.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { BaseService, ServiceValidationError } from "./baseService.server";
1010
import { TimeoutDeploymentService } from "./timeoutDeployment.server";
1111
import { getDeploymentImageRef } from "../getDeploymentImageRef.server";
1212
import { tryCatch } from "@trigger.dev/core";
13+
import { getRegistryConfig } from "../registryConfig.server";
1314

1415
const nanoid = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyz", 8);
1516

@@ -69,20 +70,15 @@ export class InitializeDeploymentService extends BaseService {
6970
})
7071
: undefined;
7172

73+
const isV4Deployment = payload.type === "MANAGED";
74+
const registryConfig = getRegistryConfig(isV4Deployment);
75+
7276
const [imageRefError, imageRefResult] = await tryCatch(
7377
getDeploymentImageRef({
74-
host: env.DEPLOY_REGISTRY_HOST,
75-
namespace: env.DEPLOY_REGISTRY_NAMESPACE,
78+
registry: registryConfig,
7679
projectRef: environment.project.externalRef,
7780
nextVersion,
7881
environmentSlug: environment.slug,
79-
registryTags: env.DEPLOY_REGISTRY_ECR_TAGS,
80-
assumeRole: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN
81-
? {
82-
roleArn: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_ARN,
83-
externalId: env.DEPLOY_REGISTRY_ECR_ASSUME_ROLE_EXTERNAL_ID,
84-
}
85-
: undefined,
8682
})
8783
);
8884

0 commit comments

Comments
 (0)