neiam /action-setup-beam
action-setup-beam
public · Issues · Pulls · Labels · Forks · Compare · Actions queued
⭐
Log in to mark this repository.
feat: setup custom retry with builtin node fetch (#304)
2fdf20c · Blake Kostner · 2024-09-25 11:20
Files changed
modified
.github/workflows/action.yml
+2
−1
@@ -7,7 +7,7 @@ on:
| 7 | 7 | - main |
| 8 | 8 | pull_request: |
| 9 | 9 | branches: |
| 10 | − - "*" | |
| 10 | + - '*' | |
| 11 | 11 | workflow_dispatch: {} |
| 12 | 12 | |
| 13 | 13 | env: |
@@ -62,6 +62,7 @@ jobs:
| 62 | 62 | id: setup-beam |
| 63 | 63 | uses: ./ |
| 64 | 64 | with: |
| 65 | + install-rebar: false | |
| 65 | 66 | version-file: test/.tool-versions |
| 66 | 67 | version-type: strict |
| 67 | 68 |
modified
.github/workflows/ubuntu.yml
+2
−6
@@ -7,7 +7,7 @@ on:
| 7 | 7 | - main |
| 8 | 8 | pull_request: |
| 9 | 9 | branches: |
| 10 | − - "*" | |
| 10 | + - '*' | |
| 11 | 11 | workflow_dispatch: {} |
| 12 | 12 | |
| 13 | 13 | jobs: |
@@ -53,10 +53,6 @@ jobs:
| 53 | 53 | elixir-version: '1.16' |
| 54 | 54 | rebar3-version: '3' |
| 55 | 55 | os: 'ubuntu-22.04' |
| 56 | − - otp-version: '24' | |
| 57 | − elixir-version: '1.16' | |
| 58 | − rebar3-version: '3' | |
| 59 | − os: 'ubuntu-22.04' | |
| 60 | 56 | - otp-version: '25.0' |
| 61 | 57 | elixir-version: 'v1.13.4-otp-25' |
| 62 | 58 | rebar3-version: '3.18.0' |
@@ -121,7 +117,7 @@ jobs:
| 121 | 117 | otp-version: false |
| 122 | 118 | os: 'ubuntu-latest' |
| 123 | 119 | disable_problem_matchers: true |
| 124 | − - gleam-version: '0.22.0' # Version with Gleam's old archive naming convention | |
| 120 | + - gleam-version: '0.22.0' # Version with Gleam's old archive naming convention | |
| 125 | 121 | otp-version: '24' |
| 126 | 122 | os: 'ubuntu-latest' |
| 127 | 123 | disable_problem_matchers: true |
modified
dist/index.js
+49
−46
@@ -9106,12 +9106,13 @@ exports["default"] = _default;
| 9106 | 9106 | const core = __nccwpck_require__(2186) |
| 9107 | 9107 | const { exec } = __nccwpck_require__(1514) |
| 9108 | 9108 | const tc = __nccwpck_require__(7784) |
| 9109 | −const http = __nccwpck_require__(6255) | |
| 9110 | 9109 | const path = __nccwpck_require__(1017) |
| 9111 | 9110 | const semver = __nccwpck_require__(1383) |
| 9112 | 9111 | const fs = __nccwpck_require__(7147) |
| 9113 | 9112 | const os = __nccwpck_require__(2037) |
| 9114 | 9113 | |
| 9114 | +const MAX_HTTP_RETRIES = 3 | |
| 9115 | + | |
| 9115 | 9116 | main().catch((err) => { |
| 9116 | 9117 | core.setFailed(err.message) |
| 9117 | 9118 | }) |
@@ -9361,8 +9362,7 @@ async function getOTPVersions(osVersion) {
| 9361 | 9362 | hexMirrors: hexMirrorsInput(), |
| 9362 | 9363 | actionTitle: `fetch ${originListing}`, |
| 9363 | 9364 | action: async (hexMirror) => { |
| 9364 | − const l = await get(`${hexMirror}${originListing}`, [null]) | |
| 9365 | − return l | |
| 9365 | + return get(`${hexMirror}${originListing}`, []) | |
| 9366 | 9366 | }, |
| 9367 | 9367 | }) |
| 9368 | 9368 | } else if (process.platform === 'win32') { |
@@ -9389,7 +9389,7 @@ async function getOTPVersions(osVersion) {
| 9389 | 9389 | }) |
| 9390 | 9390 | } else if (process.platform === 'win32') { |
| 9391 | 9391 | otpVersionsListings.forEach((otpVersionsListing) => { |
| 9392 | − jsonParseAsList(otpVersionsListing) | |
| 9392 | + otpVersionsListing | |
| 9393 | 9393 | .map((x) => x.assets) |
| 9394 | 9394 | .flat() |
| 9395 | 9395 | .filter((x) => x.name.match(/^otp_win64_.*.exe$/)) |
@@ -9413,8 +9413,7 @@ async function getElixirVersions() {
| 9413 | 9413 | hexMirrors: hexMirrorsInput(), |
| 9414 | 9414 | actionTitle: `fetch ${originListing}`, |
| 9415 | 9415 | action: async (hexMirror) => { |
| 9416 | − const l = await get(`${hexMirror}${originListing}`, [null]) | |
| 9417 | − return l | |
| 9416 | + return get(`${hexMirror}${originListing}`, []) | |
| 9418 | 9417 | }, |
| 9419 | 9418 | }) |
| 9420 | 9419 | const otpVersionsForElixirMap = {} |
@@ -9447,7 +9446,7 @@ async function getGleamVersions() {
| 9447 | 9446 | ) |
| 9448 | 9447 | const gleamVersionsListing = {} |
| 9449 | 9448 | resultJSONs.forEach((resultJSON) => { |
| 9450 | − jsonParseAsList(resultJSON) | |
| 9449 | + resultJSON | |
| 9451 | 9450 | .map((x) => x.tag_name) |
| 9452 | 9451 | .forEach((ver) => { |
| 9453 | 9452 | const gleamMatch = ver.match(/^v?([^ ]+)/) |
@@ -9466,7 +9465,7 @@ async function getRebar3Versions() {
| 9466 | 9465 | ) |
| 9467 | 9466 | const rebar3VersionsListing = {} |
| 9468 | 9467 | resultJSONs.forEach((resultJSON) => { |
| 9469 | − jsonParseAsList(resultJSON) | |
| 9468 | + resultJSON | |
| 9470 | 9469 | .map((x) => x.tag_name) |
| 9471 | 9470 | .forEach((ver) => { |
| 9472 | 9471 | rebar3VersionsListing[ver] = ver |
@@ -9660,38 +9659,56 @@ function getRunnerOSVersion() {
| 9660 | 9659 | return containerFromEnvImageOS |
| 9661 | 9660 | } |
| 9662 | 9661 | |
| 9663 | −async function get(url0, pageIdxs) { | |
| 9664 | − async function getPage(pageIdx) { | |
| 9665 | − const url = new URL(url0) | |
| 9666 | − const headers = {} | |
| 9667 | − const GithubToken = getInput('github-token', false) | |
| 9668 | − if (GithubToken && url.host === 'api.github.com') { | |
| 9669 | − headers.authorization = `Bearer ${GithubToken}` | |
| 9670 | − } | |
| 9662 | +async function getUrlResponse(url, headers, attempt = 1) { | |
| 9663 | + try { | |
| 9664 | + const response = await fetch(url, { | |
| 9665 | + headers, | |
| 9666 | + signal: AbortSignal.timeout(10000), | |
| 9667 | + }) | |
| 9668 | + const contentType = response.headers.get('content-type') || '' | |
| 9671 | 9669 | |
| 9672 | − if (pageIdx !== null) { | |
| 9673 | − url.searchParams.append('page', pageIdx) | |
| 9670 | + if (!response.ok) { | |
| 9671 | + throw new Error(response.statusText) | |
| 9674 | 9672 | } |
| 9675 | 9673 | |
| 9676 | − const httpClient = new http.HttpClient('setup-beam', [], { | |
| 9677 | − allowRetries: true, | |
| 9678 | − maxRetries: 3, | |
| 9679 | − }) | |
| 9680 | − const response = await httpClient.get(url, headers) | |
| 9681 | − if (response.statusCode >= 400 && response.statusCode <= 599) { | |
| 9682 | − throw new Error( | |
| 9683 | − `Got ${response.statusCode} from ${url}. Exiting with error`, | |
| 9684 | − ) | |
| 9674 | + if (contentType.indexOf('application/json') !== -1) { | |
| 9675 | + return response.json() | |
| 9676 | + } else { | |
| 9677 | + return response.text() | |
| 9685 | 9678 | } |
| 9679 | + } catch (err) { | |
| 9680 | + core.debug(`Error fetching from ${url}: ${err}`) | |
| 9686 | 9681 | |
| 9687 | − return response.readBody() | |
| 9682 | + if (attempt <= MAX_HTTP_RETRIES) { | |
| 9683 | + const delay = attempt * 2 * 1000 | |
| 9684 | + core.debug(`Error during fetch. Retrying in ${delay}ms`) | |
| 9685 | + await new Promise((resolve) => setTimeout(resolve, delay)) | |
| 9686 | + return getUrlResponse(url, headers, attempt + 1) | |
| 9687 | + } else { | |
| 9688 | + throw err | |
| 9689 | + } | |
| 9688 | 9690 | } |
| 9691 | +} | |
| 9689 | 9692 | |
| 9690 | − if (pageIdxs[0] === null) { | |
| 9691 | − return getPage(null) | |
| 9693 | +async function get(url0, pageIdxs) { | |
| 9694 | + const url = new URL(url0) | |
| 9695 | + const headers = {} | |
| 9696 | + const GithubToken = getInput('github-token', false) | |
| 9697 | + if (GithubToken && url.host === 'api.github.com') { | |
| 9698 | + headers.authorization = `Bearer ${GithubToken}` | |
| 9692 | 9699 | } |
| 9693 | 9700 | |
| 9694 | − return Promise.all(pageIdxs.map(getPage)) | |
| 9701 | + if (pageIdxs.length === 0) { | |
| 9702 | + return getUrlResponse(url, headers) | |
| 9703 | + } else { | |
| 9704 | + return Promise.all( | |
| 9705 | + pageIdxs.map((page) => { | |
| 9706 | + const urlWithPage = new URL(url) | |
| 9707 | + urlWithPage.searchParams.append('page', page) | |
| 9708 | + return getUrlResponse(urlWithPage, headers) | |
| 9709 | + }), | |
| 9710 | + ) | |
| 9711 | + } | |
| 9695 | 9712 | } |
| 9696 | 9713 | |
| 9697 | 9714 | function maybePrependWithV(v) { |
@@ -9786,21 +9803,6 @@ function parseVersionFile(versionFilePath0) {
| 9786 | 9803 | return appVersions |
| 9787 | 9804 | } |
| 9788 | 9805 | |
| 9789 | −function jsonParseAsList(maybeJson) { | |
| 9790 | − try { | |
| 9791 | − const obj = JSON.parse(maybeJson) | |
| 9792 | − if (!Array.isArray(obj)) { | |
| 9793 | − throw new Error('expected a list!') | |
| 9794 | − } | |
| 9795 | − | |
| 9796 | − return obj | |
| 9797 | − } catch (exc) { | |
| 9798 | − throw new Error( | |
| 9799 | − `Got an exception when trying to parse non-JSON list ${maybeJson}: ${exc}`, | |
| 9800 | − ) | |
| 9801 | − } | |
| 9802 | −} | |
| 9803 | − | |
| 9804 | 9806 | function debugLog(groupName, message) { |
| 9805 | 9807 | const group = `Debugging for ${groupName}` |
| 9806 | 9808 | core.debug( |
@@ -10158,6 +10160,7 @@ function debugLoggingEnabled() {
| 10158 | 10160 | } |
| 10159 | 10161 | |
| 10160 | 10162 | module.exports = { |
| 10163 | + get, | |
| 10161 | 10164 | getOTPVersion, |
| 10162 | 10165 | getElixirVersion, |
| 10163 | 10166 | getGleamVersion, |
modified
package-lock.json
+0
−1
@@ -9,7 +9,6 @@
| 9 | 9 | "dependencies": { |
| 10 | 10 | "@actions/core": "1.10.0", |
| 11 | 11 | "@actions/exec": "1.1.1", |
| 12 | − "@actions/http-client": "2.1.0", | |
| 13 | 12 | "@actions/tool-cache": "2.0.1", |
| 14 | 13 | "semver": "7.6.2" |
| 15 | 14 | }, |
modified
package.json
+0
−1
@@ -18,7 +18,6 @@
| 18 | 18 | "dependencies": { |
| 19 | 19 | "@actions/core": "1.10.0", |
| 20 | 20 | "@actions/exec": "1.1.1", |
| 21 | − "@actions/http-client": "2.1.0", | |
| 22 | 21 | "@actions/tool-cache": "2.0.1", |
| 23 | 22 | "semver": "7.6.2" |
| 24 | 23 | }, |
modified
src/setup-beam.js
+49
−46
@@ -1,12 +1,13 @@
| 1 | 1 | const core = require('@actions/core') |
| 2 | 2 | const { exec } = require('@actions/exec') |
| 3 | 3 | const tc = require('@actions/tool-cache') |
| 4 | −const http = require('@actions/http-client') | |
| 5 | 4 | const path = require('path') |
| 6 | 5 | const semver = require('semver') |
| 7 | 6 | const fs = require('fs') |
| 8 | 7 | const os = require('os') |
| 9 | 8 | |
| 9 | +const MAX_HTTP_RETRIES = 3 | |
| 10 | + | |
| 10 | 11 | main().catch((err) => { |
| 11 | 12 | core.setFailed(err.message) |
| 12 | 13 | }) |
@@ -261,8 +262,7 @@ async function getOTPVersions(osVersion) {
| 261 | 262 | hexMirrors: hexMirrorsInput(), |
| 262 | 263 | actionTitle: `fetch ${originListing}`, |
| 263 | 264 | action: async (hexMirror) => { |
| 264 | − const l = await get(`${hexMirror}${originListing}`, [null]) | |
| 265 | − return l | |
| 265 | + return get(`${hexMirror}${originListing}`, []) | |
| 266 | 266 | }, |
| 267 | 267 | }) |
| 268 | 268 | } else if (process.platform === 'win32') { |
@@ -289,7 +289,7 @@ async function getOTPVersions(osVersion) {
| 289 | 289 | }) |
| 290 | 290 | } else if (process.platform === 'win32') { |
| 291 | 291 | otpVersionsListings.forEach((otpVersionsListing) => { |
| 292 | − jsonParseAsList(otpVersionsListing) | |
| 292 | + otpVersionsListing | |
| 293 | 293 | .map((x) => x.assets) |
| 294 | 294 | .flat() |
| 295 | 295 | .filter((x) => x.name.match(/^otp_win64_.*.exe$/)) |
@@ -313,8 +313,7 @@ async function getElixirVersions() {
| 313 | 313 | hexMirrors: hexMirrorsInput(), |
| 314 | 314 | actionTitle: `fetch ${originListing}`, |
| 315 | 315 | action: async (hexMirror) => { |
| 316 | − const l = await get(`${hexMirror}${originListing}`, [null]) | |
| 317 | − return l | |
| 316 | + return get(`${hexMirror}${originListing}`, []) | |
| 318 | 317 | }, |
| 319 | 318 | }) |
| 320 | 319 | const otpVersionsForElixirMap = {} |
@@ -347,7 +346,7 @@ async function getGleamVersions() {
| 347 | 346 | ) |
| 348 | 347 | const gleamVersionsListing = {} |
| 349 | 348 | resultJSONs.forEach((resultJSON) => { |
| 350 | − jsonParseAsList(resultJSON) | |
| 349 | + resultJSON | |
| 351 | 350 | .map((x) => x.tag_name) |
| 352 | 351 | .forEach((ver) => { |
| 353 | 352 | const gleamMatch = ver.match(/^v?([^ ]+)/) |
@@ -366,7 +365,7 @@ async function getRebar3Versions() {
| 366 | 365 | ) |
| 367 | 366 | const rebar3VersionsListing = {} |
| 368 | 367 | resultJSONs.forEach((resultJSON) => { |
| 369 | − jsonParseAsList(resultJSON) | |
| 368 | + resultJSON | |
| 370 | 369 | .map((x) => x.tag_name) |
| 371 | 370 | .forEach((ver) => { |
| 372 | 371 | rebar3VersionsListing[ver] = ver |
@@ -560,38 +559,56 @@ function getRunnerOSVersion() {
| 560 | 559 | return containerFromEnvImageOS |
| 561 | 560 | } |
| 562 | 561 | |
| 563 | −async function get(url0, pageIdxs) { | |
| 564 | − async function getPage(pageIdx) { | |
| 565 | − const url = new URL(url0) | |
| 566 | − const headers = {} | |
| 567 | − const GithubToken = getInput('github-token', false) | |
| 568 | − if (GithubToken && url.host === 'api.github.com') { | |
| 569 | − headers.authorization = `Bearer ${GithubToken}` | |
| 570 | − } | |
| 562 | +async function getUrlResponse(url, headers, attempt = 1) { | |
| 563 | + try { | |
| 564 | + const response = await fetch(url, { | |
| 565 | + headers, | |
| 566 | + signal: AbortSignal.timeout(10000), | |
| 567 | + }) | |
| 568 | + const contentType = response.headers.get('content-type') || '' | |
| 571 | 569 | |
| 572 | − if (pageIdx !== null) { | |
| 573 | − url.searchParams.append('page', pageIdx) | |
| 570 | + if (!response.ok) { | |
| 571 | + throw new Error(response.statusText) | |
| 574 | 572 | } |
| 575 | 573 | |
| 576 | − const httpClient = new http.HttpClient('setup-beam', [], { | |
| 577 | − allowRetries: true, | |
| 578 | − maxRetries: 3, | |
| 579 | − }) | |
| 580 | − const response = await httpClient.get(url, headers) | |
| 581 | − if (response.statusCode >= 400 && response.statusCode <= 599) { | |
| 582 | − throw new Error( | |
| 583 | − `Got ${response.statusCode} from ${url}. Exiting with error`, | |
| 584 | − ) | |
| 574 | + if (contentType.indexOf('application/json') !== -1) { | |
| 575 | + return response.json() | |
| 576 | + } else { | |
| 577 | + return response.text() | |
| 585 | 578 | } |
| 579 | + } catch (err) { | |
| 580 | + core.debug(`Error fetching from ${url}: ${err}`) | |
| 586 | 581 | |
| 587 | − return response.readBody() | |
| 582 | + if (attempt <= MAX_HTTP_RETRIES) { | |
| 583 | + const delay = attempt * 2 * 1000 | |
| 584 | + core.debug(`Error during fetch. Retrying in ${delay}ms`) | |
| 585 | + await new Promise((resolve) => setTimeout(resolve, delay)) | |
| 586 | + return getUrlResponse(url, headers, attempt + 1) | |
| 587 | + } else { | |
| 588 | + throw err | |
| 589 | + } | |
| 588 | 590 | } |
| 591 | +} | |
| 589 | 592 | |
| 590 | − if (pageIdxs[0] === null) { | |
| 591 | − return getPage(null) | |
| 593 | +async function get(url0, pageIdxs) { | |
| 594 | + const url = new URL(url0) | |
| 595 | + const headers = {} | |
| 596 | + const GithubToken = getInput('github-token', false) | |
| 597 | + if (GithubToken && url.host === 'api.github.com') { | |
| 598 | + headers.authorization = `Bearer ${GithubToken}` | |
| 592 | 599 | } |
| 593 | 600 | |
| 594 | − return Promise.all(pageIdxs.map(getPage)) | |
| 601 | + if (pageIdxs.length === 0) { | |
| 602 | + return getUrlResponse(url, headers) | |
| 603 | + } else { | |
| 604 | + return Promise.all( | |
| 605 | + pageIdxs.map((page) => { | |
| 606 | + const urlWithPage = new URL(url) | |
| 607 | + urlWithPage.searchParams.append('page', page) | |
| 608 | + return getUrlResponse(urlWithPage, headers) | |
| 609 | + }), | |
| 610 | + ) | |
| 611 | + } | |
| 595 | 612 | } |
| 596 | 613 | |
| 597 | 614 | function maybePrependWithV(v) { |
@@ -686,21 +703,6 @@ function parseVersionFile(versionFilePath0) {
| 686 | 703 | return appVersions |
| 687 | 704 | } |
| 688 | 705 | |
| 689 | −function jsonParseAsList(maybeJson) { | |
| 690 | − try { | |
| 691 | − const obj = JSON.parse(maybeJson) | |
| 692 | − if (!Array.isArray(obj)) { | |
| 693 | − throw new Error('expected a list!') | |
| 694 | − } | |
| 695 | − | |
| 696 | − return obj | |
| 697 | − } catch (exc) { | |
| 698 | − throw new Error( | |
| 699 | − `Got an exception when trying to parse non-JSON list ${maybeJson}: ${exc}`, | |
| 700 | − ) | |
| 701 | − } | |
| 702 | −} | |
| 703 | − | |
| 704 | 706 | function debugLog(groupName, message) { |
| 705 | 707 | const group = `Debugging for ${groupName}` |
| 706 | 708 | core.debug( |
@@ -1058,6 +1060,7 @@ function debugLoggingEnabled() {
| 1058 | 1060 | } |
| 1059 | 1061 | |
| 1060 | 1062 | module.exports = { |
| 1063 | + get, | |
| 1061 | 1064 | getOTPVersion, |
| 1062 | 1065 | getElixirVersion, |
| 1063 | 1066 | getGleamVersion, |
modified
test/setup-beam.test.js
+27
−4
@@ -7,6 +7,7 @@ simulateInput('github-token', process.env.GITHUB_TOKEN)
| 7 | 7 | simulateInput('hexpm-mirrors', 'https://builds.hex.pm', { multiline: true }) |
| 8 | 8 | |
| 9 | 9 | const assert = require('assert') |
| 10 | +const http = require('http') | |
| 10 | 11 | const fs = require('fs') |
| 11 | 12 | const os = require('os') |
| 12 | 13 | const path = require('path') |
@@ -71,8 +72,8 @@ async function all() {
| 71 | 72 | await testRebar3Versions() |
| 72 | 73 | |
| 73 | 74 | await testGetVersionFromSpec() |
| 74 | − | |
| 75 | 75 | await testParseVersionFile() |
| 76 | + await testGetRetry() | |
| 76 | 77 | |
| 77 | 78 | await testElixirMixCompileError() |
| 78 | 79 | await testElixirMixCompileWarning() |
@@ -870,10 +871,10 @@ async function testParseVersionFile() {
| 870 | 871 | const gleamVersion = unsimulateInput('gleam-version') |
| 871 | 872 | const rebar3Version = unsimulateInput('rebar3-version') |
| 872 | 873 | |
| 873 | − const erlang = '25.1.1' | |
| 874 | − const elixir = '1.14.1' | |
| 874 | + const erlang = '27' | |
| 875 | + const elixir = '1.17.0' | |
| 875 | 876 | const gleam = '0.23.0' |
| 876 | − const rebar3 = '3.16.0' | |
| 877 | + const rebar3 = '3.24.0' | |
| 877 | 878 | const toolVersions = `# a comment |
| 878 | 879 | erlang ref:v${erlang}# comment, no space, and ref:v |
| 879 | 880 | elixir ref:${elixir} # comment, with space and ref: |
@@ -913,6 +914,28 @@ gleam ${gleam} \nrebar ${rebar3}`
| 913 | 914 | simulateInput('rebar3-version', rebar3Version) |
| 914 | 915 | } |
| 915 | 916 | |
| 917 | +async function testGetRetry() { | |
| 918 | + let attempt = 0 | |
| 919 | + const server = http.createServer((req, res) => { | |
| 920 | + attempt++ | |
| 921 | + if (attempt == 2) { | |
| 922 | + res.write('correct!') | |
| 923 | + res.end() | |
| 924 | + } | |
| 925 | + }) | |
| 926 | + | |
| 927 | + try { | |
| 928 | + server.listen(0) | |
| 929 | + const port = server.address().port | |
| 930 | + | |
| 931 | + const response = await setupBeam.get(`http://localhost:${port}`, []) | |
| 932 | + assert.equal(response, 'correct!') | |
| 933 | + assert.equal(attempt, 2) | |
| 934 | + } finally { | |
| 935 | + server.close() | |
| 936 | + } | |
| 937 | +} | |
| 938 | + | |
| 916 | 939 | async function testElixirMixCompileError() { |
| 917 | 940 | const [matcher] = problemMatcher.find( |
| 918 | 941 | ({ owner }) => owner === 'elixir-mixCompileError', |
Parents: d1c0257