Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,30 @@ jobs:

> 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)
action in combination with this action:

Expand Down
167 changes: 136 additions & 31 deletions __tests__/aws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,21 @@ describe('isPubECR', () => {
});
});

describe('getRegion', () => {
describe('getRegions', () => {
test.each([
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'eu-west-3'],
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', 'cn-north-1'],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'],
['public.ecr.aws', 'us-east-1']
])('given registry %p', async (registry, expected) => {
expect(aws.getRegion(registry)).toEqual(expected);
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['eu-west-3']],
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', undefined, ['cn-north-1']],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', undefined, ['cn-northwest-1']],
['public.ecr.aws', undefined, ['us-east-1']],
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'us-west-1,us-east-1', ['eu-west-3', 'us-west-1', 'us-east-1']],
Copy link
Member

@crazy-max crazy-max Mar 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this work if registry is scoped to eu-west-3 but region is set to us-west-1? That looks error prone to me if registry is not meaningful anymore. cc @Flydiverny

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm following this and the previously existing AWS_ACCOUNT_IDS (unless I misunderstood the purpose of both features) are more work arounds to not supporting #87 ? 🤔
Maybe it would be keep code simpler to implement support for multiple registries input and make the usage more clear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless I misunderstood the purpose of both features) are more work arounds to not supporting #87 ? 🤔

Yes this looks slightly related to multi registries support. Maybe better to consider that instead of custom implementation for just aws.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks guys, I agree that support for multiple registries is a better use-case here. Ill take a look at re-opening a new pr when I get a second 🙌

['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);
});
});

Expand Down Expand Up @@ -76,12 +83,13 @@ describe('getRegistriesData', () => {
beforeEach(() => {
jest.clearAllMocks();
delete process.env.AWS_ACCOUNT_IDS;
delete process.env.AWS_REGIONS;
});
// prettier-ignore
test.each([
[
'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',
Expand All @@ -94,6 +102,7 @@ describe('getRegistriesData', () => {
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
'dkr.ecr.eu-west-3.amazonaws.com',
'012345678910,023456789012',
undefined,
[
{
registry: '012345678901.dkr.ecr.eu-west-3.amazonaws.com',
Expand All @@ -116,41 +125,137 @@ describe('getRegistriesData', () => {
'public.ecr.aws',
undefined,
undefined,
undefined,
[
{
registry: 'public.ecr.aws',
username: 'AWS',
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'
}
]
],
[
'',
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, expected: aws.RegistryData[]) => {
])('given registry %p', async (registry, fqdn, accountIDsEnv, regionsEnv, expected: aws.RegistryData[]) => {
if (accountIDsEnv) {
process.env.AWS_ACCOUNT_IDS = accountIDsEnv;
}
const accountIDs = aws.getAccountIDs(registry);
const authData: AuthorizationData[] = [];
if (accountIDs.length == 0) {
mockEcrPublicGetAuthToken.mockImplementation(() => {
return Promise.resolve({
authorizationData: {
authorizationToken: Buffer.from(`AWS:world`).toString('base64'),
}
});
});
} else {
aws.getAccountIDs(registry).forEach(accountID => {
authData.push({
authorizationToken: Buffer.from(`${accountID}:world`).toString('base64'),
proxyEndpoint: `${accountID}.${fqdn}`
});
});
mockEcrGetAuthToken.mockImplementation(() => {
return Promise.resolve({
authorizationData: authData
});
});

if (regionsEnv) {
process.env.AWS_REGIONS = regionsEnv;
}

const accountIDs = aws.getAccountIDs(registry);
const regions = aws.getRegions(registry);
const authDataByRegion: AuthorizationData[][] = [];

if (accountIDs.length == 0) {
mockEcrPublicGetAuthToken.mockImplementation(() => ({
authorizationData: {
authorizationToken: Buffer.from(`AWS:world`).toString('base64'),
}
}));
} else {
regions.forEach(region => {
const regionAuthData = accountIDs.map(accountID => ({
authorizationToken: Buffer.from(`${accountID}:world`).toString('base64'),
proxyEndpoint: `${accountID}.dkr.ecr.${region}.amazonaws.com`
}));
authDataByRegion.push(regionAuthData);
});

mockEcrGetAuthToken.mockImplementation(() => {
const regionAuthData = authDataByRegion.shift();
return { authorizationData: regionAuthData };
});
}
const regData = await aws.getRegistriesData(registry);
expect(regData).toEqual(expected);
});
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

80 changes: 51 additions & 29 deletions src/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,39 @@ export const isPubECR = (registry: string): boolean => {
return registry === 'public.ecr.aws';
};

export const getRegion = (registry: string): string => {
export const getRegions = (registry: string): string[] => {
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);
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 [];
}

const regions: Array<string> = [matches[3]];
if (process.env.AWS_REGIONS) {
regions.push(...process.env.AWS_REGIONS.split(','));
}
return matches[3];

return regions.filter((item, index) => regions.indexOf(item) === index);
};

export const getAccountIDs = (registry: string): string[] => {
if (isPubECR(registry)) {
return [];
}

const matches = registry.match(ecrRegistryRegex);
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 [];
}
const accountIDs: Array<string> = [matches[2]];
Expand All @@ -48,7 +64,7 @@ export interface 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 authTokenRequest = {};
Expand Down Expand Up @@ -80,11 +96,11 @@ export const getRegistriesData = async (registry: string, username?: string, pas
: undefined;

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({
customUserAgent: 'docker-login-action',
credentials,
region: region,
region: regions[0],
requestHandler: new NodeHttpHandler({
httpAgent: httpProxyAgent,
httpsAgent: httpsProxyAgent
Expand All @@ -106,31 +122,37 @@ export const getRegistriesData = async (registry: string, username?: string, pas
}
];
} else {
core.info(`AWS ECR detected with ${region} region`);
const ecr = new ECR({
customUserAgent: 'docker-login-action',
credentials,
region: region,
requestHandler: new NodeHttpHandler({
httpAgent: httpProxyAgent,
httpsAgent: httpsProxyAgent
})
});
const authTokenResponse = await ecr.getAuthorizationToken(authTokenRequest);
if (!Array.isArray(authTokenResponse.authorizationData) || !authTokenResponse.authorizationData.length) {
throw new Error('Could not retrieve an authorization token from AWS ECR');
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 authData of authTokenResponse.authorizationData) {
const authToken = Buffer.from(authData.authorizationToken || '', 'base64').toString('utf-8');
const creds = authToken.split(':', 2);
core.setSecret(creds[0]); // redacted in workflow logs
core.setSecret(creds[1]); // redacted in workflow logs
regDatas.push({
registry: authData.proxyEndpoint || '',
username: creds[0],
password: creds[1]
for (const region of regions) {
const ecr = new ECR({
customUserAgent: 'docker-login-action',
credentials,
region: region,
requestHandler: new NodeHttpHandler({
httpAgent: httpProxyAgent,
httpsAgent: httpsProxyAgent
})
});
const authTokenResponse = await ecr.getAuthorizationToken(authTokenRequest);
if (!Array.isArray(authTokenResponse.authorizationData) || !authTokenResponse.authorizationData.length) {
throw new Error('Could not retrieve an authorization token from AWS ECR');
}
for (const authData of authTokenResponse.authorizationData) {
const authToken = Buffer.from(authData.authorizationToken || '', 'base64').toString('utf-8');
const creds = authToken.split(':', 2);
core.setSecret(creds[0]); // redacted in workflow logs
core.setSecret(creds[1]); // redacted in workflow logs
regDatas.push({
registry: authData.proxyEndpoint || '',
username: creds[0],
password: creds[1]
});
}
}
return regDatas;
}
Expand Down