diff --git a/action.yml b/action.yml index 3a0856d0..04133d8f 100644 --- a/action.yml +++ b/action.yml @@ -24,6 +24,10 @@ inputs: description: 'Log out from the Docker registry at the end of a job' default: 'true' required: false + attempts: + description: 'Number of attempts to try in case of server-side errors' + default: '1' + required: false runs: using: 'node20' diff --git a/src/context.ts b/src/context.ts index 8a381688..103287ef 100644 --- a/src/context.ts +++ b/src/context.ts @@ -6,6 +6,7 @@ export interface Inputs { password: string; ecr: string; logout: boolean; + attempts: number; } export function getInputs(): Inputs { @@ -14,6 +15,7 @@ export function getInputs(): Inputs { username: core.getInput('username'), password: core.getInput('password'), ecr: core.getInput('ecr'), - logout: core.getBooleanInput('logout') + logout: core.getBooleanInput('logout'), + attempts: Number.parseInt(core.getInput('attempts')) }; } diff --git a/src/docker.ts b/src/docker.ts index 5e2c1d5e..632cd6ad 100644 --- a/src/docker.ts +++ b/src/docker.ts @@ -3,11 +3,11 @@ import * as core from '@actions/core'; import {Docker} from '@docker/actions-toolkit/lib/docker/docker'; -export async function login(registry: string, username: string, password: string, ecr: string): Promise { +export async function login(registry: string, username: string, password: string, ecr: string, attempts: number): Promise { if (/true/i.test(ecr) || (ecr == 'auto' && aws.isECR(registry))) { await loginECR(registry, username, password); } else { - await loginStandard(registry, username, password); + await loginStandard(registry, username, password, attempts); } } @@ -21,7 +21,7 @@ export async function logout(registry: string): Promise { }); } -export async function loginStandard(registry: string, username: string, password: string): Promise { +export async function loginStandard(registry: string, username: string, password: string, attempts: number): Promise { if (!username && !password) { throw new Error('Username and password required'); } @@ -41,16 +41,29 @@ export async function loginStandard(registry: string, username: string, password } else { core.info(`Logging into Docker Hub...`); } - await Docker.getExecOutput(loginArgs, { - ignoreReturnCode: true, - silent: true, - input: Buffer.from(password) - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr.trim()); + let attempt: number = 1 + let succeeded: boolean = false + for (let attempt = 1; (attempt <= attempts) && (!succeeded); attempt++) { + await Docker.getExecOutput(loginArgs, { + ignoreReturnCode: true, + silent: true, + input: Buffer.from(password) + }).then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + let isRetriable: boolean + isRetriable = res.stderr.trim().endsWith("502 Bad Gateway") + if (!isRetriable || (attempt >= attempts) { + throw new Error(res.stderr.trim()); + } + } else { + core.info(`Login Succeeded!`); + succeeded = true; + } + }); + if ((attempt < attempts) && !succeeded) { + await new Promise(r => setTimeout(r, 10000)) } - core.info(`Login Succeeded!`); - }); + } } export async function loginECR(registry: string, username: string, password: string): Promise { diff --git a/src/main.ts b/src/main.ts index f35fa211..f8481c39 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,7 +8,7 @@ export async function main(): Promise { const input: context.Inputs = context.getInputs(); stateHelper.setRegistry(input.registry); stateHelper.setLogout(input.logout); - await docker.login(input.registry, input.username, input.password, input.ecr); + await docker.login(input.registry, input.username, input.password, input.ecr, input.attempts); } async function post(): Promise {