@@ -12,27 +12,23 @@ import { tryCatch } from "@trigger.dev/core";
1212import { logger } from "~/services/logger.server" ;
1313
1414// Optional configuration for cross-account access
15- export type CrossAccountConfig = {
16- assumeRole : boolean ;
17- roleName : string ;
15+ export type AssumeRoleConfig = {
16+ roleArn ?: string ;
17+ externalId ? : string ;
1818} ;
1919
20- const DEFAULT_CROSS_ACCOUNT_CONFIG : CrossAccountConfig = {
21- assumeRole : false ,
22- roleName : "OrganizationAccountAccessRole" ,
23- } ;
24-
25- async function getAssumedRoleCredentials (
26- region : string ,
27- accountId : string ,
28- config : CrossAccountConfig
29- ) : Promise < {
20+ async function getAssumedRoleCredentials ( {
21+ region,
22+ assumeRole,
23+ } : {
24+ region : string ;
25+ assumeRole ?: AssumeRoleConfig ;
26+ } ) : Promise < {
3027 accessKeyId : string ;
3128 secretAccessKey : string ;
3229 sessionToken : string ;
3330} > {
3431 const sts = new STSClient ( { region } ) ;
35- const roleArn = `arn:aws:iam::${ accountId } :role/${ config . roleName } ` ;
3632
3733 // Generate a unique session name using timestamp and random string
3834 // This helps with debugging but doesn't affect concurrent sessions
@@ -43,11 +39,12 @@ async function getAssumedRoleCredentials(
4339 try {
4440 const response = await sts . send (
4541 new AssumeRoleCommand ( {
46- RoleArn : roleArn ,
42+ RoleArn : assumeRole ?. roleArn ,
4743 RoleSessionName : sessionName ,
4844 // Sessions automatically expire after 1 hour
4945 // AWS allows 5000 concurrent sessions by default
5046 DurationSeconds : 3600 ,
47+ ExternalId : assumeRole ?. externalId ,
5148 } )
5249 ) ;
5350
@@ -69,23 +66,28 @@ async function getAssumedRoleCredentials(
6966 sessionToken : response . Credentials . SessionToken ,
7067 } ;
7168 } catch ( error ) {
72- logger . error ( "Failed to assume role" , { roleArn, sessionName, error } ) ;
69+ logger . error ( "Failed to assume role" , {
70+ assumeRole,
71+ sessionName,
72+ error,
73+ } ) ;
7374 throw error ;
7475 }
7576}
7677
77- async function createEcrClient (
78- region : string ,
79- registryId ?: string ,
80- crossAccountConfig : CrossAccountConfig = DEFAULT_CROSS_ACCOUNT_CONFIG
81- ) {
82- // If no registryId or role assumption is disabled, use default credentials
83- if ( ! registryId || ! crossAccountConfig . assumeRole ) {
78+ export async function createEcrClient ( {
79+ region,
80+ assumeRole,
81+ } : {
82+ region : string ;
83+ assumeRole ?: AssumeRoleConfig ;
84+ } ) {
85+ if ( ! assumeRole ) {
8486 return new ECRClient ( { region } ) ;
8587 }
8688
8789 // Get credentials for cross-account access
88- const credentials = await getAssumedRoleCredentials ( region , registryId , crossAccountConfig ) ;
90+ const credentials = await getAssumedRoleCredentials ( { region, assumeRole } ) ;
8991 return new ECRClient ( {
9092 region,
9193 credentials,
@@ -98,18 +100,16 @@ export async function getDeploymentImageRef({
98100 projectRef,
99101 nextVersion,
100102 environmentSlug,
101- registryId,
102103 registryTags,
103- crossAccountConfig ,
104+ assumeRole ,
104105} : {
105106 host : string ;
106107 namespace : string ;
107108 projectRef : string ;
108109 nextVersion : string ;
109110 environmentSlug : string ;
110- registryId ?: string ;
111111 registryTags ?: string ;
112- crossAccountConfig ?: CrossAccountConfig ;
112+ assumeRole ?: AssumeRoleConfig ;
113113} ) : Promise < {
114114 imageRef : string ;
115115 isEcr : boolean ;
@@ -128,9 +128,8 @@ export async function getDeploymentImageRef({
128128 ensureEcrRepositoryExists ( {
129129 repositoryName,
130130 registryHost : host ,
131- registryId,
132131 registryTags,
133- crossAccountConfig ,
132+ assumeRole ,
134133 } )
135134 ) ;
136135
@@ -163,17 +162,17 @@ function parseRegistryTags(tags: string): Tag[] {
163162async function createEcrRepository ( {
164163 repositoryName,
165164 region,
166- registryId ,
165+ accountId ,
167166 registryTags,
168- crossAccountConfig ,
167+ assumeRole ,
169168} : {
170169 repositoryName : string ;
171170 region : string ;
172- registryId ?: string ;
171+ accountId ?: string ;
173172 registryTags ?: string ;
174- crossAccountConfig ?: CrossAccountConfig ;
173+ assumeRole ?: AssumeRoleConfig ;
175174} ) : Promise < Repository > {
176- const ecr = await createEcrClient ( region , registryId , crossAccountConfig ) ;
175+ const ecr = await createEcrClient ( { region, assumeRole } ) ;
177176
178177 const result = await ecr . send (
179178 new CreateRepositoryCommand ( {
@@ -182,7 +181,7 @@ async function createEcrRepository({
182181 encryptionConfiguration : {
183182 encryptionType : "AES256" ,
184183 } ,
185- registryId,
184+ registryId : accountId ,
186185 tags : registryTags ? parseRegistryTags ( registryTags ) : undefined ,
187186 } )
188187 ) ;
@@ -198,21 +197,21 @@ async function createEcrRepository({
198197async function getEcrRepository ( {
199198 repositoryName,
200199 region,
201- registryId ,
202- crossAccountConfig ,
200+ accountId ,
201+ assumeRole ,
203202} : {
204203 repositoryName : string ;
205204 region : string ;
206- registryId ?: string ;
207- crossAccountConfig ?: CrossAccountConfig ;
205+ accountId ?: string ;
206+ assumeRole ?: AssumeRoleConfig ;
208207} ) : Promise < Repository | undefined > {
209- const ecr = await createEcrClient ( region , registryId , crossAccountConfig ) ;
208+ const ecr = await createEcrClient ( { region, assumeRole } ) ;
210209
211210 try {
212211 const result = await ecr . send (
213212 new DescribeRepositoriesCommand ( {
214213 repositoryNames : [ repositoryName ] ,
215- registryId,
214+ registryId : accountId ,
216215 } )
217216 ) ;
218217
@@ -234,35 +233,46 @@ async function getEcrRepository({
234233 }
235234}
236235
237- export function getEcrRegion ( registryHost : string ) : string | undefined {
236+ export type EcrRegistryComponents = {
237+ accountId : string ;
238+ region : string ;
239+ } ;
240+
241+ export function parseEcrRegistryDomain ( registryHost : string ) : EcrRegistryComponents {
238242 const parts = registryHost . split ( "." ) ;
239- if ( parts . length !== 6 || parts [ 1 ] !== "dkr" || parts [ 2 ] !== "ecr" ) {
240- return undefined ;
243+
244+ const isValid =
245+ parts . length === 6 &&
246+ parts [ 1 ] === "dkr" &&
247+ parts [ 2 ] === "ecr" &&
248+ parts [ 4 ] === "amazonaws" &&
249+ parts [ 5 ] === "com" ;
250+
251+ if ( ! isValid ) {
252+ throw new Error ( `Invalid ECR registry host: ${ registryHost } ` ) ;
241253 }
242- return parts [ 3 ] ;
254+
255+ return {
256+ accountId : parts [ 0 ] ,
257+ region : parts [ 3 ] ,
258+ } ;
243259}
244260
245261async function ensureEcrRepositoryExists ( {
246262 repositoryName,
247263 registryHost,
248- registryId,
249264 registryTags,
250- crossAccountConfig ,
265+ assumeRole ,
251266} : {
252267 repositoryName : string ;
253268 registryHost : string ;
254- registryId ?: string ;
255269 registryTags ?: string ;
256- crossAccountConfig ?: CrossAccountConfig ;
270+ assumeRole ?: AssumeRoleConfig ;
257271} ) : Promise < Repository > {
258- const region = getEcrRegion ( registryHost ) ;
259-
260- if ( ! region ) {
261- throw new Error ( `Invalid ECR registry host: ${ registryHost } ` ) ;
262- }
272+ const { region, accountId } = parseEcrRegistryDomain ( registryHost ) ;
263273
264274 const [ getRepoError , existingRepo ] = await tryCatch (
265- getEcrRepository ( { repositoryName, region, registryId , crossAccountConfig } )
275+ getEcrRepository ( { repositoryName, region, accountId , assumeRole } )
266276 ) ;
267277
268278 if ( getRepoError ) {
@@ -276,7 +286,7 @@ async function ensureEcrRepositoryExists({
276286 }
277287
278288 const [ createRepoError , newRepo ] = await tryCatch (
279- createEcrRepository ( { repositoryName, region, registryId , registryTags, crossAccountConfig } )
289+ createEcrRepository ( { repositoryName, region, accountId , registryTags, assumeRole } )
280290 ) ;
281291
282292 if ( createRepoError ) {
@@ -296,23 +306,21 @@ async function ensureEcrRepositoryExists({
296306
297307export async function getEcrAuthToken ( {
298308 registryHost,
299- registryId,
300- crossAccountConfig,
309+ assumeRole,
301310} : {
302311 registryHost : string ;
303- registryId ?: string ;
304- crossAccountConfig ?: CrossAccountConfig ;
312+ assumeRole ?: AssumeRoleConfig ;
305313} ) : Promise < { username : string ; password : string } > {
306- const region = getEcrRegion ( registryHost ) ;
314+ const { region, accountId } = parseEcrRegistryDomain ( registryHost ) ;
307315 if ( ! region ) {
308316 logger . error ( "Invalid ECR registry host" , { registryHost } ) ;
309317 throw new Error ( "Invalid ECR registry host" ) ;
310318 }
311319
312- const ecr = await createEcrClient ( region , registryId , crossAccountConfig ) ;
320+ const ecr = await createEcrClient ( { region, assumeRole } ) ;
313321 const response = await ecr . send (
314322 new GetAuthorizationTokenCommand ( {
315- registryIds : registryId ? [ registryId ] : undefined ,
323+ registryIds : accountId ? [ accountId ] : undefined ,
316324 } )
317325 ) ;
318326
0 commit comments