diff --git a/README.md b/README.md index 405f623..bafa457 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,18 @@ This default configuration: - Runs all Buf checks (`build`, `lint`, `format`, and `breaking`), posting a [summary comment](https://buf.build/docs/bsr/ci-cd/github-actions/#configure-summary-comment) for any pull request. - Archives corresponding [labels](https://buf.build/docs/bsr/commits-labels/#labels) in the BSR when you delete a Git branch or tag. +If BSR login intermittently fails because the registry is slow to respond, you can tune the login retry behavior: + +```yaml + - uses: bufbuild/buf-action@v1 + with: + token: ${{ secrets.BUF_TOKEN }} + login_retries: 5 + login_retry_delay_seconds: 10 +``` + +`login_retries` counts retries after the initial login attempt. `login_retries` and `login_retry_delay_seconds` default to `5` and `10` respectively. + ## Documentation For comprehensive configuration options, advanced workflows, and detailed examples, see the [Buf GitHub Action Documentation](https://buf.build/docs/bsr/ci-cd/github-actions). diff --git a/action.yml b/action.yml index 2e1262b..3307ca6 100644 --- a/action.yml +++ b/action.yml @@ -42,6 +42,16 @@ inputs: Domain for logging into the BSR, enterprise only. required: false default: "buf.build" + login_retries: + description: |- + Number of times to retry logging in to the BSR after the initial attempt fails. + required: false + default: "5" + login_retry_delay_seconds: + description: |- + Number of seconds to wait between BSR login attempts. + required: false + default: "10" github_actor: description: |- GitHub actor for API requests. diff --git a/dist/index.js b/dist/index.js index 458b4b8..52f0c44 100644 --- a/dist/index.js +++ b/dist/index.js @@ -101817,6 +101817,8 @@ function getInputs() { token: lib_core.getInput("token") || getEnv("BUF_TOKEN"), checksum: lib_core.getInput("checksum"), domain: lib_core.getInput("domain"), + login_retries: getNonNegativeIntegerInput("login_retries", 5), + login_retry_delay_seconds: getNonNegativeIntegerInput("login_retry_delay_seconds", 10), setup_only: lib_core.getBooleanInput("setup_only"), pr_comment: lib_core.getBooleanInput("pr_comment"), github_actor: lib_core.getInput("github_actor"), @@ -101865,6 +101867,18 @@ function getInputs() { } return inputs; } +function getNonNegativeIntegerInput(name, defaultValue) { + const value = lib_core.getInput(name); + if (value === "") { + return defaultValue; + } + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + lib_core.warning(`Invalid value for ${name}: ${value}. Expected a non-negative integer, using default ${defaultValue}.`); + return defaultValue; + } + return parsed; +} // getEnv returns the case insensitive value of the environment variable. // Prefers the lowercase version of the variable if it exists. function getEnv(name) { @@ -106081,15 +106095,39 @@ async function runWorkflow(bufPath, inputs, moduleNames) { } // login logs in to the Buf registry, storing credentials. async function login(bufPath, inputs) { - const { token, domain } = inputs; + const { token, domain, login_retries, login_retry_delay_seconds } = inputs; if (token == "") { lib_core.debug("Skipping login, no token provided"); return; } lib_core.debug(`Logging in to ${domain}`); - await lib_exec.exec(bufPath, ["registry", "login", domain, "--token-stdin"], { - input: Buffer.from(token + "\n"), - }); + const args = ["registry", "login", domain, "--token-stdin"]; + const input = Buffer.from(`${token}\n`); + const maxAttempts = login_retries + 1; + let lastError; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const exitCode = await lib_exec.exec(bufPath, args, { + input, + silent: attempt < maxAttempts, + }); + if (exitCode == 0) { + return; + } + lastError = new Error(`buf registry login exited with code ${exitCode}`); + } + catch (error) { + lastError = error; + } + if (attempt == maxAttempts) { + break; + } + lib_core.warning(`Login to ${domain} failed (attempt ${attempt} of ${maxAttempts}). Retrying in ${login_retry_delay_seconds} seconds.`); + await new Promise((resolve) => setTimeout(resolve, login_retry_delay_seconds * 1000)); + } + throw lastError instanceof Error + ? lastError + : new Error(`Failed to log in to ${domain}`); } // build runs the "buf build" step. async function build(bufPath, inputs) { diff --git a/dist/post/index.js b/dist/post/index.js index 19787a7..8859cd0 100644 --- a/dist/post/index.js +++ b/dist/post/index.js @@ -60470,6 +60470,8 @@ function getInputs() { token: core.getInput("token") || getEnv("BUF_TOKEN"), checksum: core.getInput("checksum"), domain: core.getInput("domain"), + login_retries: getNonNegativeIntegerInput("login_retries", 5), + login_retry_delay_seconds: getNonNegativeIntegerInput("login_retry_delay_seconds", 10), setup_only: core.getBooleanInput("setup_only"), pr_comment: core.getBooleanInput("pr_comment"), github_actor: core.getInput("github_actor"), @@ -60518,6 +60520,18 @@ function getInputs() { } return inputs; } +function getNonNegativeIntegerInput(name, defaultValue) { + const value = core.getInput(name); + if (value === "") { + return defaultValue; + } + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + core.warning(`Invalid value for ${name}: ${value}. Expected a non-negative integer, using default ${defaultValue}.`); + return defaultValue; + } + return parsed; +} // getEnv returns the case insensitive value of the environment variable. // Prefers the lowercase version of the variable if it exists. function getEnv(name) { diff --git a/src/inputs.ts b/src/inputs.ts index 6954d22..2a92de1 100644 --- a/src/inputs.ts +++ b/src/inputs.ts @@ -26,6 +26,8 @@ export interface Inputs { checksum: string; token: string; domain: string; + login_retries: number; + login_retry_delay_seconds: number; setup_only: boolean; pr_comment: boolean; github_actor: string; @@ -56,6 +58,11 @@ export function getInputs(): Inputs { token: core.getInput("token") || getEnv("BUF_TOKEN"), checksum: core.getInput("checksum"), domain: core.getInput("domain"), + login_retries: getNonNegativeIntegerInput("login_retries", 5), + login_retry_delay_seconds: getNonNegativeIntegerInput( + "login_retry_delay_seconds", + 10, + ), setup_only: core.getBooleanInput("setup_only"), pr_comment: core.getBooleanInput("pr_comment"), github_actor: core.getInput("github_actor"), @@ -107,6 +114,21 @@ export function getInputs(): Inputs { return inputs; } +function getNonNegativeIntegerInput(name: string, defaultValue: number): number { + const value = core.getInput(name); + if (value === "") { + return defaultValue; + } + const parsed = Number(value); + if (!Number.isInteger(parsed) || parsed < 0) { + core.warning( + `Invalid value for ${name}: ${value}. Expected a non-negative integer, using default ${defaultValue}.`, + ); + return defaultValue; + } + return parsed; +} + // getEnv returns the case insensitive value of the environment variable. // Prefers the lowercase version of the variable if it exists. export function getEnv(name: string): string { diff --git a/src/main.ts b/src/main.ts index f226c47..cef550e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -215,15 +215,42 @@ async function runWorkflow( // login logs in to the Buf registry, storing credentials. async function login(bufPath: string, inputs: Inputs) { - const { token, domain } = inputs; + const { token, domain, login_retries, login_retry_delay_seconds } = inputs; if (token == "") { core.debug("Skipping login, no token provided"); return; } core.debug(`Logging in to ${domain}`); - await exec.exec(bufPath, ["registry", "login", domain, "--token-stdin"], { - input: Buffer.from(token + "\n"), - }); + const args = ["registry", "login", domain, "--token-stdin"]; + const input = Buffer.from(`${token}\n`); + const maxAttempts = login_retries + 1; + let lastError: unknown; + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + const exitCode = await exec.exec(bufPath, args, { + input, + silent: attempt < maxAttempts, + }); + if (exitCode == 0) { + return; + } + lastError = new Error(`buf registry login exited with code ${exitCode}`); + } catch (error: unknown) { + lastError = error; + } + if (attempt == maxAttempts) { + break; + } + core.warning( + `Login to ${domain} failed (attempt ${attempt} of ${maxAttempts}). Retrying in ${login_retry_delay_seconds} seconds.`, + ); + await new Promise((resolve) => + setTimeout(resolve, login_retry_delay_seconds * 1000), + ); + } + throw lastError instanceof Error + ? lastError + : new Error(`Failed to log in to ${domain}`); } // build runs the "buf build" step.