This commit is contained in:
Maurizio Vitale 2023-03-28 11:24:54 +00:00 committed by GitHub
commit 6796155af6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 86 additions and 21 deletions

View file

@ -57,3 +57,16 @@ jobs:
if [[ "${{steps.output-set.outputs.result}}" != "@actions/core" ]]; then if [[ "${{steps.output-set.outputs.result}}" != "@actions/core" ]]; then
exit 1 exit 1
fi 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);

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
/node_modules/ /node_modules/
!/.vscode/ !/.vscode/
.DS_Store
.env

View file

@ -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. 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: You can also configure which status codes should be exempt from retries via the `retry-exempt-status-codes` option:
```yaml ```yaml

View file

@ -1,17 +1,19 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import {getRetryOptions} from '../src/retry-options' import { getRetryOptions } from '../src/retry-options'
describe('getRequestOptions', () => { describe('getRequestOptions', () => {
test('retries disabled if retries == 0', async () => { test('retries disabled if retries == 0', async () => {
const [retryOptions, requestOptions] = getRetryOptions( const [retryOptions, requestOptions] = getRetryOptions(
0, 0,
[400, 500, 502], [400, 500, 502],
321,
[] []
) )
expect(retryOptions.enabled).toBe(false) expect(retryOptions.enabled).toBe(false)
expect(retryOptions.doNotRetry).toBeFalsy() expect(retryOptions.doNotRetry).toBeFalsy()
expect(retryOptions.delay).toBeFalsy()
expect(requestOptions?.retries).toBeFalsy() expect(requestOptions?.retries).toBeFalsy()
}) })
@ -20,39 +22,59 @@ describe('getRequestOptions', () => {
const [retryOptions, requestOptions] = getRetryOptions( const [retryOptions, requestOptions] = getRetryOptions(
1, 1,
[400, 500, 502], [400, 500, 502],
321,
[] []
) )
expect(retryOptions.enabled).toBe(true) expect(retryOptions.enabled).toBe(true)
expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) expect(retryOptions.doNotRetry).toEqual([400, 500, 502])
expect(retryOptions.delay).toEqual(321)
expect(requestOptions?.retries).toEqual(1) 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( const [retryOptions, requestOptions] = getRetryOptions(
1, 1,
[400, 500, 502], [400, 500, 502],
-1,
[] []
) )
expect(retryOptions.enabled).toBe(true) expect(retryOptions.enabled).toBe(true)
expect(retryOptions.doNotRetry).toEqual([400, 500, 502]) 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) expect(requestOptions?.retries).toEqual(1)
}) })
test('retryOptions.doNotRetry not set if exemptStatusCodes isEmpty', async () => { 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.enabled).toBe(true)
expect(retryOptions.doNotRetry).toBeUndefined() expect(retryOptions.doNotRetry).toBeUndefined()
expect(retryOptions.delay).toBe(321)
expect(requestOptions?.retries).toEqual(1) expect(requestOptions?.retries).toEqual(1)
}) })
test('requestOptions does not override defaults from @actions/github', async () => { test('requestOptions does not override defaults from @actions/github', async () => {
const [retryOptions, requestOptions] = getRetryOptions(1, [], { const [retryOptions, requestOptions] = getRetryOptions(1, [], 321, {
request: { request: {
agent: 'default-user-agent' agent: 'default-user-agent'
}, },
@ -60,6 +82,7 @@ describe('getRequestOptions', () => {
}) })
expect(retryOptions.enabled).toBe(true) expect(retryOptions.enabled).toBe(true)
expect(retryOptions.delay).toBe(321)
expect(retryOptions.doNotRetry).toBeUndefined() expect(retryOptions.doNotRetry).toBeUndefined()
expect(requestOptions?.retries).toEqual(1) expect(requestOptions?.retries).toEqual(1)

View file

@ -26,6 +26,9 @@ inputs:
retries: retries:
description: The number of times to retry a request description: The number of times to retry a request
default: "0" 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: 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 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 default: 400,401,403,404,422 # from https://github.com/octokit/plugin-retry.js/blob/9a2443746c350b3beedec35cf26e197ea318a261/src/index.ts#L14

View file

@ -1,15 +1,15 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import {context, getOctokit} from '@actions/github' import { context, getOctokit } from '@actions/github'
import {defaults as defaultGitHubOptions} from '@actions/github/lib/utils' import { defaults as defaultGitHubOptions } from '@actions/github/lib/utils'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as io from '@actions/io' import * as io from '@actions/io'
import {retry} from '@octokit/plugin-retry' import { retry } from '@octokit/plugin-retry'
import {RequestRequestOptions} from '@octokit/types' import { RequestRequestOptions } from '@octokit/types'
import {callAsyncFunction} from './async-function'
import {getRetryOptions, parseNumberArray, RetryOptions} from './retry-options'
import {wrapRequire} from './wrap-require'
import fetch from 'node-fetch' 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) process.on('unhandledRejection', handleError)
main().catch(handleError) main().catch(handleError)
@ -23,17 +23,19 @@ type Options = {
} }
async function main(): Promise<void> { async function main(): Promise<void> {
const token = core.getInput('github-token', {required: true}) const token = core.getInput('github-token', { required: true })
const debug = core.getInput('debug') const debug = core.getInput('debug')
const userAgent = core.getInput('user-agent') const userAgent = core.getInput('user-agent')
const previews = core.getInput('previews') const previews = core.getInput('previews')
const retries = parseInt(core.getInput('retries')) const retries = parseInt(core.getInput('retries'))
const retryAfter = parseInt(core.getInput('retry-after'))
const exemptStatusCodes = parseNumberArray( const exemptStatusCodes = parseNumberArray(
core.getInput('retry-exempt-status-codes') core.getInput('retry-exempt-status-codes')
) )
const [retryOpts, requestOpts] = getRetryOptions( const [retryOpts, requestOpts] = getRetryOptions(
retries, retries,
exemptStatusCodes, exemptStatusCodes,
retryAfter,
defaultGitHubOptions defaultGitHubOptions
) )
@ -45,7 +47,7 @@ async function main(): Promise<void> {
if (requestOpts) opts.request = requestOpts if (requestOpts) opts.request = requestOpts
const github = getOctokit(token, opts, retry) 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. // Using property/value shorthand on `require` (e.g. `{require}`) causes compilation errors.
const result = await callAsyncFunction( const result = await callAsyncFunction(

View file

@ -1,25 +1,31 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {OctokitOptions} from '@octokit/core/dist-types/types' import { OctokitOptions } from '@octokit/core/dist-types/types'
import {RequestRequestOptions} from '@octokit/types' import { RequestRequestOptions } from '@octokit/types'
export type RetryOptions = { export type RetryOptions = {
doNotRetry?: number[] doNotRetry?: number[]
enabled?: boolean enabled?: boolean
retryAfterBaseValue?: number
} }
export function getRetryOptions( export function getRetryOptions(
retries: number, retries: number,
exemptStatusCodes: number[], exemptStatusCodes: number[],
retryAfter: number,
defaultOptions: OctokitOptions defaultOptions: OctokitOptions
): [RetryOptions, RequestRequestOptions | undefined] { ): [RetryOptions, RequestRequestOptions | undefined] {
if (retries <= 0) { if (retries <= 0) {
return [{enabled: false}, defaultOptions.request] return [{ enabled: false }, defaultOptions.request]
} }
const retryOptions: RetryOptions = { const retryOptions: RetryOptions = {
enabled: true enabled: true
} }
if (retryAfter > 0) {
retryOptions.retryAfterBaseValue = retryAfter
}
if (exemptStatusCodes.length > 0) { if (exemptStatusCodes.length > 0) {
retryOptions.doNotRetry = exemptStatusCodes retryOptions.doNotRetry = exemptStatusCodes
} }
@ -33,10 +39,9 @@ export function getRetryOptions(
} }
core.debug( core.debug(
`GitHub client configured with: (retries: ${ `GitHub client configured with: (retries: ${requestOptions.retries
requestOptions.retries }, retryAfterBaseValue: ${retryOptions.retryAfterBaseValue ?? 'octokit default: 1000'
}, retry-exempt-status-code: ${ } retry-exempt-status-code: ${retryOptions?.doNotRetry ?? 'octokit default: [400, 401, 403, 404, 422]'
retryOptions?.doNotRetry ?? 'octokit default: [400, 401, 403, 404, 422]'
})` })`
) )