diff --git a/action.yml b/action.yml index 39db0ad9..6669b377 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,7 @@ inputs: skip-logout: description: 'Whether to skip explicit logout of the registries during post-job cleanup. Exists for backward compatibility on self-hosted runners. Not recommended.' required: false + default: 'false' outputs: registry: description: 'The URI of the ECR registry i.e. aws_account_id.dkr.ecr.region.amazonaws.com. If multiple registries are provided as inputs, this output will not be set.' diff --git a/cleanup.js b/cleanup.js index 628605ae..67848f7c 100644 --- a/cleanup.js +++ b/cleanup.js @@ -7,19 +7,21 @@ const exec = require('@actions/exec'); */ async function cleanup() { - try { + try { const registriesState = core.getState('registries'); + if (registriesState) { const registries = registriesState.split(','); const failedLogouts = []; + // Logout of each registry for (const registry of registries) { - core.debug(`Logging out registry ${registry}`); + core.debug(`Logging out of registry ${registry}`); // Execute the docker logout command let doLogoutStdout = ''; let doLogoutStderr = ''; - const exitCode = await exec.exec('docker', ["logout", registry], { + const exitCode = await exec.exec('docker', ['logout', registry], { silent: true, ignoreReturnCode: true, listeners: { @@ -31,8 +33,7 @@ async function cleanup() { } } }); - - if (exitCode != 0) { + if (exitCode !== 0) { core.debug(doLogoutStdout); core.error(`Could not logout registry ${registry}: ${doLogoutStderr}`); failedLogouts.push(registry); diff --git a/cleanup.test.js b/cleanup.test.js index 95e185e6..78b9dbc8 100644 --- a/cleanup.test.js +++ b/cleanup.test.js @@ -7,94 +7,94 @@ jest.mock('@actions/exec'); describe('Logout from ECR', () => { - beforeEach(() => { - jest.clearAllMocks(); - - core.getState.mockReturnValue( - '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); - exec.exec.mockReturnValue(0); - }); - - test('logs out docker client for registries in action state', async () => { - await cleanup(); - - expect(core.getState).toHaveBeenCalledWith('registries'); - - expect(exec.exec).toHaveBeenCalledTimes(2); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['logout', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(exec.exec).toHaveBeenNthCalledWith(2, - 'docker', - ['logout', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - - expect(core.setFailed).toHaveBeenCalledTimes(0); - }); - - test('handles zero registries', async () => { - core.getState.mockReturnValue(''); - - await cleanup(); - - expect(core.getState).toHaveBeenCalledWith('registries'); - - expect(exec.exec).toHaveBeenCalledTimes(0); - expect(core.setFailed).toHaveBeenCalledTimes(0); - }); - - test('error is caught by core.setFailed for failed docker logout', async () => { - exec.exec.mockReturnValue(1); - - await cleanup(); - - expect(core.setFailed).toBeCalled(); - }); - - test('continues to attempt logouts after a failed logout', async () => { - core.getState.mockReturnValue( - '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com,222222222222.dkr.ecr.aws-region-1.amazonaws.com'); - exec.exec - .mockImplementationOnce((commandLine, args, options) => { - options.listeners.stdout("stdout of "); - options.listeners.stdout("registry 1"); - options.listeners.stderr("stderr of "); - options.listeners.stderr("registry 1"); - return(1); - }) - .mockImplementationOnce((commandLine, args, options) => { - options.listeners.stdout("stdout of "); - options.listeners.stdout("registry 2"); - options.listeners.stderr("stderr of "); - options.listeners.stderr("registry 2"); - return(1); - }) - .mockReturnValueOnce(0); - - await cleanup(); - - expect(core.getState).toHaveBeenCalledWith('registries'); - - expect(exec.exec).toHaveBeenCalledTimes(3); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['logout', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(exec.exec).toHaveBeenNthCalledWith(2, - 'docker', - ['logout', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(exec.exec).toHaveBeenNthCalledWith(3, - 'docker', - ['logout', '222222222222.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - - expect(core.error).toHaveBeenCalledTimes(2); - expect(core.error).toHaveBeenNthCalledWith(1, 'Could not logout registry 123456789012.dkr.ecr.aws-region-1.amazonaws.com: stderr of registry 1'); - expect(core.error).toHaveBeenNthCalledWith(2, 'Could not logout registry 111111111111.dkr.ecr.aws-region-1.amazonaws.com: stderr of registry 2'); - - expect(core.setFailed).toHaveBeenCalledTimes(1); - expect(core.setFailed).toHaveBeenCalledWith('Failed to logout: 123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); - }); + beforeEach(() => { + jest.clearAllMocks(); + + core.getState.mockReturnValue( + '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + exec.exec.mockReturnValue(0); + }); + + test('logs out docker client for registries in action state', async () => { + await cleanup(); + + expect(core.getState).toHaveBeenCalledWith('registries'); + + expect(exec.exec).toHaveBeenCalledTimes(2); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['logout', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(exec.exec).toHaveBeenNthCalledWith(2, + 'docker', + ['logout', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + + expect(core.setFailed).toHaveBeenCalledTimes(0); + }); + + test('handles zero registries', async () => { + core.getState.mockReturnValue(''); + + await cleanup(); + + expect(core.getState).toHaveBeenCalledWith('registries'); + + expect(exec.exec).toHaveBeenCalledTimes(0); + expect(core.setFailed).toHaveBeenCalledTimes(0); + }); + + test('error is caught by core.setFailed for failed docker logout', async () => { + exec.exec.mockReturnValue(1); + + await cleanup(); + + expect(core.setFailed).toBeCalled(); + }); + + test('continues to attempt logouts after a failed logout', async () => { + core.getState.mockReturnValue( + '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com,222222222222.dkr.ecr.aws-region-1.amazonaws.com'); + exec.exec + .mockImplementationOnce((commandLine, args, options) => { + options.listeners.stdout('stdout of '); + options.listeners.stdout('registry 1'); + options.listeners.stderr('stderr of '); + options.listeners.stderr('registry 1'); + return(1); + }) + .mockImplementationOnce((commandLine, args, options) => { + options.listeners.stdout('stdout of '); + options.listeners.stdout('registry 2'); + options.listeners.stderr('stderr of '); + options.listeners.stderr('registry 2'); + return(1); + }) + .mockReturnValueOnce(0); + + await cleanup(); + + expect(core.getState).toHaveBeenCalledWith('registries'); + + expect(exec.exec).toHaveBeenCalledTimes(3); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['logout', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(exec.exec).toHaveBeenNthCalledWith(2, + 'docker', + ['logout', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(exec.exec).toHaveBeenNthCalledWith(3, + 'docker', + ['logout', '222222222222.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + + expect(core.error).toHaveBeenCalledTimes(2); + expect(core.error).toHaveBeenNthCalledWith(1, 'Could not logout registry 123456789012.dkr.ecr.aws-region-1.amazonaws.com: stderr of registry 1'); + expect(core.error).toHaveBeenNthCalledWith(2, 'Could not logout registry 111111111111.dkr.ecr.aws-region-1.amazonaws.com: stderr of registry 2'); + + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(core.setFailed).toHaveBeenCalledWith('Failed to logout: 123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + }); }); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index fade4401..d854cb8d 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -1423,19 +1423,21 @@ const exec = __webpack_require__(986); */ async function cleanup() { - try { + try { const registriesState = core.getState('registries'); + if (registriesState) { const registries = registriesState.split(','); const failedLogouts = []; + // Logout of each registry for (const registry of registries) { - core.debug(`Logging out registry ${registry}`); + core.debug(`Logging out of registry ${registry}`); // Execute the docker logout command let doLogoutStdout = ''; let doLogoutStderr = ''; - const exitCode = await exec.exec('docker', ["logout", registry], { + const exitCode = await exec.exec('docker', ['logout', registry], { silent: true, ignoreReturnCode: true, listeners: { @@ -1447,8 +1449,7 @@ async function cleanup() { } } }); - - if (exitCode != 0) { + if (exitCode !== 0) { core.debug(doLogoutStdout); core.error(`Could not logout registry ${registry}: ${doLogoutStderr}`); failedLogouts.push(registry); diff --git a/dist/index.js b/dist/index.js index d95d2d0d..9bcaabfe 100644 --- a/dist/index.js +++ b/dist/index.js @@ -523,17 +523,18 @@ const exec = __webpack_require__(4986); const aws = __webpack_require__(9350); function replaceSpecialCharacters(registryUri) { - return registryUri.replace(/[^a-zA-Z0-9_]+/g, '_') + return registryUri.replace(/[^a-zA-Z0-9_]+/g, '_'); } async function run() { + // Get inputs + const skipLogout = core.getInput('skip-logout', { required: false }) === 'true'; + const registries = core.getInput('registries', { required: false }); + const registryUriState = []; - const skipLogout = core.getInput('skip-logout', { required: false }); try { - const registries = core.getInput('registries', { required: false }); - - // Get the ECR authorization token + // Get the ECR authorization token(s) const ecr = new aws.ECR({ customUserAgent: 'amazon-ecr-login-for-github-actions' }); @@ -551,14 +552,17 @@ async function run() { throw new Error('Could not retrieve an authorization token from Amazon ECR'); } + // Login to each registry for (const authData of authTokenResponse.authorizationData) { const authToken = Buffer.from(authData.authorizationToken, 'base64').toString('utf-8'); const creds = authToken.split(':', 2); const proxyEndpoint = authData.proxyEndpoint; const registryUri = proxyEndpoint.replace(/^https?:\/\//,''); - if (authTokenResponse.authorizationData.length == 1) { - // output the registry URI if this action is doing a single registry login + core.debug(`Logging in to registry ${registryUri}`); + + // output the registry URI if this action is doing a single registry login + if (authTokenResponse.authorizationData.length === 1) { core.setOutput('registry', registryUri); } @@ -577,15 +581,14 @@ async function run() { } } }); - - if (exitCode != 0) { + if (exitCode !== 0) { core.debug(doLoginStdout); - throw new Error('Could not login: ' + doLoginStderr); + throw new Error(`Could not login to ${proxyEndpoint}: ${doLoginStderr}`); } + // Output docker username and password const secretSuffix = replaceSpecialCharacters(registryUri) - core.setSecret(creds[0]) - core.setSecret(creds[1]) + core.setSecret(creds[1]); core.setOutput(`docker_username_${secretSuffix}`, creds[0]); core.setOutput(`docker_password_${secretSuffix}`, creds[1]); @@ -599,7 +602,7 @@ async function run() { // Pass the logged-in registry URIs to the post action for logout if (registryUriState.length) { if (!skipLogout) { - core.saveState('registries', registryUriState.join()); + core.saveState('registries', registryUriState.join()); } core.debug(`'skip-logout' is ${skipLogout} for ${registryUriState.length} registries.`); } @@ -612,7 +615,7 @@ module.exports = { /* istanbul ignore next */ if (require.main === require.cache[eval('__filename')]) { - run(); + run(); } diff --git a/index.js b/index.js index 1321ac41..a43315c4 100644 --- a/index.js +++ b/index.js @@ -3,17 +3,18 @@ const exec = require('@actions/exec'); const aws = require('aws-sdk'); function replaceSpecialCharacters(registryUri) { - return registryUri.replace(/[^a-zA-Z0-9_]+/g, '_') + return registryUri.replace(/[^a-zA-Z0-9_]+/g, '_'); } async function run() { + // Get inputs + const skipLogout = core.getInput('skip-logout', { required: false }) === 'true'; + const registries = core.getInput('registries', { required: false }); + const registryUriState = []; - const skipLogout = core.getInput('skip-logout', { required: false }); try { - const registries = core.getInput('registries', { required: false }); - - // Get the ECR authorization token + // Get the ECR authorization token(s) const ecr = new aws.ECR({ customUserAgent: 'amazon-ecr-login-for-github-actions' }); @@ -31,14 +32,17 @@ async function run() { throw new Error('Could not retrieve an authorization token from Amazon ECR'); } + // Login to each registry for (const authData of authTokenResponse.authorizationData) { const authToken = Buffer.from(authData.authorizationToken, 'base64').toString('utf-8'); const creds = authToken.split(':', 2); const proxyEndpoint = authData.proxyEndpoint; const registryUri = proxyEndpoint.replace(/^https?:\/\//,''); - if (authTokenResponse.authorizationData.length == 1) { - // output the registry URI if this action is doing a single registry login + core.debug(`Logging in to registry ${registryUri}`); + + // output the registry URI if this action is doing a single registry login + if (authTokenResponse.authorizationData.length === 1) { core.setOutput('registry', registryUri); } @@ -57,15 +61,14 @@ async function run() { } } }); - - if (exitCode != 0) { + if (exitCode !== 0) { core.debug(doLoginStdout); - throw new Error('Could not login: ' + doLoginStderr); + throw new Error(`Could not login to ${proxyEndpoint}: ${doLoginStderr}`); } + // Output docker username and password const secretSuffix = replaceSpecialCharacters(registryUri) - core.setSecret(creds[0]) - core.setSecret(creds[1]) + core.setSecret(creds[1]); core.setOutput(`docker_username_${secretSuffix}`, creds[0]); core.setOutput(`docker_password_${secretSuffix}`, creds[1]); @@ -79,7 +82,7 @@ async function run() { // Pass the logged-in registry URIs to the post action for logout if (registryUriState.length) { if (!skipLogout) { - core.saveState('registries', registryUriState.join()); + core.saveState('registries', registryUriState.join()); } core.debug(`'skip-logout' is ${skipLogout} for ${registryUriState.length} registries.`); } @@ -92,5 +95,5 @@ module.exports = { /* istanbul ignore next */ if (require.main === module) { - run(); + run(); } diff --git a/index.test.js b/index.test.js index 31da564e..283ecbe4 100644 --- a/index.test.js +++ b/index.test.js @@ -6,365 +6,370 @@ jest.mock('@actions/core'); jest.mock('@actions/exec'); function mockGetInput(requestResponse) { - return function (name, options) { // eslint-disable-line no-unused-vars - return requestResponse[name] - } + return function (name, options) { // eslint-disable-line no-unused-vars + return requestResponse[name] + } } const DEFAULT_INPUTS = { - 'registries': undefined, - 'skip-logout': undefined + 'registries': undefined, + 'skip-logout': undefined }; const mockEcrGetAuthToken = jest.fn(); jest.mock('aws-sdk', () => { - return { - ECR: jest.fn(() => ({ - getAuthorizationToken: mockEcrGetAuthToken - })) - }; + return { + ECR: jest.fn(() => ({ + getAuthorizationToken: mockEcrGetAuthToken + })) + }; }); describe('Login to ECR', () => { - - beforeEach(() => { - jest.clearAllMocks(); - - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(DEFAULT_INPUTS)); - - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('hello:world').toString('base64'), - proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - exec.exec.mockReturnValue(0); + beforeEach(() => { + jest.clearAllMocks(); + + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(DEFAULT_INPUTS)); + + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('hello:world').toString('base64'), + proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); - test('gets auth token from ECR and logins the Docker client for the default registry', async () => { - await run(); - expect(mockEcrGetAuthToken).toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(core.saveState).toHaveBeenCalledTimes(1); - expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + exec.exec.mockReturnValue(0); + }); + + test('gets auth token from ECR and logins the Docker client for the default registry', async () => { + await run(); + + expect(mockEcrGetAuthToken).toHaveBeenCalled(); + expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(core.setSecret).toHaveBeenCalledTimes(1); + expect(core.setOutput).toHaveBeenCalledTimes(3); + expect(core.saveState).toHaveBeenCalledTimes(1); + expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + }); + + test('gets auth token from ECR and logins the Docker client for each provided registry', async () => { + const mockInputs = {'registries' : '123456789012,111111111111'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('hello:world').toString('base64'), + proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' + }, + { + authorizationToken: Buffer.from('foo:bar').toString('base64'), + proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); - test('gets auth token from ECR and logins the Docker client for each provided registry', async () => { - const mockInputs = {'registries' : '123456789012,111111111111'}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('hello:world').toString('base64'), - proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' - }, - { - authorizationToken: Buffer.from('foo:bar').toString('base64'), - proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - await run(); - - expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ - registryIds: ['123456789012','111111111111'] - }); - expect(core.setOutput).toHaveBeenCalledTimes(4); - expect(exec.exec).toHaveBeenCalledTimes(2); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(exec.exec).toHaveBeenNthCalledWith(2, - 'docker', - ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(core.saveState).toHaveBeenCalledTimes(1); - expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); - }); + await run(); - test(`throws error when getAuthorizationToken does return an empty authorization data`, async () => { - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({authorizationData: []}); - } - }; - }); - - await run(); - expect(mockEcrGetAuthToken).toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenCalledTimes(0); - expect(exec.exec).toHaveBeenCalledTimes(0); - expect(core.saveState).toHaveBeenCalledTimes(0); - expect(core.setFailed).toHaveBeenCalledTimes(1); - expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); + expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ + registryIds: ['123456789012','111111111111'] }); - - test(`throws error when getAuthorizationToken does not contain authorization data`, async () => { - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({foo: "bar"}); - } - }; - }); - - await run(); - expect(mockEcrGetAuthToken).toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenCalledTimes(0); - expect(exec.exec).toHaveBeenCalledTimes(0); - expect(core.saveState).toHaveBeenCalledTimes(0); - expect(core.setFailed).toHaveBeenCalledTimes(1); - expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); + expect(exec.exec).toHaveBeenCalledTimes(2); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(exec.exec).toHaveBeenNthCalledWith(2, + 'docker', + ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(core.setSecret).toHaveBeenCalledTimes(2); + expect(core.setOutput).toHaveBeenCalledTimes(4); + expect(core.saveState).toHaveBeenCalledTimes(1); + expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com,111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + }); + + test('outputs the registry ID if a single registry is provided in the input', async () => { + const mockInputs = {'registries' : '111111111111'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('foo:bar').toString('base64'), + proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); - test(`throws error when getAuthorizationToken does not return data`, async () => { - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ECR.html#getAuthorizationToken-property - // data (Object) — the de-serialized data returned from the request. Set to null if a request error occurs. - return Promise.resolve(null); - } - }; - }); - - await run(); - expect(mockEcrGetAuthToken).toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenCalledTimes(0); - expect(exec.exec).toHaveBeenCalledTimes(0); - expect(core.saveState).toHaveBeenCalledTimes(0); - expect(core.setFailed).toHaveBeenCalledTimes(1); - expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); - }); + await run(); - test('outputs the registry ID if a single registry is provided in the input', async () => { - const mockInputs = {'registries' : '111111111111'}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('foo:bar').toString('base64'), - proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - await run(); - - expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ - registryIds: ['111111111111'] - }); - expect(core.setOutput).toHaveBeenCalledTimes(3); - expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'); - expect(exec.exec).toHaveBeenCalledTimes(1); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(core.saveState).toHaveBeenCalledTimes(1); - expect(core.saveState).toHaveBeenCalledWith('registries', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ + registryIds: ['111111111111'] + }); + expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + expect(exec.exec).toHaveBeenCalledTimes(1); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(core.setSecret).toHaveBeenCalledTimes(1); + expect(core.setOutput).toHaveBeenCalledTimes(3); + expect(core.saveState).toHaveBeenCalledTimes(1); + expect(core.saveState).toHaveBeenCalledWith('registries', '111111111111.dkr.ecr.aws-region-1.amazonaws.com'); + }); + + test('error is caught by core.setFailed for failed docker login', async () => { + exec.exec.mockReturnValue(1); + + await run(); + + expect(core.setFailed).toBeCalled(); + expect(core.setOutput).toHaveBeenCalledWith('registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + expect(core.saveState).toHaveBeenCalledTimes(0); + }); + + test('logged-in registries are saved as state even if the action fails', async () => { + exec.exec + .mockImplementation((commandLine, args, options) => { + options.listeners.stdout('Hello World '); + options.listeners.stdout('on stdout\n'); + options.listeners.stderr('Some fancy error '); + options.listeners.stderr('from docker login stderr'); + return(1); + }) + .mockReturnValueOnce(0); + + const mockInputs = {'registries' : '123456789012,111111111111'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('hello:world').toString('base64'), + proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' + }, + { + authorizationToken: Buffer.from('foo:bar').toString('base64'), + proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); - test('error is caught by core.setFailed for failed docker login', async () => { - exec.exec.mockReturnValue(1); - - await run(); + await run(); - expect(core.setFailed).toBeCalled(); - expect(core.setOutput).toHaveBeenCalledWith('registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); - expect(core.saveState).toHaveBeenCalledTimes(0); + expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ + registryIds: ['123456789012','111111111111'] }); - - test('logged-in registries are saved as state even if the action fails', async () => { - exec.exec - .mockImplementation((commandLine, args, options) => { - options.listeners.stdout("Hello"); - options.listeners.stdout(" World"); - options.listeners.stdout(" on stdout\n"); - options.listeners.stderr("Some fancy error"); - options.listeners.stderr(" from docker login stderr"); - return(1); - }) - .mockReturnValueOnce(0); - - const mockInputs = {'registries' : '123456789012,111111111111'}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('hello:world').toString('base64'), - proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' - }, - { - authorizationToken: Buffer.from('foo:bar').toString('base64'), - proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - await run(); - - expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ - registryIds: ['123456789012','111111111111'] - }); - expect(core.setOutput).toHaveBeenCalledTimes(2); - expect(exec.exec).toHaveBeenCalledTimes(2); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(exec.exec).toHaveBeenNthCalledWith(2, - 'docker', - ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - - expect(core.setFailed).toBeCalled(); - expect(core.setFailed).toHaveBeenCalledWith("Could not login: Some fancy error from docker login stderr"); - expect(core.saveState).toHaveBeenCalledTimes(1); - expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + expect(core.setOutput).toHaveBeenCalledTimes(2); + expect(exec.exec).toHaveBeenCalledTimes(2); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(exec.exec).toHaveBeenNthCalledWith(2, + 'docker', + ['login', '-u', 'foo', '-p', 'bar', 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + + expect(core.setFailed).toBeCalled(); + expect(core.setFailed).toHaveBeenCalledWith('Could not login to https://111111111111.dkr.ecr.aws-region-1.amazonaws.com: Some fancy error from docker login stderr'); + expect(core.saveState).toHaveBeenCalledTimes(1); + expect(core.saveState).toHaveBeenCalledWith('registries', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + }); + + test(`throws error when getAuthorizationToken does return an empty authorization data`, async () => { + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({authorizationData: []}); + } + }; }); - test('error is caught by core.setFailed for ECR call', async () => { - mockEcrGetAuthToken.mockImplementation(() => { - throw new Error(); - }); - - await run(); - - expect(core.setFailed).toBeCalled(); - expect(core.setOutput).toHaveBeenCalledTimes(0); - expect(core.saveState).toHaveBeenCalledTimes(0); + await run(); + + expect(mockEcrGetAuthToken).toHaveBeenCalled(); + expect(core.setOutput).toHaveBeenCalledTimes(0); + expect(exec.exec).toHaveBeenCalledTimes(0); + expect(core.saveState).toHaveBeenCalledTimes(0); + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); + }); + + test(`throws error when getAuthorizationToken does not contain authorization data`, async () => { + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({foo: 'bar'}); + } + }; }); - test('skips logout when specified and logging into default registry', async () => { - const mockInputs = {'skip-logout' : true}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - - await run(); - expect(mockEcrGetAuthToken).toHaveBeenCalled(); - expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); - expect(exec.exec).toHaveBeenNthCalledWith(1, - 'docker', - ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], - expect.anything()); - expect(core.saveState).toHaveBeenCalledTimes(0); + await run(); + expect(mockEcrGetAuthToken).toHaveBeenCalled(); + expect(core.setOutput).toHaveBeenCalledTimes(0); + expect(exec.exec).toHaveBeenCalledTimes(0); + expect(core.saveState).toHaveBeenCalledTimes(0); + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); + }); + + test(`throws error when getAuthorizationToken does not return data`, async () => { + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ECR.html#getAuthorizationToken-property + // data (Object) — the de-serialized data returned from the request. Set to null if a request error occurs. + return Promise.resolve(null); + } + }; }); - test('skips logout when specified and logging into multiple registries', async () => { - const mockInputs = {'registries' : '123456789012,111111111111', 'skip-logout' : true}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('hello:world').toString('base64'), - proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' - }, - { - authorizationToken: Buffer.from('foo:bar').toString('base64'), - proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - await run(); - - expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ - registryIds: ['123456789012','111111111111'] - }); - expect(core.setOutput).toHaveBeenCalledTimes(4); - expect(exec.exec).toHaveBeenCalledTimes(2); - expect(core.saveState).toHaveBeenCalledTimes(0); + await run(); + expect(mockEcrGetAuthToken).toHaveBeenCalled(); + expect(core.setOutput).toHaveBeenCalledTimes(0); + expect(exec.exec).toHaveBeenCalledTimes(0); + expect(core.saveState).toHaveBeenCalledTimes(0); + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(core.setFailed).toHaveBeenCalledWith('Could not retrieve an authorization token from Amazon ECR'); + }); + + test('error is caught by core.setFailed for ECR call', async () => { + mockEcrGetAuthToken.mockImplementation(() => { + throw new Error(); }); - test('replaces special characters', () => { - expect(replaceSpecialCharacters('111111111111.dkr.ecr.aws-region-1.amazonaws.com')).toBe('111111111111_dkr_ecr_aws_region_1_amazonaws_com') - expect(replaceSpecialCharacters('229236603350.dkr.ecr.us-east-1.amazonaws.com')).toBe('229236603350_dkr_ecr_us_east_1_amazonaws_com') + await run(); + + expect(core.setFailed).toBeCalled(); + expect(core.setOutput).toHaveBeenCalledTimes(0); + expect(core.saveState).toHaveBeenCalledTimes(0); + }); + + test('skips logout when specified and logging into default registry', async () => { + const mockInputs = {'skip-logout' : 'true'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + + await run(); + expect(mockEcrGetAuthToken).toHaveBeenCalled(); + expect(core.setOutput).toHaveBeenNthCalledWith(1, 'registry', '123456789012.dkr.ecr.aws-region-1.amazonaws.com'); + expect(exec.exec).toHaveBeenNthCalledWith(1, + 'docker', + ['login', '-u', 'hello', '-p', 'world', 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com'], + expect.anything()); + expect(core.saveState).toHaveBeenCalledTimes(0); + }); + + test('skips logout when specified and logging into multiple registries', async () => { + const mockInputs = {'registries' : '123456789012,111111111111', 'skip-logout' : 'true'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('hello:world').toString('base64'), + proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' + }, + { + authorizationToken: Buffer.from('foo:bar').toString('base64'), + proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); - test('sets the Actions outputs to the docker credentials', async () => { - const mockInputs = {'registries' : '123456789012,111111111111', 'skip-logout' : true}; - core.getInput = jest - .fn() - .mockImplementation(mockGetInput(mockInputs)); - mockEcrGetAuthToken.mockImplementation(() => { - return { - promise() { - return Promise.resolve({ - authorizationData: [ - { - authorizationToken: Buffer.from('hello:world').toString('base64'), - proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' - }, - { - authorizationToken: Buffer.from('foo:bar').toString('base64'), - proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' - } - ] - }); - } - }; - }); - - await run(); - expect(core.setSecret).toHaveBeenNthCalledWith(1, 'hello'); - expect(core.setSecret).toHaveBeenNthCalledWith(2, 'world'); - expect(core.setSecret).toHaveBeenNthCalledWith(3, 'foo'); - expect(core.setSecret).toHaveBeenNthCalledWith(4, 'bar'); - expect(core.setOutput).toHaveBeenNthCalledWith(1, 'docker_username_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'hello'); - expect(core.setOutput).toHaveBeenNthCalledWith(2, 'docker_password_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'world'); - expect(core.setOutput).toHaveBeenNthCalledWith(3, 'docker_username_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'foo'); - expect(core.setOutput).toHaveBeenNthCalledWith(4, 'docker_password_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'bar'); + await run(); + + expect(mockEcrGetAuthToken).toHaveBeenCalledWith({ + registryIds: ['123456789012','111111111111'] + }); + expect(core.setOutput).toHaveBeenCalledTimes(4); + expect(exec.exec).toHaveBeenCalledTimes(2); + expect(core.saveState).toHaveBeenCalledTimes(0); + }); + + test('replaces special characters', () => { + expect(replaceSpecialCharacters('111111111111.dkr.ecr.aws-region-1.amazonaws.com')).toBe('111111111111_dkr_ecr_aws_region_1_amazonaws_com') + expect(replaceSpecialCharacters('229236603350.dkr.ecr.us-east-1.amazonaws.com')).toBe('229236603350_dkr_ecr_us_east_1_amazonaws_com') + }); + + test('sets the Actions outputs to the docker credentials', async () => { + const mockInputs = {'registries' : '123456789012,111111111111', 'skip-logout' : 'true'}; + core.getInput = jest + .fn() + .mockImplementation(mockGetInput(mockInputs)); + mockEcrGetAuthToken.mockImplementation(() => { + return { + promise() { + return Promise.resolve({ + authorizationData: [ + { + authorizationToken: Buffer.from('hello:world').toString('base64'), + proxyEndpoint: 'https://123456789012.dkr.ecr.aws-region-1.amazonaws.com' + }, + { + authorizationToken: Buffer.from('foo:bar').toString('base64'), + proxyEndpoint: 'https://111111111111.dkr.ecr.aws-region-1.amazonaws.com' + } + ] + }); + } + }; }); + + await run(); + + expect(core.setSecret).toHaveBeenCalledTimes(2); + expect(core.setOutput).toHaveBeenCalledTimes(4); + expect(core.setSecret).toHaveBeenNthCalledWith(1, 'world'); + expect(core.setSecret).toHaveBeenNthCalledWith(2, 'bar'); + expect(core.setOutput).toHaveBeenNthCalledWith(1, 'docker_username_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'hello'); + expect(core.setOutput).toHaveBeenNthCalledWith(2, 'docker_password_123456789012_dkr_ecr_aws_region_1_amazonaws_com', 'world'); + expect(core.setOutput).toHaveBeenNthCalledWith(3, 'docker_username_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'foo'); + expect(core.setOutput).toHaveBeenNthCalledWith(4, 'docker_password_111111111111_dkr_ecr_aws_region_1_amazonaws_com', 'bar'); + }); });