Skip to content

Commit f336459

Browse files
authored
Merge pull request #40 from crazy-max/registry-ids
Handle Amazon ECR registries associated with other accounts
2 parents 9f18920 + 24646ef commit f336459

File tree

5 files changed

+182
-71
lines changed

5 files changed

+182
-71
lines changed

README.md

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ jobs:
227227

228228
### AWS Elastic Container Registry (ECR)
229229

230-
Use an IAM user with the [ability to push to ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html).
230+
Use an IAM user with the ability to [push to ECR with `AmazonEC2ContainerRegistryPowerUser` managed policy for example](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html#AmazonEC2ContainerRegistryPowerUser).
231231
Then create and download access keys and save `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [as secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
232232
in your GitHub repo.
233233

@@ -251,6 +251,33 @@ jobs:
251251
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
252252
```
253253

254+
If you need to log in to Amazon ECR registries associated with other accounts, you can use the `AWS_ACCOUNT_IDS`
255+
environment variable:
256+
257+
```yaml
258+
name: ci
259+
260+
on:
261+
push:
262+
branches: master
263+
264+
jobs:
265+
login:
266+
runs-on: ubuntu-latest
267+
steps:
268+
-
269+
name: Login to ECR
270+
uses: docker/login-action@v1
271+
with:
272+
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
273+
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
274+
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
275+
env:
276+
AWS_ACCOUNT_IDS: 012345678910,023456789012
277+
```
278+
279+
> Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html)
280+
254281
You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) action in
255282
combination with this action:
256283

@@ -283,7 +310,7 @@ jobs:
283310

284311
### AWS Public Elastic Container Registry (ECR)
285312

286-
Use an IAM user with the [ability to push to ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html).
313+
Use an IAM user with the ability to [push to ECR Public with `AmazonElasticContainerRegistryPublicPowerUser` managed policy for example](https://docs.aws.amazon.com/AmazonECR/latest/public/public-ecr-managed-policies.html#AmazonElasticContainerRegistryPublicPowerUser).
287314
Then create and download access keys and save `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [as secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
288315
in your GitHub repo.
289316

@@ -311,41 +338,15 @@ jobs:
311338

312339
> Replace `<region>` with its respective value (default `us-east-1`).
313340

314-
You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) action in
315-
combination with this action:
316-
317-
```yaml
318-
name: ci
319-
320-
on:
321-
push:
322-
branches: master
323-
324-
jobs:
325-
login:
326-
runs-on: ubuntu-latest
327-
steps:
328-
-
329-
name: Configure AWS Credentials
330-
uses: aws-actions/configure-aws-credentials@v1
331-
with:
332-
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
333-
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
334-
aws-region: <region>
335-
-
336-
name: Login to Public ECR
337-
uses: docker/login-action@v1
338-
with:
339-
registry: public.ecr.aws
340-
```
341-
342-
> Replace `<region>` with its respective value.
343-
344341
### OCI Oracle Cloud Infrastructure Registry (OCIR)
342+
345343
To push into OCIR in specific tenancy the [username](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#LogintoOracleCloudInfrastructureRegistryfromtheDockerCLI)
346-
must be placed in format `<tenancy>/<username>` (in case of federated tenancy use the format `<tenancy-namespace>/oracleidentitycloudservice/<username>`).
347-
For password [create an auth token](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#GetanAuthToken). Save username and token
348-
[as a secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository) in your GitHub repo.
344+
must be placed in format `<tenancy>/<username>` (in case of federated tenancy use the format
345+
`<tenancy-namespace>/oracleidentitycloudservice/<username>`).
346+
347+
For password [create an auth token](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#GetanAuthToken).
348+
Save username and token [as a secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets#creating-encrypted-secrets-for-a-repository)
349+
in your GitHub repo.
349350

350351
```yaml
351352
name: ci
@@ -366,6 +367,7 @@ jobs:
366367
username: ${{ secrets.OCI_USERNAME }}
367368
password: ${{ secrets.OCI_TOKEN }}
368369
```
370+
369371
> Replace `<region>` with their respective values from [availability regions](https://docs.cloud.oracle.com/iaas/Content/Registry/Concepts/registryprerequisites.htm#Availab)
370372

371373
## Customizing

__tests__/aws.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ describe('isECR', () => {
66
['registry.gitlab.com', false],
77
['gcr.io', false],
88
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', true],
9+
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', true],
10+
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true],
911
['public.ecr.aws', true]
1012
])('given registry %p', async (registry, expected) => {
1113
expect(await aws.isECR(registry)).toEqual(expected);
@@ -17,6 +19,8 @@ describe('isPubECR', () => {
1719
['registry.gitlab.com', false],
1820
['gcr.io', false],
1921
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', false],
22+
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', false],
23+
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false],
2024
['public.ecr.aws', true]
2125
])('given registry %p', async (registry, expected) => {
2226
expect(await aws.isPubECR(registry)).toEqual(expected);
@@ -59,8 +63,37 @@ describe('parseCLIVersion', () => {
5963
describe('getRegion', () => {
6064
test.each([
6165
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', 'eu-west-3'],
66+
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', 'cn-north-1'],
67+
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', 'cn-northwest-1'],
6268
['public.ecr.aws', 'us-east-1']
6369
])('given registry %p', async (registry, expected) => {
6470
expect(await aws.getRegion(registry)).toEqual(expected);
6571
});
6672
});
73+
74+
describe('getAccountIDs', () => {
75+
test.each([
76+
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['012345678901']],
77+
[
78+
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
79+
'012345678910,023456789012',
80+
['012345678901', '012345678910', '023456789012']
81+
],
82+
[
83+
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
84+
'012345678901,012345678910,023456789012',
85+
['012345678901', '012345678910', '023456789012']
86+
],
87+
[
88+
'390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn',
89+
'012345678910,023456789012',
90+
['390948362332', '012345678910', '023456789012']
91+
],
92+
['public.ecr.aws', undefined, []]
93+
])('given registry %p', async (registry, accountIDsEnv, expected) => {
94+
if (accountIDsEnv) {
95+
process.env.AWS_ACCOUNT_IDS = accountIDsEnv;
96+
}
97+
expect(await aws.getAccountIDs(registry)).toEqual(expected);
98+
});
99+
});

dist/index.js

Lines changed: 55 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/aws.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,40 @@ import * as semver from 'semver';
22
import * as io from '@actions/io';
33
import * as execm from './exec';
44

5-
export const isECR = async (registry: string): Promise<boolean> => {
6-
return registry.includes('amazonaws') || (await isPubECR(registry));
5+
const ecrRegistryRegex = /^(([0-9]{12})\.dkr\.ecr\.(.+)\.amazonaws\.com(.cn)?)(\/([^:]+)(:.+)?)?$/;
6+
7+
export const isECR = (registry: string): boolean => {
8+
return ecrRegistryRegex.test(registry) || isPubECR(registry);
79
};
810

9-
export const isPubECR = async (registry: string): Promise<boolean> => {
11+
export const isPubECR = (registry: string): boolean => {
1012
return registry === 'public.ecr.aws';
1113
};
1214

13-
export const getRegion = async (registry: string): Promise<string> => {
14-
if (await isPubECR(registry)) {
15+
export const getRegion = (registry: string): string => {
16+
if (isPubECR(registry)) {
1517
return process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
1618
}
17-
return registry.substring(registry.indexOf('ecr.') + 4, registry.indexOf('.amazonaws'));
19+
const matches = registry.match(ecrRegistryRegex);
20+
if (!matches) {
21+
return '';
22+
}
23+
return matches[3];
24+
};
25+
26+
export const getAccountIDs = (registry: string): string[] => {
27+
if (isPubECR(registry)) {
28+
return [];
29+
}
30+
const matches = registry.match(ecrRegistryRegex);
31+
if (!matches) {
32+
return [];
33+
}
34+
let accountIDs: Array<string> = [matches[2]];
35+
if (process.env.AWS_ACCOUNT_IDS) {
36+
accountIDs.push(...process.env.AWS_ACCOUNT_IDS.split(','));
37+
}
38+
return accountIDs.filter((item, index) => accountIDs.indexOf(item) === index);
1839
};
1940

2041
export const getCLI = async (): Promise<string> => {
@@ -45,15 +66,28 @@ export const parseCLIVersion = async (stdout: string): Promise<string> => {
4566
return semver.clean(matches[1]);
4667
};
4768

48-
export const getDockerLoginCmd = async (cliVersion: string, registry: string, region: string): Promise<string> => {
69+
export const getDockerLoginCmds = async (
70+
cliVersion: string,
71+
registry: string,
72+
region: string,
73+
accountIDs: string[]
74+
): Promise<string[]> => {
4975
let ecrCmd = (await isPubECR(registry)) ? 'ecr-public' : 'ecr';
5076
if (semver.satisfies(cliVersion, '>=2.0.0') || (await isPubECR(registry))) {
5177
return execCLI([ecrCmd, 'get-login-password', '--region', region]).then(pwd => {
52-
return `docker login --username AWS --password ${pwd} ${registry}`;
78+
return [`docker login --username AWS --password ${pwd} ${registry}`];
5379
});
5480
} else {
55-
return execCLI([ecrCmd, 'get-login', '--region', region, '--no-include-email']).then(dockerLoginCmd => {
56-
return dockerLoginCmd;
81+
return execCLI([
82+
ecrCmd,
83+
'get-login',
84+
'--region',
85+
region,
86+
'--registry-ids',
87+
accountIDs.join(' '),
88+
'--no-include-email'
89+
]).then(dockerLoginCmds => {
90+
return dockerLoginCmds.trim().split(`\n`);
5791
});
5892
}
5993
};

src/docker.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export async function loginECR(registry: string, username: string, password: str
4444
const cliPath = await aws.getCLI();
4545
const cliVersion = await aws.getCLIVersion();
4646
const region = await aws.getRegion(registry);
47+
const accountIDs = await aws.getAccountIDs(registry);
4748

4849
if (await aws.isPubECR(registry)) {
4950
core.info(`💡 AWS Public ECR detected with ${region} region`);
@@ -55,13 +56,19 @@ export async function loginECR(registry: string, username: string, password: str
5556
process.env.AWS_SECRET_ACCESS_KEY = password || process.env.AWS_SECRET_ACCESS_KEY;
5657

5758
core.info(`⬇️ Retrieving docker login command through AWS CLI ${cliVersion} (${cliPath})...`);
58-
const loginCmd = await aws.getDockerLoginCmd(cliVersion, registry, region);
59+
const loginCmds = await aws.getDockerLoginCmds(cliVersion, registry, region, accountIDs);
5960

6061
core.info(`🔑 Logging into ${registry}...`);
61-
execm.exec(loginCmd, [], true).then(res => {
62-
if (res.stderr != '' && !res.success) {
63-
throw new Error(res.stderr);
64-
}
65-
core.info('🎉 Login Succeeded!');
62+
loginCmds.forEach((loginCmd, index) => {
63+
execm.exec(loginCmd, [], true).then(res => {
64+
if (res.stderr != '' && !res.success) {
65+
throw new Error(res.stderr);
66+
}
67+
if (loginCmds.length > 1) {
68+
core.info(`🎉 Login Succeeded! (${index}/${loginCmds.length})`);
69+
} else {
70+
core.info('🎉 Login Succeeded!');
71+
}
72+
});
6673
});
6774
}

0 commit comments

Comments
 (0)