mirror of
				https://github.com/docker/login-action.git
				synced 2025-10-31 10:10:09 +08:00 
			
		
		
		
	feature: Support multi reagion login for AWS ECR
This adds support for allowing AWS ECR logins via multiple regions. Functionality is quite similar to the existing support for multi-AWS accounts. This adds in a new valid environment variable `AWS_REGIONS` that can additionally be used to run the login against multiple regions. Main changes are in `aws.ts` where `getRegion` is replaced by `getRegions` which will construct and return a list rather than a string. Since `getRegistriesData` already returns `regDatas` in a list due to its support for multi-aws-accounts, all I really needed to add was a `for-loop` wrapper to iterate on regions around the existing loop that iterates on account IDs. Signed-off-by: Helen Lim <hlim2@atlassian.com>
This commit is contained in:
		
							parent
							
								
									327cd5a69d
								
							
						
					
					
						commit
						a98cecd3e7
					
				
							
								
								
									
										24
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								README.md
									
									
									
									
									
								
							| @ -352,6 +352,30 @@ jobs: | |||||||
| 
 | 
 | ||||||
| > Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html) | > Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html) | ||||||
| 
 | 
 | ||||||
|  | You can use the environment variable `AWS_REGIONS` to set multiple regions account ids in `AWS_ACCOUNT_IDS`. | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | name: ci | ||||||
|  | 
 | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     branches: main | ||||||
|  | 
 | ||||||
|  | jobs: | ||||||
|  |   login: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - | ||||||
|  |         name: Login to ECR | ||||||
|  |         uses: docker/login-action@v3 | ||||||
|  |         with: | ||||||
|  |           registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com | ||||||
|  |           username: ${{ vars.AWS_ACCESS_KEY_ID }} | ||||||
|  |           password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||||||
|  |         env: | ||||||
|  |           AWS_REGIONS: us-west-2,us-east-1,eu-central-1 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) | You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) | ||||||
| action in combination with this action: | action in combination with this action: | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -29,14 +29,21 @@ describe('isPubECR', () => { | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| describe('getRegion', () => { | describe('getRegions', () => { | ||||||
|   test.each([ |   test.each([ | ||||||
|     ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'eu-west-3'], |     ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['eu-west-3']], | ||||||
|     ['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', 'cn-north-1'], |     ['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', undefined, ['cn-north-1']], | ||||||
|     ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'], |     ['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', undefined, ['cn-northwest-1']], | ||||||
|     ['public.ecr.aws', 'us-east-1'] |     ['public.ecr.aws', undefined, ['us-east-1']], | ||||||
|   ])('given registry %p', async (registry, expected) => { |     ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'us-west-1,us-east-1', ['eu-west-3', 'us-west-1', 'us-east-1']], | ||||||
|     expect(aws.getRegion(registry)).toEqual(expected); |     ['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'us-west-1,eu-west-3,us-east-1', ['eu-west-3', 'us-west-1', 'us-east-1']], | ||||||
|  |     ['', 'us-west-1,us-east-1', ['us-west-1', 'us-east-1']], | ||||||
|  |     ['', 'us-west-1,us-east-1,us-east-1', ['us-west-1', 'us-east-1']] | ||||||
|  |   ])('given registry %p', async (registry, regionsEnv, expected) => { | ||||||
|  |     if (regionsEnv) { | ||||||
|  |       process.env.AWS_REGIONS = regionsEnv; | ||||||
|  |     } | ||||||
|  |     expect(aws.getRegions(registry)).toEqual(expected); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| @ -76,12 +83,13 @@ describe('getRegistriesData', () => { | |||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     jest.clearAllMocks(); |     jest.clearAllMocks(); | ||||||
|     delete process.env.AWS_ACCOUNT_IDS; |     delete process.env.AWS_ACCOUNT_IDS; | ||||||
|  |     delete process.env.AWS_REGIONS; | ||||||
|   }); |   }); | ||||||
|   // prettier-ignore
 |   // prettier-ignore
 | ||||||
|   test.each([ |   test.each([ | ||||||
|     [ |     [ | ||||||
|       '012345678901.dkr.ecr.aws-region-1.amazonaws.com', |       '012345678901.dkr.ecr.aws-region-1.amazonaws.com', | ||||||
|       'dkr.ecr.aws-region-1.amazonaws.com', undefined, |       'dkr.ecr.aws-region-1.amazonaws.com', undefined, undefined, | ||||||
|       [ |       [ | ||||||
|         { |         { | ||||||
|           registry: '012345678901.dkr.ecr.aws-region-1.amazonaws.com', |           registry: '012345678901.dkr.ecr.aws-region-1.amazonaws.com', | ||||||
| @ -94,6 +102,7 @@ describe('getRegistriesData', () => { | |||||||
|       '012345678901.dkr.ecr.eu-west-3.amazonaws.com', |       '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|       'dkr.ecr.eu-west-3.amazonaws.com', |       'dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|       '012345678910,023456789012', |       '012345678910,023456789012', | ||||||
|  |       undefined, | ||||||
|       [ |       [ | ||||||
|         { |         { | ||||||
|           registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com', |           registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
| @ -116,6 +125,7 @@ describe('getRegistriesData', () => { | |||||||
|       'public.ecr.aws', |       'public.ecr.aws', | ||||||
|       undefined, |       undefined, | ||||||
|       undefined, |       undefined, | ||||||
|  |       undefined, | ||||||
|       [ |       [ | ||||||
|         { |         { | ||||||
|           registry: 'public.ecr.aws', |           registry: 'public.ecr.aws', | ||||||
| @ -123,32 +133,127 @@ describe('getRegistriesData', () => { | |||||||
|           password: 'world' |           password: 'world' | ||||||
|         } |         } | ||||||
|       ] |       ] | ||||||
|  |     ], | ||||||
|  |     [ | ||||||
|  |       '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|  |       undefined, | ||||||
|  |       undefined, | ||||||
|  |       'us-west-1,us-east-3', | ||||||
|  |       [ | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-west-1.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-east-3.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         } | ||||||
|  |       ], | ||||||
|  |     ], | ||||||
|  |     [ | ||||||
|  |       '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|  |       undefined, | ||||||
|  |       '023456789012', | ||||||
|  |       'us-west-1,us-east-3', | ||||||
|  |       [ | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '023456789012.dkr.ecr.eu-west-3.amazonaws.com', | ||||||
|  |           username: '023456789012', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-west-1.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '023456789012.dkr.ecr.us-west-1.amazonaws.com', | ||||||
|  |           username: '023456789012', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-east-3.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '023456789012.dkr.ecr.us-east-3.amazonaws.com', | ||||||
|  |           username: '023456789012', | ||||||
|  |           password: 'world' | ||||||
|  |         } | ||||||
|       ] |       ] | ||||||
|   ])('given registry %p', async (registry, fqdn, accountIDsEnv, expected: aws.RegistryData[]) => { |     ], | ||||||
|  |     [ | ||||||
|  |       '', | ||||||
|  |       undefined, | ||||||
|  |       '012345678901,023456789012', | ||||||
|  |       'us-west-1,us-east-3', | ||||||
|  |       [ | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-west-1.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '023456789012.dkr.ecr.us-west-1.amazonaws.com', | ||||||
|  |           username: '023456789012', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '012345678901.dkr.ecr.us-east-3.amazonaws.com', | ||||||
|  |           username: '012345678901', | ||||||
|  |           password: 'world' | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |           registry: '023456789012.dkr.ecr.us-east-3.amazonaws.com', | ||||||
|  |           username: '023456789012', | ||||||
|  |           password: 'world' | ||||||
|  |         } | ||||||
|  |       ] | ||||||
|  |     ] | ||||||
|  |   ])('given registry %p', async (registry, fqdn, accountIDsEnv, regionsEnv, expected: aws.RegistryData[]) => { | ||||||
|     if (accountIDsEnv) { |     if (accountIDsEnv) { | ||||||
|       process.env.AWS_ACCOUNT_IDS = accountIDsEnv; |       process.env.AWS_ACCOUNT_IDS = accountIDsEnv; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     if (regionsEnv) { | ||||||
|  |       process.env.AWS_REGIONS = regionsEnv; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const accountIDs = aws.getAccountIDs(registry); |     const accountIDs = aws.getAccountIDs(registry); | ||||||
|     const authData: AuthorizationData[] = []; |     const regions = aws.getRegions(registry); | ||||||
|  |     const authDataByRegion: AuthorizationData[][] = []; | ||||||
|  | 
 | ||||||
|   if (accountIDs.length == 0) { |   if (accountIDs.length == 0) { | ||||||
|       mockEcrPublicGetAuthToken.mockImplementation(() => { |     mockEcrPublicGetAuthToken.mockImplementation(() => ({ | ||||||
|         return Promise.resolve({ |  | ||||||
|       authorizationData: { |       authorizationData: { | ||||||
|         authorizationToken: Buffer.from(`AWS:world`).toString('base64'), |         authorizationToken: Buffer.from(`AWS:world`).toString('base64'), | ||||||
|       } |       } | ||||||
|         }); |     })); | ||||||
|       }); |  | ||||||
|   }  else { |   }  else { | ||||||
|       aws.getAccountIDs(registry).forEach(accountID => { |     regions.forEach(region => { | ||||||
|         authData.push({ |       const regionAuthData = accountIDs.map(accountID => ({ | ||||||
|         authorizationToken: Buffer.from(`${accountID}:world`).toString('base64'), |         authorizationToken: Buffer.from(`${accountID}:world`).toString('base64'), | ||||||
|           proxyEndpoint: `${accountID}.${fqdn}` |         proxyEndpoint: `${accountID}.dkr.ecr.${region}.amazonaws.com` | ||||||
|         }); |       })); | ||||||
|  |       authDataByRegion.push(regionAuthData); | ||||||
|     }); |     }); | ||||||
|  | 
 | ||||||
|     mockEcrGetAuthToken.mockImplementation(() => { |     mockEcrGetAuthToken.mockImplementation(() => { | ||||||
|         return Promise.resolve({ |       const regionAuthData = authDataByRegion.shift(); | ||||||
|           authorizationData: authData |       return { authorizationData: regionAuthData }; | ||||||
|         }); |  | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
|     const regData = await aws.getRegistriesData(registry); |     const regData = await aws.getRegistriesData(registry); | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								dist/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/index.js.map
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js.map
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										40
									
								
								src/aws.ts
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								src/aws.ts
									
									
									
									
									
								
							| @ -15,23 +15,39 @@ export const isPubECR = (registry: string): boolean => { | |||||||
|   return registry === 'public.ecr.aws'; |   return registry === 'public.ecr.aws'; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const getRegion = (registry: string): string => { | export const getRegions = (registry: string): string[] => { | ||||||
|   if (isPubECR(registry)) { |   if (isPubECR(registry)) { | ||||||
|     return process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1'; |     return [process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1']; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   const matches = registry.match(ecrRegistryRegex); |   const matches = registry.match(ecrRegistryRegex); | ||||||
|   if (!matches) { |   if (!matches) { | ||||||
|     return ''; |     if (process.env.AWS_REGIONS) { | ||||||
|  |       const regions: Array<string> = [...process.env.AWS_REGIONS.split(',')]; | ||||||
|  |       return regions.filter((item, index) => regions.indexOf(item) === index); | ||||||
|     } |     } | ||||||
|   return matches[3]; |     return []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const regions: Array<string> = [matches[3]]; | ||||||
|  |   if (process.env.AWS_REGIONS) { | ||||||
|  |     regions.push(...process.env.AWS_REGIONS.split(',')); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return regions.filter((item, index) => regions.indexOf(item) === index); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const getAccountIDs = (registry: string): string[] => { | export const getAccountIDs = (registry: string): string[] => { | ||||||
|   if (isPubECR(registry)) { |   if (isPubECR(registry)) { | ||||||
|     return []; |     return []; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   const matches = registry.match(ecrRegistryRegex); |   const matches = registry.match(ecrRegistryRegex); | ||||||
|   if (!matches) { |   if (!matches) { | ||||||
|  |     if (process.env.AWS_ACCOUNT_IDS) { | ||||||
|  |       const accountIDs: Array<string> = [...process.env.AWS_ACCOUNT_IDS.split(',')]; | ||||||
|  |       return accountIDs.filter((item, index) => accountIDs.indexOf(item) === index); | ||||||
|  |     } | ||||||
|     return []; |     return []; | ||||||
|   } |   } | ||||||
|   const accountIDs: Array<string> = [matches[2]]; |   const accountIDs: Array<string> = [matches[2]]; | ||||||
| @ -48,7 +64,7 @@ export interface RegistryData { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getRegistriesData = async (registry: string, username?: string, password?: string): Promise<RegistryData[]> => { | export const getRegistriesData = async (registry: string, username?: string, password?: string): Promise<RegistryData[]> => { | ||||||
|   const region = getRegion(registry); |   const regions = getRegions(registry); | ||||||
|   const accountIDs = getAccountIDs(registry); |   const accountIDs = getAccountIDs(registry); | ||||||
| 
 | 
 | ||||||
|   const authTokenRequest = {}; |   const authTokenRequest = {}; | ||||||
| @ -80,11 +96,11 @@ export const getRegistriesData = async (registry: string, username?: string, pas | |||||||
|       : undefined; |       : undefined; | ||||||
| 
 | 
 | ||||||
|   if (isPubECR(registry)) { |   if (isPubECR(registry)) { | ||||||
|     core.info(`AWS Public ECR detected with ${region} region`); |     core.info(`AWS Public ECR detected with region ${regions[0]}`); | ||||||
|     const ecrPublic = new ECRPUBLIC({ |     const ecrPublic = new ECRPUBLIC({ | ||||||
|       customUserAgent: 'docker-login-action', |       customUserAgent: 'docker-login-action', | ||||||
|       credentials, |       credentials, | ||||||
|       region: region, |       region: regions[0], | ||||||
|       requestHandler: new NodeHttpHandler({ |       requestHandler: new NodeHttpHandler({ | ||||||
|         httpAgent: httpProxyAgent, |         httpAgent: httpProxyAgent, | ||||||
|         httpsAgent: httpsProxyAgent |         httpsAgent: httpsProxyAgent | ||||||
| @ -106,7 +122,13 @@ export const getRegistriesData = async (registry: string, username?: string, pas | |||||||
|       } |       } | ||||||
|     ]; |     ]; | ||||||
|   } else { |   } else { | ||||||
|     core.info(`AWS ECR detected with ${region} region`); |     if (regions.length > 1) { | ||||||
|  |       core.info(`AWS ECR detected with regions ${regions}`); | ||||||
|  |     } else { | ||||||
|  |       core.info(`AWS ECR detected with region ${regions[0]}`); | ||||||
|  |     } | ||||||
|  |     const regDatas: RegistryData[] = []; | ||||||
|  |     for (const region of regions) { | ||||||
|       const ecr = new ECR({ |       const ecr = new ECR({ | ||||||
|         customUserAgent: 'docker-login-action', |         customUserAgent: 'docker-login-action', | ||||||
|         credentials, |         credentials, | ||||||
| @ -120,7 +142,6 @@ export const getRegistriesData = async (registry: string, username?: string, pas | |||||||
|       if (!Array.isArray(authTokenResponse.authorizationData) || !authTokenResponse.authorizationData.length) { |       if (!Array.isArray(authTokenResponse.authorizationData) || !authTokenResponse.authorizationData.length) { | ||||||
|         throw new Error('Could not retrieve an authorization token from AWS ECR'); |         throw new Error('Could not retrieve an authorization token from AWS ECR'); | ||||||
|       } |       } | ||||||
|     const regDatas: RegistryData[] = []; |  | ||||||
|       for (const authData of authTokenResponse.authorizationData) { |       for (const authData of authTokenResponse.authorizationData) { | ||||||
|         const authToken = Buffer.from(authData.authorizationToken || '', 'base64').toString('utf-8'); |         const authToken = Buffer.from(authData.authorizationToken || '', 'base64').toString('utf-8'); | ||||||
|         const creds = authToken.split(':', 2); |         const creds = authToken.split(':', 2); | ||||||
| @ -132,6 +153,7 @@ export const getRegistriesData = async (registry: string, username?: string, pas | |||||||
|           password: creds[1] |           password: creds[1] | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|     return regDatas; |     return regDatas; | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Helen Lim
						Helen Lim