diff --git a/__tests__/api_client.test.ts b/__tests__/api_client.test.ts index 9ef2bbf..0a0aa92 100644 --- a/__tests__/api_client.test.ts +++ b/__tests__/api_client.test.ts @@ -4,12 +4,13 @@ import { CredentialFetchingError, JobDetailsFetchingError } from '../src/api-client' +import {HttpClientError} from '@actions/http-client' describe('ApiClient', () => { - const mockAxios: any = { - get: jest.fn() + const mockHttpClient: any = { + getJson: jest.fn() } - const api = new ApiClient(mockAxios, { + const api = new ApiClient(mockHttpClient, { jobId: 1, jobToken: 'xxx', credentialsToken: 'yyy', @@ -37,7 +38,10 @@ describe('ApiClient', () => { } } } - mockAxios.get.mockResolvedValue({status: 200, data: apiResponse}) + mockHttpClient.getJson.mockResolvedValue({ + statusCode: 200, + result: apiResponse + }) const jobDetails = await api.getJobDetails() expect(jobDetails['allowed-updates'].length).toBe(1) @@ -54,32 +58,25 @@ describe('ApiClient', () => { } ] } - mockAxios.get.mockRejectedValue({ - isAxiosError: true, - response: {status: 400, data: apiResponse} - }) + mockHttpClient.getJson.mockRejectedValue( + new HttpClientError(JSON.stringify(apiResponse), 400) + ) await expect(api.getJobDetails()).rejects.toThrowError( new JobDetailsFetchingError( - 'fetching job details: received code 400: {"errors":[{"status":400,"title":"Bad Request","detail":"Update job has already been processed"}]}' + 'fetching job details: unexpected status code: 400: {"errors":[{"status":400,"title":"Bad Request","detail":"Update job has already been processed"}]}' ) ) }) test('job details with certificate error', async () => { - const errorObject = { - isAxiosError: true, - message: 'unable to get local issuer certificate', - name: 'Error', - stack: 'Error: unable to get local issuer certificate...', - status: null - } - - mockAxios.get.mockRejectedValue(errorObject) + mockHttpClient.getJson.mockRejectedValue( + new Error('unable to get local issuer certificate') + ) await expect(api.getJobDetails()).rejects.toThrowError( new JobDetailsFetchingError( - 'fetching job details: unable to get local issuer certificate' + 'fetching job details: Error: unable to get local issuer certificate' ) ) }) @@ -121,7 +118,10 @@ describe('ApiClient', () => { } } } - mockAxios.get.mockResolvedValue({status: 200, data: apiResponse}) + mockHttpClient.getJson.mockResolvedValue({ + statusCode: 200, + result: apiResponse + }) jest.spyOn(core, 'setSecret').mockImplementation(jest.fn()) const jobCredentials = await api.getCredentials() @@ -145,13 +145,12 @@ describe('ApiClient', () => { ] } - mockAxios.get.mockRejectedValue({ - isAxiosError: true, - response: {status: 422, data: apiResponse} - }) + mockHttpClient.getJson.mockRejectedValue( + new HttpClientError(JSON.stringify(apiResponse), 422) + ) await expect(api.getCredentials()).rejects.toThrowError( new CredentialFetchingError( - 'fetching credentials: received code 422: {"errors":[{"status":422,"title":"Secret Not Found","detail":"MISSING_SECRET_NAME"}]}' + 'fetching credentials: unexpected status code: 422: {"errors":[{"status":422,"title":"Secret Not Found","detail":"MISSING_SECRET_NAME"}]}' ) ) }) diff --git a/__tests__/updater-integration.test.ts b/__tests__/updater-integration.test.ts index 3e0edd1..e6f5610 100644 --- a/__tests__/updater-integration.test.ts +++ b/__tests__/updater-integration.test.ts @@ -1,5 +1,4 @@ -import axios from 'axios' -import axiosRetry from 'axios-retry' +import * as httpClient from '@actions/http-client' import fs from 'fs' import path from 'path' import {ApiClient} from '../src/api-client' @@ -43,13 +42,9 @@ integration('Updater', () => { workingDirectory ) - const client = axios.create({baseURL: dependabotApiUrl}) - axiosRetry(client, { - retryDelay: axiosRetry.exponentialDelay, // eslint-disable-line @typescript-eslint/unbound-method - retryCondition: e => { - return axiosRetry.isNetworkError(e) || axiosRetry.isRetryableError(e) - } - }) + const client = new httpClient.HttpClient( + 'github/dependabot-action integration' + ) const apiClient = new ApiClient(client, params) beforeAll(async () => { @@ -86,10 +81,10 @@ integration('Updater', () => { // NOTE: This will not work when running against the actual dependabot-api // Checks if the pr was persisted in the fake json-server - const res: any = await client.get('/pull_requests/1') + const res = await client.getJson(`${dependabotApiUrl}/pull_requests/1`) - expect(res.status).toEqual(200) - expect(res.data['pr-title']).toEqual( + expect(res.statusCode).toEqual(200) + expect(res.result['pr-title']).toEqual( 'Bump fetch-factory from 0.0.1 to 0.2.1' ) }) diff --git a/package-lock.json b/package-lock.json index e791f45..f26fb74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,8 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^5.1.1", + "@actions/http-client": "^2.2.0", "@octokit/webhooks-types": "^7.3.1", - "axios": "^1.6.0", - "axios-retry": "^3.8.1", "commander": "^11.1.0", "dockerode": "^4.0.0", "node-forge": "^1.3.1", @@ -75,11 +74,12 @@ } }, "node_modules/@actions/http-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", - "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", + "integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==", "dependencies": { - "tunnel": "^0.0.6" + "tunnel": "^0.0.6", + "undici": "^5.25.4" } }, "node_modules/@babel/code-frame": { @@ -698,6 +698,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -851,6 +852,14 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==", + "engines": { + "node": ">=14" + } + }, "node_modules/@github/browserslist-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@github/browserslist-config/-/browserslist-config-1.0.0.tgz", @@ -2293,11 +2302,6 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -2319,25 +2323,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios-retry": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.8.1.tgz", - "integrity": "sha512-4XseuArB4CEbfLRtMpUods2q8MLBvD4r8ifKgK4SP2FRgzQIPUDpzZ+cjQ/19eu3w2UpKgkJA+myEh2BYDSjqQ==", - "dependencies": { - "@babel/runtime": "^7.15.4", - "is-retry-allowed": "^2.2.0" - } - }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -2863,17 +2848,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -3194,14 +3168,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4652,25 +4618,6 @@ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4680,19 +4627,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5428,17 +5362,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", @@ -7007,6 +6930,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -7015,6 +6939,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -7739,11 +7664,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -7864,7 +7784,8 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", @@ -8946,6 +8867,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.0.tgz", + "integrity": "sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", @@ -9305,11 +9237,12 @@ } }, "@actions/http-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", - "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz", + "integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==", "requires": { - "tunnel": "^0.0.6" + "tunnel": "^0.0.6", + "undici": "^5.25.4" } }, "@babel/code-frame": { @@ -9805,6 +9738,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.11" } @@ -9923,6 +9857,11 @@ "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true }, + "@fastify/busboy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz", + "integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==" + }, "@github/browserslist-config": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@github/browserslist-config/-/browserslist-config-1.0.0.tgz", @@ -11061,11 +11000,6 @@ "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -11078,25 +11012,6 @@ "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", "dev": true }, - "axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "axios-retry": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.8.1.tgz", - "integrity": "sha512-4XseuArB4CEbfLRtMpUods2q8MLBvD4r8ifKgK4SP2FRgzQIPUDpzZ+cjQ/19eu3w2UpKgkJA+myEh2BYDSjqQ==", - "requires": { - "@babel/runtime": "^7.15.4", - "is-retry-allowed": "^2.2.0" - } - }, "axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -11485,14 +11400,6 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", @@ -11735,11 +11642,6 @@ "object-keys": "^1.1.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -12807,11 +12709,6 @@ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, - "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - }, "for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -12821,16 +12718,6 @@ "is-callable": "^1.1.3" } }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -13326,11 +13213,6 @@ "has-tostringtag": "^1.0.0" } }, - "is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==" - }, "is-set": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", @@ -14488,12 +14370,14 @@ "mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true }, "mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "requires": { "mime-db": "1.52.0" } @@ -15020,11 +14904,6 @@ "ipaddr.js": "1.9.1" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -15103,7 +14982,8 @@ "regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true }, "regexp.prototype.flags": { "version": "1.4.3", @@ -15881,6 +15761,14 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.27.0.tgz", + "integrity": "sha512-l3ydWhlhOJzMVOYkymLykcRRXqbUaQriERtR70B9LzNkZ4bX52Fc8wbTDneMiwo8T+AemZXvXaTx+9o5ROxrXg==", + "requires": { + "@fastify/busboy": "^2.0.0" + } + }, "undici-types": { "version": "5.25.3", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", diff --git a/package.json b/package.json index 26b8c6f..0628431 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,8 @@ "dependencies": { "@actions/core": "^1.10.1", "@actions/github": "^5.1.1", + "@actions/http-client": "^2.2.0", "@octokit/webhooks-types": "^7.3.1", - "axios": "^1.6.0", - "axios-retry": "^3.8.1", "commander": "^11.1.0", "dockerode": "^4.0.0", "node-forge": "^1.3.1", diff --git a/src/api-client.ts b/src/api-client.ts index b0fc978..21a99b5 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,7 +1,7 @@ import * as core from '@actions/core' -import axios from 'axios' -import type {AxiosInstance} from 'axios' +import * as httpClient from '@actions/http-client' import {JobParameters} from './inputs' +import {TypedResponse} from '@actions/http-client/lib/interfaces' // JobDetails are information about the repository and dependencies to be updated export type JobDetails = { @@ -32,7 +32,7 @@ export class CredentialFetchingError extends Error {} export class ApiClient { constructor( - private readonly client: AxiosInstance, + private readonly client: httpClient.HttpClient, readonly params: JobParameters ) {} @@ -43,48 +43,65 @@ export class ApiClient { } async getJobDetails(): Promise { - const detailsURL = `/update_jobs/${this.params.jobId}/details` + const detailsURL = `${this.params.dependabotApiUrl}/update_jobs/${this.params.jobId}/details` try { - const res: any = await this.client.get(detailsURL, { - headers: {Authorization: this.params.jobToken} - }) - if (res.status !== 200) { + const res = await this.getJsonWithRetry( + detailsURL, + this.params.jobToken + ) + if (res.statusCode !== 200) { throw new JobDetailsFetchingError( - `fetching job details: unexpected status code: ${res.status}` + `fetching job details: unexpected status code: ${ + res.statusCode + }: ${JSON.stringify(res.result)}` + ) + } + if (res.result == null) { + throw new JobDetailsFetchingError( + `fetching job details: missing response` ) } - return res.data.data.attributes + return res.result.data.attributes } catch (error: unknown) { - if (axios.isAxiosError(error)) { - const err = error - if (err.response) { - throw new JobDetailsFetchingError( - `fetching job details: received code ${err.response - ?.status}: ${JSON.stringify(err.response?.data)}` - ) - } else { - throw new JobDetailsFetchingError( - `fetching job details: ${err.message}` - ) - } - } else { + if (error instanceof JobDetailsFetchingError) { + throw error + } else if (error instanceof httpClient.HttpClientError) { throw new JobDetailsFetchingError( - `fetching job details: ${(error as Error).message}` + `fetching job details: unexpected status code: ${error.statusCode}: ${error.message}` + ) + } else if (error instanceof Error) { + throw new JobDetailsFetchingError( + `fetching job details: ${error.name}: ${error.message}` ) } + throw error } } async getCredentials(): Promise { - const credentialsURL = `/update_jobs/${this.params.jobId}/credentials` + const credentialsURL = `${this.params.dependabotApiUrl}/update_jobs/${this.params.jobId}/credentials` try { - const res: any = await this.client.get(credentialsURL, { - headers: {Authorization: this.params.credentialsToken} - }) + const res = await this.getJsonWithRetry( + credentialsURL, + this.params.credentialsToken + ) + + if (res.statusCode !== 200) { + throw new CredentialFetchingError( + `fetching credentials: unexpected status code: ${ + res.statusCode + }: ${JSON.stringify(res.result)}` + ) + } + if (res.result == null) { + throw new CredentialFetchingError( + `fetching credentials: missing response` + ) + } // Mask any secrets we've just retrieved from Actions logs - for (const credential of res.data.data.attributes.credentials) { + for (const credential of res.result.data.attributes.credentials) { if (credential.password) { core.setSecret(credential.password) } @@ -93,53 +110,84 @@ export class ApiClient { } } - return res.data.data.attributes.credentials + return res.result.data.attributes.credentials } catch (error: unknown) { - if (axios.isAxiosError(error)) { - const err = error - if (err.response) { - throw new CredentialFetchingError( - `fetching credentials: received code ${err.response - ?.status}: ${JSON.stringify(err.response?.data)}` - ) - } else { - throw new CredentialFetchingError( - `fetching credentials: ${err.message}` - ) - } - } else { + if (error instanceof CredentialFetchingError) { + throw error + } else if (error instanceof httpClient.HttpClientError) { + throw new CredentialFetchingError( + `fetching credentials: unexpected status code: ${error.statusCode}: ${error.message}` + ) + } else if (error instanceof Error) { throw new CredentialFetchingError( - `fetching credentials: ${(error as Error).message}` + `fetching credentials: ${error.name}: ${error.message}` ) } + throw error } } async reportJobError(error: JobError): Promise { - const recordErrorURL = `/update_jobs/${this.params.jobId}/record_update_job_error` - const res = await this.client.post( + const recordErrorURL = `${this.params.dependabotApiUrl}/update_jobs/${this.params.jobId}/record_update_job_error` + const res = await this.client.postJson( recordErrorURL, {data: error}, { - headers: {Authorization: this.params.jobToken} + ['Authorization']: this.params.jobToken } ) - if (res.status !== 204) { - throw new Error(`Unexpected status code: ${res.status}`) + if (res.statusCode !== 204) { + throw new Error(`Unexpected status code: ${res.statusCode}`) } } async markJobAsProcessed(): Promise { - const markAsProcessedURL = `/update_jobs/${this.params.jobId}/mark_as_processed` - const res = await this.client.patch( + const markAsProcessedURL = `${this.params.dependabotApiUrl}/update_jobs/${this.params.jobId}/mark_as_processed` + const res = await this.client.patchJson( markAsProcessedURL, {data: this.UnknownSha}, { - headers: {Authorization: this.params.jobToken} + ['Authorization']: this.params.jobToken } ) - if (res.status !== 204) { - throw new Error(`Unexpected status code: ${res.status}`) + if (res.statusCode !== 204) { + throw new Error(`Unexpected status code: ${res.statusCode}`) + } + } + + private async getJsonWithRetry( + url: string, + token: string + ): Promise> { + let attempt = 1 + + const execute = async (): Promise> => { + try { + return await this.client.getJson(url, { + ['Authorization']: token + }) + } catch (error: unknown) { + if (error instanceof httpClient.HttpClientError) { + if (error.statusCode >= 500 && error.statusCode <= 599) { + if (attempt >= 3) { + throw error + } + core.warning( + `Retrying failed request with status code: ${error.statusCode}` + ) + + // exponential backoff + const delayMs = 1000 * 2 ** attempt + await new Promise(resolve => setTimeout(resolve, delayMs)) + + attempt++ + return execute() + } + } + throw error + } } + + return execute() } } diff --git a/src/main.ts b/src/main.ts index 16ae3fc..3b90066 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,7 +1,6 @@ -import axios from 'axios' -import axiosRetry from 'axios-retry' import * as core from '@actions/core' import * as github from '@actions/github' +import * as httpClient from '@actions/http-client' import {Context} from '@actions/github/lib/context' import {ApiClient, CredentialFetchingError} from './api-client' import {getJobParameters} from './inputs' @@ -35,13 +34,7 @@ export async function run(context: Context): Promise { core.setSecret(params.jobToken) core.setSecret(params.credentialsToken) - const client = axios.create({baseURL: params.dependabotApiUrl}) - axiosRetry(client, { - retryDelay: axiosRetry.exponentialDelay, // eslint-disable-line @typescript-eslint/unbound-method - retryCondition: e => { - return axiosRetry.isNetworkError(e) || axiosRetry.isRetryableError(e) - } - }) + const client = new httpClient.HttpClient('github/dependabot-action') const apiClient = new ApiClient(client, params) core.info('Fetching job details')