Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Amazon ECR registries associated with other accounts #40

Merged
merged 2 commits into from
Dec 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 37 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ jobs:

### AWS Elastic Container Registry (ECR)

Use an IAM user with the [ability to push to ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html).
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).
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)
in your GitHub repo.

Expand All @@ -251,6 +251,33 @@ jobs:
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

If you need to log in to Amazon ECR registries associated with other accounts, you can use the `AWS_ACCOUNT_IDS`
environment variable:

```yaml
name: ci

on:
push:
branches: master

jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Login to ECR
uses: docker/login-action@v1
with:
registry: <aws-account-number>.dkr.ecr.<region>.amazonaws.com
username: ${{ secrets.AWS_ACCESS_KEY_ID }}
password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
env:
AWS_ACCOUNT_IDS: 012345678910,023456789012
```

> Only available with [AWS CLI version 1](https://docs.aws.amazon.com/cli/latest/reference/ecr/get-login.html)

You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) action in
combination with this action:

Expand Down Expand Up @@ -283,7 +310,7 @@ jobs:

### AWS Public Elastic Container Registry (ECR)

Use an IAM user with the [ability to push to ECR](https://docs.aws.amazon.com/AmazonECR/latest/userguide/ecr_managed_policies.html).
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).
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)
in your GitHub repo.

Expand Down Expand Up @@ -311,41 +338,15 @@ jobs:

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

You can also use the [Configure AWS Credentials](https://github.com/aws-actions/configure-aws-credentials) action in
combination with this action:

```yaml
name: ci

on:
push:
branches: master

jobs:
login:
runs-on: ubuntu-latest
steps:
-
name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: <region>
-
name: Login to Public ECR
uses: docker/login-action@v1
with:
registry: public.ecr.aws
```

> Replace `<region>` with its respective value.

### OCI Oracle Cloud Infrastructure Registry (OCIR)

To push into OCIR in specific tenancy the [username](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#LogintoOracleCloudInfrastructureRegistryfromtheDockerCLI)
must be placed in format `<tenancy>/<username>` (in case of federated tenancy use the format `<tenancy-namespace>/oracleidentitycloudservice/<username>`).
For password [create an auth token](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#GetanAuthToken). 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) in your GitHub repo.
must be placed in format `<tenancy>/<username>` (in case of federated tenancy use the format
`<tenancy-namespace>/oracleidentitycloudservice/<username>`).

For password [create an auth token](https://www.oracle.com/webfolder/technetwork/tutorials/obe/oci/registry/index.html#GetanAuthToken).
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)
in your GitHub repo.

```yaml
name: ci
Expand All @@ -366,6 +367,7 @@ jobs:
username: ${{ secrets.OCI_USERNAME }}
password: ${{ secrets.OCI_TOKEN }}
```

> Replace `<region>` with their respective values from [availability regions](https://docs.cloud.oracle.com/iaas/Content/Registry/Concepts/registryprerequisites.htm#Availab)

## Customizing
Expand Down
33 changes: 33 additions & 0 deletions __tests__/aws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ describe('isECR', () => {
['registry.gitlab.com', false],
['gcr.io', false],
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', true],
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', true],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', true],
['public.ecr.aws', true]
])('given registry %p', async (registry, expected) => {
expect(await aws.isECR(registry)).toEqual(expected);
Expand All @@ -17,6 +19,8 @@ describe('isPubECR', () => {
['registry.gitlab.com', false],
['gcr.io', false],
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', false],
['876820548815.dkr.ecr.cn-north-1.amazonaws.com.cn', false],
['390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn', false],
['public.ecr.aws', true]
])('given registry %p', async (registry, expected) => {
expect(await aws.isPubECR(registry)).toEqual(expected);
Expand Down Expand Up @@ -59,8 +63,37 @@ describe('parseCLIVersion', () => {
describe('getRegion', () => {
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(await aws.getRegion(registry)).toEqual(expected);
});
});

describe('getAccountIDs', () => {
test.each([
['012345678901.dkr.ecr.eu-west-3.amazonaws.com', undefined, ['012345678901']],
[
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
'012345678910,023456789012',
['012345678901', '012345678910', '023456789012']
],
[
'012345678901.dkr.ecr.eu-west-3.amazonaws.com',
'012345678901,012345678910,023456789012',
['012345678901', '012345678910', '023456789012']
],
[
'390948362332.dkr.ecr.cn-northwest-1.amazonaws.com.cn',
'012345678910,023456789012',
['390948362332', '012345678910', '023456789012']
],
['public.ecr.aws', undefined, []]
])('given registry %p', async (registry, accountIDsEnv, expected) => {
if (accountIDsEnv) {
process.env.AWS_ACCOUNT_IDS = accountIDsEnv;
}
expect(await aws.getAccountIDs(registry)).toEqual(expected);
});
});
75 changes: 55 additions & 20 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 44 additions & 10 deletions src/aws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,40 @@ import * as semver from 'semver';
import * as io from '@actions/io';
import * as execm from './exec';

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

export const isECR = (registry: string): boolean => {
return ecrRegistryRegex.test(registry) || isPubECR(registry);
};

export const isPubECR = async (registry: string): Promise<boolean> => {
export const isPubECR = (registry: string): boolean => {
return registry === 'public.ecr.aws';
};

export const getRegion = async (registry: string): Promise<string> => {
if (await isPubECR(registry)) {
export const getRegion = (registry: string): string => {
if (isPubECR(registry)) {
return process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
}
return registry.substring(registry.indexOf('ecr.') + 4, registry.indexOf('.amazonaws'));
const matches = registry.match(ecrRegistryRegex);
if (!matches) {
return '';
}
return matches[3];
};

export const getAccountIDs = (registry: string): string[] => {
if (isPubECR(registry)) {
return [];
}
const matches = registry.match(ecrRegistryRegex);
if (!matches) {
return [];
}
let accountIDs: Array<string> = [matches[2]];
if (process.env.AWS_ACCOUNT_IDS) {
accountIDs.push(...process.env.AWS_ACCOUNT_IDS.split(','));
}
return accountIDs.filter((item, index) => accountIDs.indexOf(item) === index);
};

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

export const getDockerLoginCmd = async (cliVersion: string, registry: string, region: string): Promise<string> => {
export const getDockerLoginCmds = async (
cliVersion: string,
registry: string,
region: string,
accountIDs: string[]
): Promise<string[]> => {
let ecrCmd = (await isPubECR(registry)) ? 'ecr-public' : 'ecr';
if (semver.satisfies(cliVersion, '>=2.0.0')) {
return execCLI([ecrCmd, 'get-login-password', '--region', region]).then(pwd => {
return `docker login --username AWS --password ${pwd} ${registry}`;
return [`docker login --username AWS --password ${pwd} ${registry}`];
});
} else {
return execCLI([ecrCmd, 'get-login', '--region', region, '--no-include-email']).then(dockerLoginCmd => {
return dockerLoginCmd;
return execCLI([
ecrCmd,
'get-login',
'--region',
region,
'--registry-ids',
accountIDs.join(' '),
'--no-include-email'
]).then(dockerLoginCmds => {
return dockerLoginCmds.trim().split(`\n`);
});
}
};
19 changes: 13 additions & 6 deletions src/docker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export async function loginECR(registry: string, username: string, password: str
const cliPath = await aws.getCLI();
const cliVersion = await aws.getCLIVersion();
const region = await aws.getRegion(registry);
const accountIDs = await aws.getAccountIDs(registry);

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

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

core.info(`🔑 Logging into ${registry}...`);
execm.exec(loginCmd, [], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
}
core.info('🎉 Login Succeeded!');
loginCmds.forEach((loginCmd, index) => {
execm.exec(loginCmd, [], true).then(res => {
if (res.stderr != '' && !res.success) {
throw new Error(res.stderr);
}
if (loginCmds.length > 1) {
core.info(`🎉 Login Succeeded! (${index}/${loginCmds.length})`);
} else {
core.info('🎉 Login Succeeded!');
}
});
});
}