From 4e4bfe925606829ee2ab87390cad22890cdad22f Mon Sep 17 00:00:00 2001 From: Rhys Arkins <rhys@arkins.net> Date: Fri, 3 Apr 2020 13:45:55 +0200 Subject: [PATCH] feat(internal): http util wrapper (#5841) Co-Authored-By: Michael Kriese <michael.kriese@visualon.de> --- lib/config/presets/github.ts | 11 +- lib/datasource/cdnjs/index.ts | 8 +- lib/datasource/crate/index.ts | 8 +- lib/datasource/dart/index.ts | 13 +- .../docker/__snapshots__/index.spec.ts.snap | 189 +++++++++++++++++- lib/datasource/docker/index.ts | 52 ++--- lib/datasource/galaxy/index.ts | 8 +- lib/datasource/go/index.ts | 16 +- lib/datasource/gradle-version/index.spec.ts | 4 +- lib/datasource/gradle-version/index.ts | 25 +-- lib/datasource/helm/index.ts | 6 +- lib/datasource/hex/index.ts | 9 +- lib/datasource/maven/util.ts | 22 +- lib/datasource/npm/get.ts | 13 +- lib/datasource/nuget/v2.ts | 16 +- lib/datasource/nuget/v3.ts | 33 +-- lib/datasource/orb/index.spec.ts | 8 +- lib/datasource/orb/index.ts | 9 +- lib/datasource/packagist/index.ts | 33 +-- .../pypi/__snapshots__/index.spec.ts.snap | 88 +++----- lib/datasource/pypi/index.ts | 14 +- lib/datasource/ruby-version/index.ts | 6 +- lib/datasource/rubygems/get-rubygems-org.ts | 7 +- lib/datasource/rubygems/get.ts | 10 +- lib/datasource/rubygems/retriable.spec.ts | 31 --- lib/datasource/rubygems/retriable.ts | 63 ------ lib/datasource/terraform-module/index.ts | 11 +- lib/datasource/terraform-provider/index.ts | 11 +- lib/manager/bazel/update.ts | 6 +- lib/manager/gradle-wrapper/update.ts | 8 +- lib/manager/homebrew/update.ts | 8 +- lib/util/got/common.ts | 1 + lib/util/http/index.ts | 104 ++++++++++ tools/jest-gh-reporter.ts | 5 +- 34 files changed, 479 insertions(+), 377 deletions(-) delete mode 100644 lib/datasource/rubygems/retriable.spec.ts delete mode 100644 lib/datasource/rubygems/retriable.ts create mode 100644 lib/util/http/index.ts diff --git a/lib/config/presets/github.ts b/lib/config/presets/github.ts index 175c7dbb82..addef24b7a 100644 --- a/lib/config/presets/github.ts +++ b/lib/config/presets/github.ts @@ -1,22 +1,23 @@ import { logger } from '../../logger'; import { Preset } from './common'; -import got, { GotJSONOptions } from '../../util/got'; +import { Http, HttpOptions } from '../../util/http'; import { PLATFORM_FAILURE } from '../../constants/error-messages'; +const id = 'github'; +const http = new Http(id); + async function fetchJSONFile(repo: string, fileName: string): Promise<Preset> { const url = `https://api.github.com/repos/${repo}/contents/${fileName}`; - const opts: GotJSONOptions = { + const opts: HttpOptions = { headers: { accept: global.appMode ? 'application/vnd.github.machine-man-preview+json' : 'application/vnd.github.v3+json', }, - json: true, - hostType: 'github', }; let res: { body: { content: string } }; try { - res = await got(url, opts); + res = await http.getJson(url, opts); } catch (err) { if (err.message === PLATFORM_FAILURE) { throw err; diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts index fea307efd2..eb37fc211a 100644 --- a/lib/datasource/cdnjs/index.ts +++ b/lib/datasource/cdnjs/index.ts @@ -1,5 +1,5 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { DatasourceError, ReleaseResult, GetReleasesConfig } from '../common'; export interface CdnjsAsset { @@ -10,6 +10,8 @@ export interface CdnjsAsset { export const id = 'cdnjs'; +const http = new Http(id); + const cacheNamespace = `datasource-${id}`; const cacheMinutes = 60; @@ -36,7 +38,7 @@ export async function getDigest( const assetName = lookupName.replace(`${library}/`, ''); let res = null; try { - res = await got(url, { hostType: id, json: true }); + res = await http.getJson(url); } catch (e) /* istanbul ignore next */ { return null; } @@ -68,7 +70,7 @@ export async function getPkgReleases({ const url = depUrl(library); try { - const res = await got(url, { hostType: id, json: true }); + const res = await http.getJson(url); const cdnjsResp: CdnjsResponse = res.body; diff --git a/lib/datasource/crate/index.ts b/lib/datasource/crate/index.ts index 0640c13f1a..519daf8b43 100644 --- a/lib/datasource/crate/index.ts +++ b/lib/datasource/crate/index.ts @@ -1,5 +1,5 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { DatasourceError, GetReleasesConfig, @@ -9,6 +9,8 @@ import { export const id = 'crate'; +const http = new Http(id); + export async function getPkgReleases({ lookupName, }: GetReleasesConfig): Promise<ReleaseResult | null> { @@ -41,9 +43,7 @@ export async function getPkgReleases({ 'https://raw.githubusercontent.com/rust-lang/crates.io-index/master/'; const crateUrl = baseUrl + path; try { - let res: any = await got(crateUrl, { - hostType: id, - }); + let res: any = await http.get(crateUrl); if (!res || !res.body) { logger.warn( { dependency: lookupName }, diff --git a/lib/datasource/dart/index.ts b/lib/datasource/dart/index.ts index 7c571066ad..edbe714f0b 100644 --- a/lib/datasource/dart/index.ts +++ b/lib/datasource/dart/index.ts @@ -1,9 +1,11 @@ -import got from '../../util/got'; +import { Http, HttpResponse } from '../../util/http'; import { logger } from '../../logger'; import { DatasourceError, ReleaseResult, GetReleasesConfig } from '../common'; export const id = 'dart'; +const http = new Http(id); + export async function getPkgReleases({ lookupName, }: GetReleasesConfig): Promise<ReleaseResult | null> { @@ -18,14 +20,9 @@ export async function getPkgReleases({ }; } - let raw: { - body: DartResult; - } = null; + let raw: HttpResponse<DartResult> = null; try { - raw = await got(pkgUrl, { - hostType: id, - json: true, - }); + raw = await http.getJson<DartResult>(pkgUrl); } catch (err) { if (err.statusCode === 404 || err.code === 'ENOTFOUND') { logger.debug({ lookupName }, `Dependency lookup failure: not found`); diff --git a/lib/datasource/docker/__snapshots__/index.spec.ts.snap b/lib/datasource/docker/__snapshots__/index.spec.ts.snap index 88b83ee46b..da12801f3f 100644 --- a/lib/datasource/docker/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/docker/__snapshots__/index.spec.ts.snap @@ -6,6 +6,13 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (explicit Array [ "https://index.docker.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -15,7 +22,14 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (explicit "headers": Object { "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", "json": true, + "method": "get", }, ], Array [ @@ -24,12 +38,25 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (explicit "headers": Object { "authorization": "Bearer some-token ", }, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://index.docker.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -39,6 +66,13 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (explicit "headers": Object { "accept": "application/vnd.docker.distribution.manifest.v2+json", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], ], @@ -94,6 +128,13 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (implicit Array [ "https://index.docker.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -103,7 +144,14 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (implicit "headers": Object { "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", "json": true, + "method": "get", }, ], Array [ @@ -112,12 +160,25 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (implicit "headers": Object { "authorization": "Bearer some-token ", }, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://index.docker.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -127,6 +188,13 @@ exports[`api/docker getPkgReleases adds library/ prefix for Docker Hub (implicit "headers": Object { "accept": "application/vnd.docker.distribution.manifest.v2+json", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], ], @@ -182,6 +250,13 @@ exports[`api/docker getPkgReleases adds no library/ prefix for other registries Array [ "https://k8s.gcr.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -191,7 +266,14 @@ exports[`api/docker getPkgReleases adds no library/ prefix for other registries "headers": Object { "authorization": "Basic c29tZS11c2VybmFtZTpzb21lLXBhc3N3b3Jk", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", "json": true, + "method": "get", }, ], Array [ @@ -200,12 +282,25 @@ exports[`api/docker getPkgReleases adds no library/ prefix for other registries "headers": Object { "authorization": "Bearer some-token ", }, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://k8s.gcr.io/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -215,6 +310,13 @@ exports[`api/docker getPkgReleases adds no library/ prefix for other registries "headers": Object { "accept": "application/vnd.docker.distribution.manifest.v2+json", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], ], @@ -270,6 +372,13 @@ exports[`api/docker getPkgReleases uses custom registry in depName 1`] = ` Array [ "https://registry.company.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -277,12 +386,25 @@ exports[`api/docker getPkgReleases uses custom registry in depName 1`] = ` "https://registry.company.com/v2/node/tags/list?n=10000", Object { "headers": Object {}, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://registry.company.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -318,6 +440,13 @@ Array [ Array [ "https://registry.company.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -325,19 +454,38 @@ Array [ "https://registry.company.com/v2/node/tags/list?n=10000", Object { "headers": Object {}, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://api.github.com/user/9287/repos?page=3&per_page=100", Object { "headers": Object {}, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://registry.company.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -347,6 +495,13 @@ Array [ "headers": Object { "accept": "application/vnd.docker.distribution.manifest.v2+json", }, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], ] @@ -358,6 +513,13 @@ exports[`api/docker getPkgReleases uses lower tag limit for ECR deps 1`] = ` Array [ "https://123456789.dkr.ecr.us-east-1.amazonaws.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], @@ -365,12 +527,25 @@ exports[`api/docker getPkgReleases uses lower tag limit for ECR deps 1`] = ` "https://123456789.dkr.ecr.us-east-1.amazonaws.com/v2/node/tags/list?n=1000", Object { "headers": Object {}, - "json": true, + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", }, ], Array [ "https://123456789.dkr.ecr.us-east-1.amazonaws.com/v2/", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, + "hostType": "docker", + "method": "get", "throwHttpErrors": false, }, ], diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts index b0f80b414e..05a7b84b7b 100644 --- a/lib/datasource/docker/index.ts +++ b/lib/datasource/docker/index.ts @@ -6,10 +6,9 @@ import wwwAuthenticate from 'www-authenticate'; import { OutgoingHttpHeaders } from 'http'; import AWS from 'aws-sdk'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http, HttpResponse } from '../../util/http'; import * as hostRules from '../../util/host-rules'; import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; -import { GotResponse } from '../../platform'; import { HostRule } from '../../types'; // TODO: add got typings when available @@ -17,6 +16,8 @@ import { HostRule } from '../../types'; export const id = 'docker'; +const http = new Http(id); + const ecrRegex = /\d+\.dkr\.ecr\.([-a-z0-9]+)\.amazonaws\.com/; export interface RegistryRepository { @@ -98,7 +99,9 @@ async function getAuthHeaders( ): Promise<OutgoingHttpHeaders | null> { try { const apiCheckUrl = `${registry}/v2/`; - const apiCheckResponse = await got(apiCheckUrl, { throwHttpErrors: false }); + const apiCheckResponse = await http.get(apiCheckUrl, { + throwHttpErrors: false, + }); if (apiCheckResponse.headers['www-authenticate'] === undefined) { return {}; } @@ -127,7 +130,7 @@ async function getAuthHeaders( if (authenticateHeader.scheme.toUpperCase() === 'BASIC') { logger.debug(`Using Basic auth for docker registry ${repository}`); - await got(apiCheckUrl, opts); + await http.get(apiCheckUrl, opts); return opts.headers; } @@ -136,7 +139,12 @@ async function getAuthHeaders( logger.trace( `Obtaining docker registry token for ${repository} using url ${authUrl}` ); - const authResponse = (await got(authUrl, opts)).body; + const authResponse = ( + await http.getJson<{ token?: string; access_token?: string }>( + authUrl, + opts + ) + ).body; const token = authResponse.token || authResponse.access_token; // istanbul ignore if @@ -187,7 +195,7 @@ function digestFromManifestStr(str: hasha.HashaInput): string { return 'sha256:' + hasha(str, { algorithm: 'sha256' }); } -function extractDigestFromResponse(manifestResponse: GotResponse): string { +function extractDigestFromResponse(manifestResponse: HttpResponse): string { if (manifestResponse.headers['docker-content-digest'] === undefined) { return digestFromManifestStr(manifestResponse.body); } @@ -198,7 +206,7 @@ async function getManifestResponse( registry: string, repository: string, tag: string -): Promise<GotResponse> { +): Promise<HttpResponse> { logger.debug(`getManifestResponse(${registry}, ${repository}, ${tag})`); try { const headers = await getAuthHeaders(registry, repository); @@ -208,7 +216,7 @@ async function getManifestResponse( } headers.accept = 'application/vnd.docker.distribution.manifest.v2+json'; const url = `${registry}/v2/${repository}/manifests/${tag}`; - const manifestResponse = await got(url, { + const manifestResponse = await http.get(url, { headers, }); return manifestResponse; @@ -347,7 +355,7 @@ async function getTags( } let page = 1; do { - const res = await got<{ tags: string[] }>(url, { json: true, headers }); + const res = await http.getJson<{ tags: string[] }>(url, { headers }); tags = tags.concat(res.body.tags); const linkHeader = parseLinkHeader(res.headers.link as string); url = @@ -415,31 +423,9 @@ async function getTags( export function getConfigResponse( url: string, headers: OutgoingHttpHeaders -): Promise<GotResponse> { - return got(url, { +): Promise<HttpResponse> { + return http.get(url, { headers, - hooks: { - beforeRedirect: [ - (options: any): void => { - if ( - options.search && - options.search.indexOf('X-Amz-Algorithm') !== -1 - ) { - // if there is no port in the redirect URL string, then delete it from the redirect options. - // This can be evaluated for removal after upgrading to Got v10 - const portInUrl = options.href.split('/')[2].split(':')[1]; - if (!portInUrl) { - // eslint-disable-next-line no-param-reassign - delete options.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively - } - - // docker registry is hosted on amazon, redirect url includes authentication. - // eslint-disable-next-line no-param-reassign - delete options.headers.authorization; - } - }, - ], - }, }); } diff --git a/lib/datasource/galaxy/index.ts b/lib/datasource/galaxy/index.ts index f51b9378dd..f4a2d53c1a 100644 --- a/lib/datasource/galaxy/index.ts +++ b/lib/datasource/galaxy/index.ts @@ -1,5 +1,5 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { DatasourceError, GetReleasesConfig, @@ -9,6 +9,8 @@ import { export const id = 'galaxy'; +const http = new Http(id); + export async function getPkgReleases({ lookupName, }: GetReleasesConfig): Promise<ReleaseResult | null> { @@ -36,9 +38,7 @@ export async function getPkgReleases({ projectName; const galaxyProjectUrl = baseUrl + userName + '/' + projectName; try { - let res: any = await got(galaxyAPIUrl, { - hostType: id, - }); + let res: any = await http.get(galaxyAPIUrl); if (!res || !res.body) { logger.warn( { dependency: lookupName }, diff --git a/lib/datasource/go/index.ts b/lib/datasource/go/index.ts index 5e9c85964b..8e31730d71 100644 --- a/lib/datasource/go/index.ts +++ b/lib/datasource/go/index.ts @@ -1,11 +1,13 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import * as github from '../github-tags'; import { DigestConfig, GetReleasesConfig, ReleaseResult } from '../common'; import { regEx } from '../../util/regex'; export const id = 'go'; +const http = new Http(id); + interface DataSource { datasource: string; lookupName: string; @@ -32,14 +34,10 @@ async function getDatasource(goModule: string): Promise<DataSource | null> { } const pkgUrl = `https://${goModule}?go-get=1`; try { - const res = ( - await got(pkgUrl, { - hostType: id, - }) - ).body; - const sourceMatch = res.match( - regEx(`<meta\\s+name="go-source"\\s+content="${goModule}\\s+([^\\s]+)`) - ); + const res = (await http.get(pkgUrl)).body; + const sourceMatch = regEx( + `<meta\\s+name="go-source"\\s+content="${goModule}\\s+([^\\s]+)` + ).exec(res); if (sourceMatch) { const [, goSourceUrl] = sourceMatch; logger.debug({ goModule, goSourceUrl }, 'Go lookup source url'); diff --git a/lib/datasource/gradle-version/index.spec.ts b/lib/datasource/gradle-version/index.spec.ts index e9d24669bc..49fb650f70 100644 --- a/lib/datasource/gradle-version/index.spec.ts +++ b/lib/datasource/gradle-version/index.spec.ts @@ -15,7 +15,9 @@ let config: any = {}; describe('datasource/gradle-version', () => { describe('getPkgReleases', () => { beforeEach(() => { - config = {}; + config = { + lookupName: 'abc', + }; jest.clearAllMocks(); global.repoCache = {}; return global.renovateCache.rmAll(); diff --git a/lib/datasource/gradle-version/index.ts b/lib/datasource/gradle-version/index.ts index 6c4ac4945a..9e22603806 100644 --- a/lib/datasource/gradle-version/index.ts +++ b/lib/datasource/gradle-version/index.ts @@ -2,7 +2,7 @@ import is from '@sindresorhus/is'; import { coerce } from 'semver'; import { regEx } from '../../util/regex'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { DatasourceError, GetReleasesConfig, @@ -12,18 +12,18 @@ import { export const id = 'gradle-version'; +const http = new Http(id); + const GradleVersionsServiceUrl = 'https://services.gradle.org/versions/all'; interface GradleRelease { - body: { - snapshot?: boolean; - nightly?: boolean; - rcFor?: string; - version: string; - downloadUrl?: string; - checksumUrl?: string; - buildTime?: string; - }[]; + snapshot?: boolean; + nightly?: boolean; + rcFor?: string; + version: string; + downloadUrl?: string; + checksumUrl?: string; + buildTime?: string; } const buildTimeRegex = regEx( @@ -50,10 +50,7 @@ export async function getPkgReleases({ const allReleases: Release[][] = await Promise.all( versionsUrls.map(async url => { try { - const response: GradleRelease = await got(url, { - hostType: id, - json: true, - }); + const response = await http.getJson<GradleRelease[]>(url); const releases = response.body .filter(release => !release.snapshot && !release.nightly) .filter( diff --git a/lib/datasource/helm/index.ts b/lib/datasource/helm/index.ts index c2ba01f962..2b7bbbdbc1 100644 --- a/lib/datasource/helm/index.ts +++ b/lib/datasource/helm/index.ts @@ -1,11 +1,13 @@ import yaml from 'js-yaml'; import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; export const id = 'helm'; +const http = new Http(id); + export async function getRepositoryData( repository: string ): Promise<ReleaseResult[]> { @@ -17,7 +19,7 @@ export async function getRepositoryData( } let res: any; try { - res = await got('index.yaml', { hostType: id, baseUrl: repository }); + res = await http.get('index.yaml', { baseUrl: repository }); if (!res || !res.body) { logger.warn(`Received invalid response from ${repository}`); return null; diff --git a/lib/datasource/hex/index.ts b/lib/datasource/hex/index.ts index 3067723be6..79dd1d8f94 100644 --- a/lib/datasource/hex/index.ts +++ b/lib/datasource/hex/index.ts @@ -1,9 +1,11 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { DatasourceError, ReleaseResult, GetReleasesConfig } from '../common'; export const id = 'hex'; +const http = new Http(id); + interface HexRelease { html_url: string; meta?: { links?: Record<string, string> }; @@ -24,10 +26,7 @@ export async function getPkgReleases({ const hexPackageName = lookupName.split(':')[0]; const hexUrl = `https://hex.pm/api/packages/${hexPackageName}`; try { - const response = await got(hexUrl, { - json: true, - hostType: id, - }); + const response = await http.getJson<HexRelease>(hexUrl); const hexRelease: HexRelease = response.body; diff --git a/lib/datasource/maven/util.ts b/lib/datasource/maven/util.ts index 717762391b..aa1814739d 100644 --- a/lib/datasource/maven/util.ts +++ b/lib/datasource/maven/util.ts @@ -1,10 +1,12 @@ import url from 'url'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; import { DatasourceError } from '../common'; import { id, MAVEN_REPO, MAVEN_REPO_DEPRECATED } from './common'; +const http = new Http(id); + const getHost = (x: string): string => new url.URL(x).host; const defaultHosts = [MAVEN_REPO, MAVEN_REPO_DEPRECATED].map(getHost); @@ -46,23 +48,7 @@ export async function downloadHttpProtocol( ): Promise<string | null> { let raw: { body: string }; try { - raw = await got(pkgUrl, { - hostType, - hooks: { - beforeRedirect: [ - (options: any): void => { - if ( - options.search && - options.search.indexOf('X-Amz-Algorithm') !== -1 - ) { - // maven repository is hosted on amazon, redirect url includes authentication. - // eslint-disable-next-line no-param-reassign - delete options.auth; - } - }, - ], - }, - }); + raw = await http.get(pkgUrl.toString()); } catch (err) { const failedUrl = pkgUrl.toString(); if (isNotFoundError(err)) { diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts index 6a54de6e95..4788527560 100644 --- a/lib/datasource/npm/get.ts +++ b/lib/datasource/npm/get.ts @@ -7,12 +7,14 @@ import { OutgoingHttpHeaders } from 'http'; import is from '@sindresorhus/is'; import { logger } from '../../logger'; import { find } from '../../util/host-rules'; -import got, { GotJSONOptions } from '../../util/got'; +import { Http, HttpOptions } from '../../util/http'; import { maskToken } from '../../util/mask'; import { getNpmrc } from './npmrc'; import { DatasourceError, Release, ReleaseResult } from '../common'; import { id } from './common'; +const http = new Http(id); + let memcache = {}; export function resetMemCache(): void { @@ -127,14 +129,12 @@ export async function getDependency( try { const useCache = retries === 3; // Disable cache if we're retrying - const opts: GotJSONOptions = { - hostType: id, - json: true, - retry: 5, + const opts: HttpOptions = { headers, useCache, }; - const raw = await got(pkgUrl, opts); + // TODO: fix type + const raw = await http.getJson<any>(pkgUrl, opts); if (retries < 3) { logger.debug({ pkgUrl, retries }, 'Recovered from npm error'); } @@ -249,6 +249,7 @@ export async function getDependency( return null; } if (uri.host === 'registry.npmjs.org') { + // istanbul ignore if if ( (err.name === 'ParseError' || err.code === 'ECONNRESET' || diff --git a/lib/datasource/nuget/v2.ts b/lib/datasource/nuget/v2.ts index 89c8afbf39..86b765dc93 100644 --- a/lib/datasource/nuget/v2.ts +++ b/lib/datasource/nuget/v2.ts @@ -1,10 +1,12 @@ import { XmlDocument, XmlElement } from 'xmldoc'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { ReleaseResult } from '../common'; import { id } from './common'; +const http = new Http(id); + function getPkgProp(pkgInfo: XmlElement, propName: string): string { return pkgInfo.childNamed('m:properties').childNamed(`d:${propName}`).val; } @@ -20,17 +22,7 @@ export async function getPkgReleases( try { let pkgUrlList = `${feedUrl}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl`; do { - const pkgVersionsListRaw = await got(pkgUrlList, { - hostType: id, - }); - if (pkgVersionsListRaw.statusCode !== 200) { - logger.debug( - { dependency: pkgName, pkgVersionsListRaw }, - `nuget registry failure: status code != 200` - ); - return null; - } - + const pkgVersionsListRaw = await http.get(pkgUrlList); const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body); const pkgInfoList = pkgVersionsListDoc.childrenNamed('entry'); diff --git a/lib/datasource/nuget/v3.ts b/lib/datasource/nuget/v3.ts index 9bbb381014..75d57e170a 100644 --- a/lib/datasource/nuget/v3.ts +++ b/lib/datasource/nuget/v3.ts @@ -1,11 +1,13 @@ import * as semver from 'semver'; import { XmlDocument } from 'xmldoc'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { ReleaseResult } from '../common'; import { id } from './common'; +const http = new Http(id); + // https://api.nuget.org/v3/index.json is a default official nuget feed const defaultNugetFeed = 'https://api.nuget.org/v3/index.json'; const cacheNamespace = 'datasource-nuget'; @@ -29,17 +31,8 @@ export async function getQueryUrl(url: string): Promise<string | null> { } try { - const servicesIndexRaw = await got(url, { - json: true, - hostType: id, - }); - if (servicesIndexRaw.statusCode !== 200) { - logger.debug( - { dependency: url, servicesIndexRaw }, - `nuget registry failure: status code != 200` - ); - return null; - } + // TODO: fix types + const servicesIndexRaw = await http.getJson<any>(url); const searchQueryService = servicesIndexRaw.body.resources.find( resource => resource['@type'] && resource['@type'].startsWith(resourceType) @@ -78,18 +71,8 @@ export async function getPkgReleases( releases: [], }; try { - const pkgUrlListRaw = await got(queryUrl, { - json: true, - hostType: id, - }); - if (pkgUrlListRaw.statusCode !== 200) { - logger.debug( - { dependency: pkgName, pkgUrlListRaw }, - `nuget registry failure: status code != 200` - ); - return null; - } - + // TODO: fix types + const pkgUrlListRaw = await http.getJson<any>(queryUrl); const match = pkgUrlListRaw.body.data.find( item => item.id.toLowerCase() === pkgName.toLowerCase() ); @@ -121,7 +104,7 @@ export async function getPkgReleases( const nugetOrgApi = `https://api.nuget.org/v3-flatcontainer/${pkgName.toLowerCase()}/${lastVersion}/${pkgName.toLowerCase()}.nuspec`; let metaresult: { body: string }; try { - metaresult = await got(nugetOrgApi, { hostType: id }); + metaresult = await http.get(nugetOrgApi); } catch (err) /* istanbul ignore next */ { logger.debug( `Cannot fetch metadata for ${pkgName} using popped version ${lastVersion}` diff --git a/lib/datasource/orb/index.spec.ts b/lib/datasource/orb/index.spec.ts index 5ede9da3e9..b491e0793c 100644 --- a/lib/datasource/orb/index.spec.ts +++ b/lib/datasource/orb/index.spec.ts @@ -34,7 +34,7 @@ describe('datasource/orb', () => { return global.renovateCache.rmAll(); }); it('returns null for empty result', async () => { - got.post.mockReturnValueOnce({ body: {} }); + got.mockReturnValueOnce({ body: {} }); expect( await datasource.getPkgReleases({ lookupName: 'hyper-expanse/library-release-workflows', @@ -42,7 +42,7 @@ describe('datasource/orb', () => { ).toBeNull(); }); it('returns null for missing orb', async () => { - got.post.mockReturnValueOnce({ body: { data: {} } }); + got.mockReturnValueOnce({ body: { data: {} } }); expect( await datasource.getPkgReleases({ lookupName: 'hyper-expanse/library-release-wonkflows', @@ -72,7 +72,7 @@ describe('datasource/orb', () => { ).toBeNull(); }); it('processes real data', async () => { - got.post.mockReturnValueOnce({ + got.mockReturnValueOnce({ body: orbData, }); const res = await datasource.getPkgReleases({ @@ -83,7 +83,7 @@ describe('datasource/orb', () => { }); it('processes homeUrl', async () => { orbData.data.orb.homeUrl = 'https://google.com'; - got.post.mockReturnValueOnce({ + got.mockReturnValueOnce({ body: orbData, }); const res = await datasource.getPkgReleases({ diff --git a/lib/datasource/orb/index.ts b/lib/datasource/orb/index.ts index 2c6903b3b8..9d7388966b 100644 --- a/lib/datasource/orb/index.ts +++ b/lib/datasource/orb/index.ts @@ -1,9 +1,11 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'orb'; +const http = new Http(id); + interface OrbRelease { homeUrl?: string; versions: { @@ -38,11 +40,8 @@ export async function getPkgReleases({ }; try { const res: OrbRelease = ( - await got.post(url, { + await http.postJson<{ data: { orb: OrbRelease } }>(url, { body, - hostType: id, - json: true, - retry: 5, }) ).body.data.orb; if (!res) { diff --git a/lib/datasource/packagist/index.ts b/lib/datasource/packagist/index.ts index 8c989a7ca3..0755d2a4aa 100644 --- a/lib/datasource/packagist/index.ts +++ b/lib/datasource/packagist/index.ts @@ -4,17 +4,17 @@ import URL from 'url'; import delay from 'delay'; import pAll from 'p-all'; import { logger } from '../../logger'; - -import got, { GotJSONOptions } from '../../util/got'; +import { Http, HttpOptions } from '../../util/http'; import * as hostRules from '../../util/host-rules'; import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'packagist'; -function getHostOpts(url: string): GotJSONOptions { - const opts: GotJSONOptions = { - json: true, - }; +const http = new Http(id); + +// We calculate auth at this datasource layer so that we can know whether it's safe to cache or not +function getHostOpts(url: string): HttpOptions { + const opts: HttpOptions = {}; const { username, password } = hostRules.find({ hostType: id, url, @@ -47,7 +47,7 @@ async function getRegistryMeta(regUrl: string): Promise<RegistryMeta | null> { try { const url = URL.resolve(regUrl.replace(/\/?$/, '/'), 'packages.json'); const opts = getHostOpts(url); - const res: PackageMeta = (await got(url, opts)).body; + const res = (await http.getJson<PackageMeta>(url, opts)).body; const meta: RegistryMeta = {}; meta.packages = res.packages; if (res.includes) { @@ -107,7 +107,8 @@ async function getPackagistFile( const fileName = key.replace('%hash%', sha256); const opts = getHostOpts(regUrl); if (opts.auth || (opts.headers && opts.headers.authorization)) { - return (await got(regUrl + '/' + fileName, opts)).body; + return (await http.getJson<PackagistFile>(regUrl + '/' + fileName, opts)) + .body; } const cacheNamespace = 'datasource-packagist-files'; const cacheKey = regUrl + key; @@ -117,7 +118,8 @@ async function getPackagistFile( if (cachedResult && cachedResult.sha256 === sha256) { return cachedResult.res; } - const res = (await got(regUrl + '/' + fileName, opts)).body; + const res = (await http.getJson<PackagistFile>(regUrl + '/' + fileName, opts)) + .body; const cacheMinutes = 1440; // 1 day await renovateCache.set( cacheNamespace, @@ -223,12 +225,8 @@ async function packagistOrgLookup(name: string): Promise<ReleaseResult> { let dep: ReleaseResult = null; const regUrl = 'https://packagist.org'; const pkgUrl = URL.resolve(regUrl, `/p/${name}.json`); - const res = ( - await got(pkgUrl, { - json: true, - retry: 5, - }) - ).body.packages[name]; + // TODO: fix types + const res = (await http.getJson<any>(pkgUrl)).body.packages[name]; if (res) { dep = extractDepReleases(res); dep.name = name; @@ -276,7 +274,10 @@ async function packageLookup( .replace('%hash%', providerPackages[name]) ); const opts = getHostOpts(regUrl); - const versions = (await got(pkgUrl, opts)).body.packages[name]; + // TODO: fix types + const versions = (await http.getJson<any>(pkgUrl, opts)).body.packages[ + name + ]; const dep = extractDepReleases(versions); dep.name = name; logger.trace({ dep }, 'dep'); diff --git a/lib/datasource/pypi/__snapshots__/index.spec.ts.snap b/lib/datasource/pypi/__snapshots__/index.spec.ts.snap index b1e8f65fac..7e9db5af86 100644 --- a/lib/datasource/pypi/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/pypi/__snapshots__/index.spec.ts.snap @@ -215,23 +215,15 @@ Object { exports[`datasource/pypi getPkgReleases supports custom datasource url 1`] = ` Array [ Array [ - Url { - "auth": null, - "hash": null, - "host": "custom.pypi.net", - "hostname": "custom.pypi.net", - "href": "https://custom.pypi.net/foo/azure-cli-monitor/json", - "path": "/foo/azure-cli-monitor/json", - "pathname": "/foo/azure-cli-monitor/json", - "port": null, - "protocol": "https:", - "query": null, - "search": null, - "slashes": true, - }, + "https://custom.pypi.net/foo/azure-cli-monitor/json", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, "hostType": "pypi", - "json": true, + "method": "get", }, ], ] @@ -240,23 +232,15 @@ Array [ exports[`datasource/pypi getPkgReleases supports custom datasource url from environmental variable 1`] = ` Array [ Array [ - Url { - "auth": null, - "hash": null, - "host": "my.pypi.python", - "hostname": "my.pypi.python", - "href": "https://my.pypi.python/pypi/azure-cli-monitor/json", - "path": "/pypi/azure-cli-monitor/json", - "pathname": "/pypi/azure-cli-monitor/json", - "port": null, - "protocol": "https:", - "query": null, - "search": null, - "slashes": true, - }, + "https://my.pypi.python/pypi/azure-cli-monitor/json", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, "hostType": "pypi", - "json": true, + "method": "get", }, ], ] @@ -265,43 +249,27 @@ Array [ exports[`datasource/pypi getPkgReleases supports multiple custom datasource urls 1`] = ` Array [ Array [ - Url { - "auth": null, - "hash": null, - "host": "custom.pypi.net", - "hostname": "custom.pypi.net", - "href": "https://custom.pypi.net/foo/azure-cli-monitor/json", - "path": "/foo/azure-cli-monitor/json", - "pathname": "/foo/azure-cli-monitor/json", - "port": null, - "protocol": "https:", - "query": null, - "search": null, - "slashes": true, - }, + "https://custom.pypi.net/foo/azure-cli-monitor/json", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, "hostType": "pypi", - "json": true, + "method": "get", }, ], Array [ - Url { - "auth": null, - "hash": null, - "host": "second-index", - "hostname": "second-index", - "href": "https://second-index/foo/azure-cli-monitor/json", - "path": "/foo/azure-cli-monitor/json", - "pathname": "/foo/azure-cli-monitor/json", - "port": null, - "protocol": "https:", - "query": null, - "search": null, - "slashes": true, - }, + "https://second-index/foo/azure-cli-monitor/json", Object { + "hooks": Object { + "beforeRedirect": Array [ + [Function], + ], + }, "hostType": "pypi", - "json": true, + "method": "get", }, ], ] diff --git a/lib/datasource/pypi/index.ts b/lib/datasource/pypi/index.ts index ba210f0536..bbb7a30884 100644 --- a/lib/datasource/pypi/index.ts +++ b/lib/datasource/pypi/index.ts @@ -3,11 +3,13 @@ import url from 'url'; import { parse } from 'node-html-parser'; import { logger } from '../../logger'; import { matches } from '../../versioning/pep440'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'pypi'; +const http = new Http(id); + function normalizeName(input: string): string { return input.toLowerCase().replace(/(-|\.)/g, '_'); } @@ -39,10 +41,8 @@ async function getDependency( const lookupUrl = url.resolve(hostUrl, `${packageName}/json`); const dependency: ReleaseResult = { releases: null }; logger.trace({ lookupUrl }, 'Pypi api got lookup'); - const rep = await got(url.parse(lookupUrl), { - json: true, - hostType: id, - }); + // TODO: fix type + const rep = await http.getJson<any>(lookupUrl); const dep = rep && rep.body; if (!dep) { logger.trace({ dependency: packageName }, 'pip package not found'); @@ -123,9 +123,7 @@ async function getSimpleDependency( const lookupUrl = url.resolve(hostUrl, `${packageName}`); try { const dependency: ReleaseResult = { releases: null }; - const response = await got<string>(url.parse(lookupUrl), { - hostType: id, - }); + const response = await http.get(lookupUrl); const dep = response && response.body; if (!dep) { logger.trace({ dependency: packageName }, 'pip package not found'); diff --git a/lib/datasource/ruby-version/index.ts b/lib/datasource/ruby-version/index.ts index f19c3270be..d449ae2f2c 100644 --- a/lib/datasource/ruby-version/index.ts +++ b/lib/datasource/ruby-version/index.ts @@ -1,11 +1,13 @@ import { parse } from 'node-html-parser'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { isVersion } from '../../versioning/ruby'; import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'ruby-version'; +const http = new Http(id); + const rubyVersionsUrl = 'https://www.ruby-lang.org/en/downloads/releases/'; export async function getPkgReleases( @@ -27,7 +29,7 @@ export async function getPkgReleases( sourceUrl: 'https://github.com/ruby/ruby', releases: [], }; - const response = await got(rubyVersionsUrl); + const response = await http.get(rubyVersionsUrl); const root: any = parse(response.body); const rows = root.querySelector('.release-list').querySelectorAll('tr'); for (const row of rows) { diff --git a/lib/datasource/rubygems/get-rubygems-org.ts b/lib/datasource/rubygems/get-rubygems-org.ts index fa9f2d94e1..71d5916283 100644 --- a/lib/datasource/rubygems/get-rubygems-org.ts +++ b/lib/datasource/rubygems/get-rubygems-org.ts @@ -1,7 +1,10 @@ import { hrtime } from 'process'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; import { DatasourceError, ReleaseResult } from '../common'; +import { id } from './common'; + +const http = new Http(id); let lastSync = new Date('2000-01-01'); let packageReleases: Record<string, string[]> = Object.create(null); // Because we might need a "constructor" key @@ -22,7 +25,7 @@ async function updateRubyGemsVersions(): Promise<void> { try { logger.debug('Rubygems: Fetching rubygems.org versions'); const startTime = hrtime(); - newLines = (await got(url, options)).body; + newLines = (await http.get(url, options)).body; const duration = hrtime(startTime); const seconds = Math.round(duration[0] + duration[1] / 1e9); logger.debug({ seconds }, 'Rubygems: Fetched rubygems.org versions'); diff --git a/lib/datasource/rubygems/get.ts b/lib/datasource/rubygems/get.ts index 9a32f01c65..fec4e2cca3 100644 --- a/lib/datasource/rubygems/get.ts +++ b/lib/datasource/rubygems/get.ts @@ -1,12 +1,13 @@ import { OutgoingHttpHeaders } from 'http'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { maskToken } from '../../util/mask'; -import retriable from './retriable'; import { UNAUTHORIZED, FORBIDDEN, NOT_FOUND } from './errors'; import { ReleaseResult } from '../common'; import { id } from './common'; +const http = new Http(id); + const INFO_PATH = '/api/v1/gems'; const VERSIONS_PATH = '/api/v1/versions'; @@ -35,16 +36,13 @@ const getHeaders = (): OutgoingHttpHeaders => { }; const fetch = async ({ dependency, registry, path }): Promise<any> => { - const json = true; - - const retry = { retries: retriable() }; const headers = getHeaders(); const name = `${path}/${dependency}.json`; const baseUrl = registry; logger.trace({ dependency }, `RubyGems lookup request: ${baseUrl} ${name}`); - const response = (await got(name, { retry, json, baseUrl, headers })) || { + const response = (await http.getJson(name, { baseUrl, headers })) || { body: undefined, }; diff --git a/lib/datasource/rubygems/retriable.spec.ts b/lib/datasource/rubygems/retriable.spec.ts deleted file mode 100644 index 19ddba9fe7..0000000000 --- a/lib/datasource/rubygems/retriable.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import retriable from './retriable'; - -describe('datasource/rubygems/retriable', () => { - it('returns 0 when numberOfRetries equals 0', () => { - expect(retriable(0)(null, null)).toEqual(0); - }); - - it('returns retry after header + 1 second if request is banned', () => { - expect( - retriable(1)(null, { - statusCode: 429, - headers: { 'retry-after': '5' }, - }) - ).toEqual(6000); - - expect( - retriable(1)(null, { - statusCode: 503, - headers: { 'retry-after': '9' }, - }) - ).toEqual(10000); - }); - - it('returns default delay if request is not banned', () => { - expect(retriable(1)(null, { statusCode: 500 })).toEqual(2000); - }); - - it('uses default numberOfRetries', () => { - expect(retriable()(null, { statusCode: 500 })).toEqual(1000); - }); -}); diff --git a/lib/datasource/rubygems/retriable.ts b/lib/datasource/rubygems/retriable.ts deleted file mode 100644 index 8d85ed2c4e..0000000000 --- a/lib/datasource/rubygems/retriable.ts +++ /dev/null @@ -1,63 +0,0 @@ -import got from 'got'; -import { logger } from '../../logger'; -import { - UNAUTHORIZED, - FORBIDDEN, - REQUEST_TIMEOUT, - TOO_MANY_REQUEST, - SERVICE_UNAVAILABLE, -} from './errors'; - -const DEFAULT_BANNED_RETRY_AFTER = 600; -const NUMBER_OF_RETRIES = 2; - -const getBannedDelay = (retryAfter: string): number => - (parseInt(retryAfter, 10) || DEFAULT_BANNED_RETRY_AFTER) + 1; -const getDefaultDelay = (count: number): number => - +(NUMBER_OF_RETRIES / count).toFixed(3); - -const getErrorMessage = (status: number): string => { - // istanbul ignore next - switch (status) { - case UNAUTHORIZED: - case FORBIDDEN: - return `RubyGems registry: Authentication failed.`; - case TOO_MANY_REQUEST: - return `RubyGems registry: Too Many Requests.`; - case REQUEST_TIMEOUT: - case SERVICE_UNAVAILABLE: - return `RubyGems registry: Temporary Unavailable`; - default: - return `RubyGems registry: Internal Server Error`; - } -}; - -// TODO: workaround because got does not export HTTPError, should be moved to `lib/util/got` -export type HTTPError = InstanceType<got.GotInstance['HTTPError']>; - -export default (numberOfRetries = NUMBER_OF_RETRIES): got.RetryFunction => ( - _?: number, - err?: Partial<HTTPError> -): number => { - if (numberOfRetries === 0) { - return 0; - } - - const { headers, statusCode } = err; - - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After - const isBanned = [TOO_MANY_REQUEST, SERVICE_UNAVAILABLE].includes(statusCode); - const delaySec = isBanned - ? getBannedDelay(headers['retry-after']) - : getDefaultDelay(numberOfRetries); - - // eslint-disable-next-line - numberOfRetries--; - - const errorMessage = getErrorMessage(statusCode); - const message = `${errorMessage} Retry in ${delaySec} seconds.`; - - logger.debug(message); - - return delaySec * 1000; -}; diff --git a/lib/datasource/terraform-module/index.ts b/lib/datasource/terraform-module/index.ts index a9578d7753..5e44921767 100644 --- a/lib/datasource/terraform-module/index.ts +++ b/lib/datasource/terraform-module/index.ts @@ -1,10 +1,12 @@ import is from '@sindresorhus/is'; import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'terraform-module'; +const http = new Http(id); + interface RegistryRepository { registry: string; repository: string; @@ -72,12 +74,7 @@ export async function getPkgReleases({ return cachedResult; } try { - const res: TerraformRelease = ( - await got(pkgUrl, { - json: true, - hostType: id, - }) - ).body; + const res = (await http.getJson<TerraformRelease>(pkgUrl)).body; const returnedName = res.namespace + '/' + res.name + '/' + res.provider; if (returnedName !== repository) { logger.warn({ pkgUrl }, 'Terraform registry result mismatch'); diff --git a/lib/datasource/terraform-provider/index.ts b/lib/datasource/terraform-provider/index.ts index 0c3544d3bd..480e1d5769 100644 --- a/lib/datasource/terraform-provider/index.ts +++ b/lib/datasource/terraform-provider/index.ts @@ -1,9 +1,11 @@ import { logger } from '../../logger'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'terraform-provider'; +const http = new Http(id); + interface TerraformProvider { namespace: string; name: string; @@ -35,12 +37,7 @@ export async function getPkgReleases({ return cachedResult; } try { - const res: TerraformProvider = ( - await got(pkgUrl, { - json: true, - hostType: id, - }) - ).body; + const res = (await http.getJson<TerraformProvider>(pkgUrl)).body; // Simplify response before caching and returning const dep: ReleaseResult = { name: repository, diff --git a/lib/manager/bazel/update.ts b/lib/manager/bazel/update.ts index 2c0670b256..11e7efb6ef 100644 --- a/lib/manager/bazel/update.ts +++ b/lib/manager/bazel/update.ts @@ -1,9 +1,11 @@ import { fromStream } from 'hasha'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; import { UpdateDependencyConfig } from '../common'; import { regEx } from '../../util/regex'; +const http = new Http('bazel'); + function updateWithNewVersion( content: string, currentValue: string, @@ -52,7 +54,7 @@ async function getHashFromUrl(url: string): Promise<string | null> { return cachedResult; } try { - const hash = await fromStream(got.stream(url), { + const hash = await fromStream(http.stream(url), { algorithm: 'sha256', }); const cacheMinutes = 3 * 24 * 60; // 3 days diff --git a/lib/manager/gradle-wrapper/update.ts b/lib/manager/gradle-wrapper/update.ts index fe9e77d612..051c66b9cb 100644 --- a/lib/manager/gradle-wrapper/update.ts +++ b/lib/manager/gradle-wrapper/update.ts @@ -1,4 +1,4 @@ -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; import { UpdateDependencyConfig } from '../common'; import { @@ -7,14 +7,16 @@ import { VERSION_REGEX, } from './search'; +const http = new Http('gradle-wrapper'); + function replaceType(url: string): string { return url.replace('bin', 'all'); } async function getChecksum(url: string): Promise<string> { try { - const response = await got(url); - return response.body as string; + const response = await http.get(url); + return response.body; } catch (err) { if (err.statusCode === 404 || err.code === 'ENOTFOUND') { logger.debug('Gradle checksum lookup failure: not found'); diff --git a/lib/manager/homebrew/update.ts b/lib/manager/homebrew/update.ts index 39bb54ea97..cdd58b6665 100644 --- a/lib/manager/homebrew/update.ts +++ b/lib/manager/homebrew/update.ts @@ -2,10 +2,12 @@ import { fromStream } from 'hasha'; import { coerce } from 'semver'; import { parseUrlPath } from './extract'; import { skip, isSpace, removeComments } from './util'; -import got from '../../util/got'; +import { Http } from '../../util/http'; import { logger } from '../../logger'; import { UpdateDependencyConfig } from '../common'; +const http = new Http('homebrew'); + function replaceUrl( idx: number, content: string, @@ -160,7 +162,7 @@ export async function updateDependency({ }/releases/download/${upgrade.newValue}/${ upgrade.managerData.repoName }-${coerce(upgrade.newValue)}.tar.gz`; - newSha256 = await fromStream(got.stream(newUrl), { + newSha256 = await fromStream(http.stream(newUrl), { algorithm: 'sha256', }); } catch (errOuter) { @@ -169,7 +171,7 @@ export async function updateDependency({ ); try { newUrl = `https://github.com/${upgrade.managerData.ownerName}/${upgrade.managerData.repoName}/archive/${upgrade.newValue}.tar.gz`; - newSha256 = await fromStream(got.stream(newUrl), { + newSha256 = await fromStream(http.stream(newUrl), { algorithm: 'sha256', }); } catch (errInner) { diff --git a/lib/util/got/common.ts b/lib/util/got/common.ts index b431d7fbe9..23ff6abde7 100644 --- a/lib/util/got/common.ts +++ b/lib/util/got/common.ts @@ -2,6 +2,7 @@ import got from 'got'; import { Url } from 'url'; export interface Options { + hooks?: any; hostType?: string; search?: string; token?: string; diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts new file mode 100644 index 0000000000..0aa11e1c2c --- /dev/null +++ b/lib/util/http/index.ts @@ -0,0 +1,104 @@ +import is from '@sindresorhus/is/dist'; +import URL from 'url'; +import got from '../got'; + +interface OutgoingHttpHeaders { + [header: string]: number | string | string[] | undefined; +} + +export interface HttpOptions { + auth?: string; + baseUrl?: string; + headers?: OutgoingHttpHeaders; + throwHttpErrors?: boolean; + useCache?: boolean; +} + +export interface HttpPostOptions extends HttpOptions { + body: unknown; +} + +interface InternalHttpOptions extends HttpOptions { + json?: boolean; + method?: 'get' | 'post'; +} + +export interface HttpResponse<T = string> { + body: T; + headers: any; +} + +export class Http { + constructor(private hostType: string, private options?: HttpOptions) {} + + private async request( + url: string | URL, + options?: InternalHttpOptions + ): Promise<HttpResponse | null> { + let resolvedUrl = url.toString(); + if (options?.baseUrl) { + resolvedUrl = URL.resolve(options.baseUrl, resolvedUrl); + } + // TODO: deep merge in order to merge headers + const combinedOptions: any = { + method: 'get', + ...this.options, + hostType: this.hostType, + ...options, + }; + combinedOptions.hooks = { + beforeRedirect: [ + (opts: any): void => { + // Check if request has been redirected to Amazon + if (opts.search?.includes('X-Amz-Algorithm')) { + // if there is no port in the redirect URL string, then delete it from the redirect options. + // This can be evaluated for removal after upgrading to Got v10 + const portInUrl = opts.href.split('/')[2].split(':')[1]; + if (!portInUrl) { + // eslint-disable-next-line no-param-reassign + delete opts.port; // Redirect will instead use 80 or 443 for HTTP or HTTPS respectively + } + + // registry is hosted on amazon, redirect url includes authentication. + delete opts.headers.authorization; // eslint-disable-line no-param-reassign + delete opts.auth; // eslint-disable-line no-param-reassign + } + }, + ], + }; + const res = await got(resolvedUrl, combinedOptions); + return { body: res.body, headers: res.headers }; + } + + get(url: string, options: HttpOptions = {}): Promise<HttpResponse> { + return this.request(url, options); + } + + async getJson<T = unknown>( + url: string, + options: HttpOptions = {} + ): Promise<HttpResponse<T>> { + const res = await this.request(url, options); + const body = is.string(res.body) ? JSON.parse(res.body) : res.body; + return { ...res, body }; + } + + async postJson<T = unknown>( + url: string, + options: HttpPostOptions + ): Promise<HttpResponse<T>> { + const res = await this.request(url, { ...options, method: 'post' }); + const body = is.string(res.body) ? JSON.parse(res.body) : res.body; + return { ...res, body }; + } + + stream(url: string, options?: HttpOptions): NodeJS.ReadableStream { + const combinedOptions: any = { + method: 'get', + ...this.options, + hostType: this.hostType, + ...options, + }; + return got.stream(url, combinedOptions); + } +} diff --git a/tools/jest-gh-reporter.ts b/tools/jest-gh-reporter.ts index 856de776f5..8b33cc512d 100644 --- a/tools/jest-gh-reporter.ts +++ b/tools/jest-gh-reporter.ts @@ -135,8 +135,9 @@ class GitHubReporter extends BaseReporter { info(`repo: ${owner} / ${repo}`); info(`sha: ${ref}`); - const output: Octokit.ChecksUpdateParamsOutput = { + const output: Octokit.ChecksCreateParamsOutput = { summary: 'Jest test results', + title: 'Jest', }; if (annotations.length) { output.annotations = annotations; @@ -171,7 +172,7 @@ class GitHubReporter extends BaseReporter { head_sha: ref, completed_at: new Date().toISOString(), conclusion: success ? 'success' : 'failure', - output: { ...output, title: 'Jest' }, + output: { ...output }, }); } } -- GitLab