diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index db4ac388..5dd8d7c4 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -57,3 +57,16 @@ jobs: if [[ "${{steps.output-set.outputs.result}}" != "@actions/core" ]]; then exit 1 fi + + test-retry-after: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Test retry with after + uses: ./ + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + retries: 3 + retry-after: 999 + script: | + console.log('Exec the plugin', context.repo.owner); diff --git a/.gitignore b/.gitignore index 2a275926..3712a231 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /node_modules/ -!/.vscode/ \ No newline at end of file +!/.vscode/ +.DS_Store +.env \ No newline at end of file diff --git a/README.md b/README.md index 07a337f1..eaf6ab7b 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,23 @@ By default, requests made with the `github` instance will not be retried. You ca In this example, request failures from `github.rest.issues.get()` will be retried up to 3 times. +You can also configure the delay (delayInSeconds) before retrying via the `retry-after` option: + +```yaml +- uses: actions/github-script@v6 + id: my-script + with: + result-encoding: string + retries: 3 + retry-after: 500 + script: | + github.rest.issues.get({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + }) +``` + You can also configure which status codes should be exempt from retries via the `retry-exempt-status-codes` option: ```yaml diff --git a/__test__/get-retry-options.test.ts b/__test__/get-retry-options.test.ts index 13fd3f7b..a338c5a2 100644 --- a/__test__/get-retry-options.test.ts +++ b/__test__/get-retry-options.test.ts @@ -1,17 +1,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {getRetryOptions} from '../src/retry-options' +import { getRetryOptions } from '../src/retry-options' describe('getRequestOptions', () => { test('retries disabled if retries == 0', async () => { const [retryOptions, requestOptions] = getRetryOptions( 0, [400, 500, 502], + 321, [] ) expect(retryOptions.enabled).toBe(false) expect(retryOptions.doNotRetry).toBeFalsy() + expect(retryOptions.delay).toBeFalsy() expect(requestOptions?.retries).toBeFalsy() }) @@ -20,39 +22,59 @@ describe('getRequestOptions', () => { const [retryOptions, requestOptions] = getRetryOptions( 1, [400, 500, 502], + 321, [] ) expect(retryOptions.enabled).toBe(true) expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) + expect(retryOptions.delay).toEqual(321) expect(requestOptions?.retries).toEqual(1) }) - test('properties set if retries > 0', async () => { + test('delay should be undefined if retryAfter < 0', async () => { + const [retryOptions, requestOptions] = getRetryOptions( + 1, + [400, 500, 502], + -1, + [] + ) + + expect(retryOptions.enabled).toBe(true) + expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) + expect(retryOptions.delay).toBeUndefined() + + expect(requestOptions?.retries).toEqual(1) + }) + + test('delay should be undefined if retryAfter = 0', async () => { const [retryOptions, requestOptions] = getRetryOptions( 1, [400, 500, 502], + 0, [] ) expect(retryOptions.enabled).toBe(true) expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) + expect(retryOptions.delay).toBeUndefined() expect(requestOptions?.retries).toEqual(1) }) test('retryOptions.doNotRetry not set if exemptStatusCodes isEmpty', async () => { - const [retryOptions, requestOptions] = getRetryOptions(1, [], []) + const [retryOptions, requestOptions] = getRetryOptions(1, [], 321, []) expect(retryOptions.enabled).toBe(true) expect(retryOptions.doNotRetry).toBeUndefined() + expect(retryOptions.delay).toBe(321) expect(requestOptions?.retries).toEqual(1) }) test('requestOptions does not override defaults from @actions/github', async () => { - const [retryOptions, requestOptions] = getRetryOptions(1, [], { + const [retryOptions, requestOptions] = getRetryOptions(1, [], 321, { request: { agent: 'default-user-agent' }, @@ -60,6 +82,7 @@ describe('getRequestOptions', () => { }) expect(retryOptions.enabled).toBe(true) + expect(retryOptions.delay).toBe(321) expect(retryOptions.doNotRetry).toBeUndefined() expect(requestOptions?.retries).toEqual(1) diff --git a/action.yml b/action.yml index 58a010c4..7d715131 100644 --- a/action.yml +++ b/action.yml @@ -26,6 +26,9 @@ inputs: retries: description: The number of times to retry a request default: "0" + retry-after: + description: The number of seconds after which the retry attempt should be made. No effect unless `retries` is set + default: "1000" retry-exempt-status-codes: description: A comma separated list of status codes that will NOT be retried e.g. "400,500". No effect unless `retries` is set default: 400,401,403,404,422 # from https://github.com/octokit/plugin-retry.js/blob/9a2443746c350b3beedec35cf26e197ea318a261/src/index.ts#L14 diff --git a/src/main.ts b/src/main.ts index 307f7984..766b293a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,15 +1,15 @@ import * as core from '@actions/core' import * as exec from '@actions/exec' -import {context, getOctokit} from '@actions/github' -import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' +import { context, getOctokit } from '@actions/github' +import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils' import * as glob from '@actions/glob' import * as io from '@actions/io' -import {retry} from '@octokit/plugin-retry' -import {RequestRequestOptions} from '@octokit/types' -import {callAsyncFunction} from './async-function' -import {getRetryOptions, parseNumberArray, RetryOptions} from './retry-options' -import {wrapRequire} from './wrap-require' +import { retry } from '@octokit/plugin-retry' +import { RequestRequestOptions } from '@octokit/types' import fetch from 'node-fetch' +import { callAsyncFunction } from './async-function' +import { getRetryOptions, parseNumberArray, RetryOptions } from './retry-options' +import { wrapRequire } from './wrap-require' process.on('unhandledRejection', handleError) main().catch(handleError) @@ -23,17 +23,19 @@ type Options = { } async function main(): Promise { - const token = core.getInput('github-token', {required: true}) + const token = core.getInput('github-token', { required: true }) const debug = core.getInput('debug') const userAgent = core.getInput('user-agent') const previews = core.getInput('previews') const retries = parseInt(core.getInput('retries')) + const retryAfter = parseInt(core.getInput('retry-after')) const exemptStatusCodes = parseNumberArray( core.getInput('retry-exempt-status-codes') ) const [retryOpts, requestOpts] = getRetryOptions( retries, exemptStatusCodes, + retryAfter, defaultGitHubOptions ) @@ -45,7 +47,7 @@ async function main(): Promise { if (requestOpts) opts.request = requestOpts const github = getOctokit(token, opts, retry) - const script = core.getInput('script', {required: true}) + const script = core.getInput('script', { required: true }) // Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors. const result = await callAsyncFunction( diff --git a/src/retry-options.ts b/src/retry-options.ts index 6602d13e..46a3c93f 100644 --- a/src/retry-options.ts +++ b/src/retry-options.ts @@ -1,25 +1,31 @@ import * as core from '@actions/core' -import {OctokitOptions} from '@octokit/core/dist-types/types' -import {RequestRequestOptions} from '@octokit/types' +import { OctokitOptions } from '@octokit/core/dist-types/types' +import { RequestRequestOptions } from '@octokit/types' export type RetryOptions = { doNotRetry?: number[] enabled?: boolean + retryAfterBaseValue?: number } export function getRetryOptions( retries: number, exemptStatusCodes: number[], + retryAfter: number, defaultOptions: OctokitOptions ): [RetryOptions, RequestRequestOptions | undefined] { if (retries <= 0) { - return [{enabled: false}, defaultOptions.request] + return [{ enabled: false }, defaultOptions.request] } const retryOptions: RetryOptions = { enabled: true } + if (retryAfter > 0) { + retryOptions.retryAfterBaseValue = retryAfter + } + if (exemptStatusCodes.length > 0) { retryOptions.doNotRetry = exemptStatusCodes } @@ -33,10 +39,9 @@ export function getRetryOptions( } core.debug( - `GitHub client configured with: (retries: ${ - requestOptions.retries - }, retry-exempt-status-code: ${ - retryOptions?.doNotRetry ?? 'octokit default: [400, 401, 403, 404, 422]' + `GitHub client configured with: (retries: ${requestOptions.retries + }, retryAfterBaseValue: ${retryOptions.retryAfterBaseValue ?? 'octokit default: 1000' + } retry-exempt-status-code: ${retryOptions?.doNotRetry ?? 'octokit default: [400, 401, 403, 404, 422]' })` )