diff --git a/lib/config/presets/github/index.ts b/lib/config/presets/github/index.ts index 7363c3810dce02..2a781f073aea6e 100644 --- a/lib/config/presets/github/index.ts +++ b/lib/config/presets/github/index.ts @@ -1,6 +1,6 @@ -import { PLATFORM_FAILURE } from '../../../constants/error-messages'; import { PLATFORM_TYPE_GITHUB } from '../../../constants/platforms'; import { logger } from '../../../logger'; +import { ExternalHostError } from '../../../types/error'; import { Http, HttpOptions } from '../../../util/http'; import { Preset, PresetConfig } from '../common'; import { PRESET_DEP_NOT_FOUND, fetchPreset } from '../util'; @@ -27,7 +27,7 @@ export async function fetchJSONFile( res = await http.getJson(url, opts); } catch (err) { // istanbul ignore if: not testable with nock - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } logger.debug( diff --git a/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap b/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap index a04e3707f89e47..b79273bd383513 100644 --- a/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap @@ -70,7 +70,7 @@ Array [ ] `; -exports[`config/presets/gitlab/index getPreset() throws platform-failure 1`] = ` +exports[`config/presets/gitlab/index getPreset() throws EXTERNAL_HOST_ERROR 1`] = ` Array [ Object { "headers": Object { diff --git a/lib/config/presets/gitlab/index.spec.ts b/lib/config/presets/gitlab/index.spec.ts index e180961dd3fb92..2d48a835a59c22 100644 --- a/lib/config/presets/gitlab/index.spec.ts +++ b/lib/config/presets/gitlab/index.spec.ts @@ -1,6 +1,6 @@ import * as httpMock from '../../../../test/httpMock'; import { getName } from '../../../../test/util'; -import { PLATFORM_FAILURE } from '../../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../../constants/error-messages'; import { PRESET_DEP_NOT_FOUND } from '../util'; import * as gitlab from '.'; @@ -18,14 +18,14 @@ describe(getName(__filename), () => { }); describe('getPreset()', () => { - it('throws platform-failure', async () => { + it('throws EXTERNAL_HOST_ERROR', async () => { httpMock.scope(gitlabApiHost).get(`${basePath}/branches`).reply(500); await expect( gitlab.getPreset({ packageName: 'some/repo', presetName: 'non-default', }) - ).rejects.toThrow(PLATFORM_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/config/presets/gitlab/index.ts b/lib/config/presets/gitlab/index.ts index 7bec62cc2fff73..a5f7eb78211cf1 100644 --- a/lib/config/presets/gitlab/index.ts +++ b/lib/config/presets/gitlab/index.ts @@ -1,5 +1,5 @@ -import { PLATFORM_FAILURE } from '../../../constants/error-messages'; import { logger } from '../../../logger'; +import { ExternalHostError } from '../../../types/error'; import type { GitLabBranch } from '../../../types/platform/gitlab'; import { GitlabHttp } from '../../../util/http/gitlab'; import { Preset, PresetConfig } from '../common'; @@ -42,7 +42,7 @@ export async function fetchJSONFile( const url = `${endpoint}projects/${urlEncodedRepo}/repository/files/${urlEncodedPkgName}/raw?ref=${defautlBranchName}`; return (await gitlabApi.getJson(url)).body; } catch (err) { - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } logger.debug( diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 144d068c958344..3a9eea8fdfe6d1 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -1,10 +1,7 @@ import is from '@sindresorhus/is'; -import { - CONFIG_VALIDATION, - DATASOURCE_FAILURE, - PLATFORM_FAILURE, -} from '../../constants/error-messages'; +import { CONFIG_VALIDATION } from '../../constants/error-messages'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { regEx } from '../../util/regex'; import { RenovateConfig } from '../common'; import * as massage from '../massage'; @@ -207,10 +204,7 @@ export async function resolveConfigPresets( } catch (err) { logger.debug({ preset, err }, 'Preset fetch error'); // istanbul ignore if - if ( - err.message === PLATFORM_FAILURE || - err.message === DATASOURCE_FAILURE - ) { + if (err instanceof ExternalHostError) { throw err; } const error = new Error(CONFIG_VALIDATION); diff --git a/lib/constants/error-messages.ts b/lib/constants/error-messages.ts index b1bb53ef6de252..12ad1164c19456 100644 --- a/lib/constants/error-messages.ts +++ b/lib/constants/error-messages.ts @@ -5,7 +5,6 @@ export const SYSTEM_INSUFFICIENT_MEMORY = 'out-of-memory'; // Platform Error export const PLATFORM_AUTHENTICATION_ERROR = 'authentication-error'; export const PLATFORM_BAD_CREDENTIALS = 'bad-credentials'; -export const PLATFORM_FAILURE = 'platform-failure'; export const PLATFORM_GPG_FAILED = 'gpg-failed'; export const PLATFORM_INTEGRATION_UNAUTHORIZED = 'integration-unauthorized'; export const PLATFORM_NOT_FOUND = 'platform-not-found'; @@ -34,8 +33,8 @@ export const REPOSITORY_UNINITIATED = 'uninitiated'; export const MANAGER_LOCKFILE_ERROR = 'lockfile-error'; export const MANAGER_NO_PACKAGE_FILES = 'no-package-files'; -// Datasource error -export const DATASOURCE_FAILURE = 'registry-failure'; +// Host error +export const EXTERNAL_HOST_ERROR = 'external-host-error'; // Worker Error export const WORKER_FILE_UPDATE_FAILED = 'update-failure'; diff --git a/lib/datasource/cdnjs/index.spec.ts b/lib/datasource/cdnjs/index.spec.ts index 976b61003caad9..2ff92f7cbf5e6d 100644 --- a/lib/datasource/cdnjs/index.spec.ts +++ b/lib/datasource/cdnjs/index.spec.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import { getPkgReleases } from '..'; import * as httpMock from '../../../test/httpMock'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import { id as datasource } from '.'; let res1 = fs.readFileSync( @@ -36,14 +36,14 @@ describe('datasource/cdnjs', () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(200, null); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('throws for error', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error'); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for 404', async () => { @@ -70,28 +70,28 @@ describe('datasource/cdnjs', () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(401); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('throws for 429', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(429); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('throws for 5xx', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(502); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for unknown error', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error'); await expect( getPkgReleases({ datasource, depName: 'foo/bar' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('processes real data', async () => { diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts index 5b5f7f20264a72..c408bd0d147e71 100644 --- a/lib/datasource/cdnjs/index.ts +++ b/lib/datasource/cdnjs/index.ts @@ -1,7 +1,8 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http } from '../../util/http'; import { CachePromise, cacheAble } from '../cache'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'cdnjs'; @@ -60,7 +61,7 @@ export async function getReleases({ logger.debug({ library }, 'cdnjs library not found'); return null; } - // Throw a DatasourceError for all other types of errors - throw new DatasourceError(err); + // Throw an ExternalHostError for all other types of errors + throw new ExternalHostError(err); } } diff --git a/lib/datasource/common.ts b/lib/datasource/common.ts index 1d4be8b8c4d349..0f31ca9cb227ee 100644 --- a/lib/datasource/common.ts +++ b/lib/datasource/common.ts @@ -1,5 +1,3 @@ -import { DATASOURCE_FAILURE } from '../constants/error-messages'; - export interface Config { datasource?: string; depName?: string; @@ -80,18 +78,3 @@ export interface Datasource { defaultConfig?: object; registryStrategy?: 'first' | 'hunt' | 'merge'; } - -export class DatasourceError extends Error { - err: Error; - - datasource?: string; - - lookupName?: string; - - constructor(err: Error) { - super(DATASOURCE_FAILURE); - // Set the prototype explicitly: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work - Object.setPrototypeOf(this, DatasourceError.prototype); - this.err = err; - } -} diff --git a/lib/datasource/crate/__snapshots__/index.spec.ts.snap b/lib/datasource/crate/__snapshots__/index.spec.ts.snap index 63116b90b90601..f5d2a6e7250c73 100644 --- a/lib/datasource/crate/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/crate/__snapshots__/index.spec.ts.snap @@ -764,7 +764,7 @@ Array [ ] `; -exports[`datasource/crate getReleases throws for 5xx 1`] = `[Error: registry-failure]`; +exports[`datasource/crate getReleases throws for 5xx 1`] = `[Error: external-host-error]`; exports[`datasource/crate getReleases throws for 5xx 2`] = ` Array [ diff --git a/lib/datasource/crate/index.ts b/lib/datasource/crate/index.ts index fe948c1ab79079..40134c936da8ca 100644 --- a/lib/datasource/crate/index.ts +++ b/lib/datasource/crate/index.ts @@ -1,12 +1,8 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; -import { - DatasourceError, - GetReleasesConfig, - Release, - ReleaseResult, -} from '../common'; +import { GetReleasesConfig, Release, ReleaseResult } from '../common'; export const id = 'crate'; @@ -105,7 +101,7 @@ export async function getReleases({ err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } logger.warn({ err, lookupName }, 'crates.io lookup failure: Unknown error'); return null; diff --git a/lib/datasource/dart/__snapshots__/index.spec.ts.snap b/lib/datasource/dart/__snapshots__/index.spec.ts.snap index 39f878f58fd9a7..856d329c5d3f78 100644 --- a/lib/datasource/dart/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/dart/__snapshots__/index.spec.ts.snap @@ -144,7 +144,7 @@ Array [ ] `; -exports[`datasource/dart getReleases throws for 5xx 1`] = `[Error: registry-failure]`; +exports[`datasource/dart getReleases throws for 5xx 1`] = `[Error: external-host-error]`; exports[`datasource/dart getReleases throws for 5xx 2`] = ` Array [ diff --git a/lib/datasource/dart/index.ts b/lib/datasource/dart/index.ts index e080ef053f3b51..4043f1df5b3248 100644 --- a/lib/datasource/dart/index.ts +++ b/lib/datasource/dart/index.ts @@ -1,6 +1,7 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http, HttpResponse } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'dart'; @@ -33,7 +34,7 @@ export async function getReleases({ err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } logger.warn( { err, lookupName }, diff --git a/lib/datasource/docker/index.spec.ts b/lib/datasource/docker/index.spec.ts index 7d2c09c7ffe7d4..2f69ffa7d32564 100644 --- a/lib/datasource/docker/index.spec.ts +++ b/lib/datasource/docker/index.spec.ts @@ -2,7 +2,7 @@ import AWS from 'aws-sdk'; import AWSMock from 'aws-sdk-mock'; import { getDigest, getPkgReleases } from '..'; import * as httpMock from '../../../test/httpMock'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as _hostRules from '../../util/host-rules'; import * as docker from '.'; @@ -331,13 +331,13 @@ describe('api/docker', () => { httpMock.scope(baseUrl).get('/').replyWithError({ statusCode: 429 }); await expect( getDigest({ datasource: 'docker', depName: 'some-dep' }, 'latest') - ).rejects.toThrow(Error(DATASOURCE_FAILURE)); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('should throw error for 5xx', async () => { httpMock.scope(baseUrl).get('/').replyWithError({ statusCode: 504 }); await expect( getDigest({ datasource: 'docker', depName: 'some-dep' }, 'latest') - ).rejects.toThrow(Error(DATASOURCE_FAILURE)); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); }); describe('getReleases', () => { diff --git a/lib/datasource/docker/index.ts b/lib/datasource/docker/index.ts index 34536479d3370f..7a9e57c4199f41 100644 --- a/lib/datasource/docker/index.ts +++ b/lib/datasource/docker/index.ts @@ -6,10 +6,11 @@ import parseLinkHeader from 'parse-link-header'; import wwwAuthenticate from 'www-authenticate'; import { logger } from '../../logger'; import { HostRule } from '../../types'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import * as hostRules from '../../util/host-rules'; import { Http, HttpResponse } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; // TODO: add got typings when available // TODO: replace www-authenticate with https://www.npmjs.com/package/auth-header ? @@ -220,14 +221,14 @@ async function getAuthHeaders( } // prettier-ignore if (err.name === 'RequestError' && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization] - throw new DatasourceError(err); + throw new ExternalHostError(err); } // prettier-ignore if (err.statusCode === 429 && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization] - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.statusCode >= 500 && err.statusCode < 600) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } logger.warn( { registry, dockerRepository: repository, err }, @@ -267,7 +268,7 @@ async function getManifestResponse( }); return manifestResponse; } catch (err) /* istanbul ignore next */ { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } if (err.statusCode === 401) { @@ -292,10 +293,10 @@ async function getManifestResponse( } // prettier-ignore if (err.statusCode === 429 && registry.endsWith('docker.io')) { // lgtm [js/incomplete-url-substring-sanitization] - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.statusCode >= 500 && err.statusCode < 600) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.code === 'ETIMEDOUT') { logger.debug( @@ -356,7 +357,7 @@ export async function getDigest( logger.debug({ digest }, 'Got docker digest'); } } catch (err) /* istanbul ignore next */ { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } logger.debug( @@ -413,7 +414,7 @@ async function getTags( await globalCache.set(cacheNamespace, cacheKey, tags, cacheMinutes); return tags; } catch (err) /* istanbul ignore next */ { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } logger.debug( @@ -441,14 +442,14 @@ async function getTags( { registry, dockerRepository: repository, err }, 'docker registry failure: too many requests' ); - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.statusCode >= 500 && err.statusCode < 600) { logger.warn( { registry, dockerRepository: repository, err }, 'docker registry failure: internal error' ); - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.code === 'ETIMEDOUT') { logger.debug( @@ -543,7 +544,7 @@ async function getLabels( await globalCache.set(cacheNamespace, cacheKey, labels, cacheMinutes); return labels; } catch (err) { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } if (err.statusCode === 400 || err.statusCode === 401) { diff --git a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap index 1ecdb5c221d5cd..3e2f68cc8b7ee2 100644 --- a/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/galaxy/__snapshots__/index.spec.ts.snap @@ -119,7 +119,7 @@ Array [ ] `; -exports[`datasource/galaxy getReleases throws for 5xx 1`] = `[Error: registry-failure]`; +exports[`datasource/galaxy getReleases throws for 5xx 1`] = `[Error: external-host-error]`; exports[`datasource/galaxy getReleases throws for 5xx 2`] = ` Array [ diff --git a/lib/datasource/galaxy/index.ts b/lib/datasource/galaxy/index.ts index ce6b9e89643596..a76942dfd4c43f 100644 --- a/lib/datasource/galaxy/index.ts +++ b/lib/datasource/galaxy/index.ts @@ -1,12 +1,8 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; -import { - DatasourceError, - GetReleasesConfig, - Release, - ReleaseResult, -} from '../common'; +import { GetReleasesConfig, Release, ReleaseResult } from '../common'; export const id = 'galaxy'; @@ -101,7 +97,7 @@ export async function getReleases({ err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } return null; } diff --git a/lib/datasource/gradle-version/index.ts b/lib/datasource/gradle-version/index.ts index aab1d9d61399d5..624bb27b5ea89e 100644 --- a/lib/datasource/gradle-version/index.ts +++ b/lib/datasource/gradle-version/index.ts @@ -1,7 +1,8 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http } from '../../util/http'; import { regEx } from '../../util/regex'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'gradle-version'; export const defaultRegistryUrls = ['https://services.gradle.org/versions/all']; @@ -50,7 +51,7 @@ export async function getReleases({ })); } catch (err) /* istanbul ignore next */ { if (err.host === 'services.gradle.org') { - throw new DatasourceError(err); + throw new ExternalHostError(err); } logger.debug({ err }, 'gradle-version err'); return null; diff --git a/lib/datasource/helm/__snapshots__/index.spec.ts.snap b/lib/datasource/helm/__snapshots__/index.spec.ts.snap index 8ae2aea4bbf36f..d348280ab66f46 100644 --- a/lib/datasource/helm/__snapshots__/index.spec.ts.snap +++ b/lib/datasource/helm/__snapshots__/index.spec.ts.snap @@ -244,7 +244,7 @@ Array [ ] `; -exports[`datasource/helm getReleases throws for 5xx 1`] = `[Error: registry-failure]`; +exports[`datasource/helm getReleases throws for 5xx 1`] = `[Error: external-host-error]`; exports[`datasource/helm getReleases throws for 5xx 2`] = ` Array [ diff --git a/lib/datasource/helm/index.ts b/lib/datasource/helm/index.ts index 4b684275219c16..5ba8ba238fa10e 100644 --- a/lib/datasource/helm/index.ts +++ b/lib/datasource/helm/index.ts @@ -1,10 +1,11 @@ import yaml from 'js-yaml'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; import { ensureTrailingSlash } from '../../util/url'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'helm'; @@ -60,7 +61,7 @@ export async function getRepositoryData( err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } // istanbul ignore if if (err.name === 'UnsupportedProtocolError') { diff --git a/lib/datasource/hex/index.spec.ts b/lib/datasource/hex/index.spec.ts index a751a20da5061b..14dabf83f2dfda 100644 --- a/lib/datasource/hex/index.spec.ts +++ b/lib/datasource/hex/index.spec.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import { getPkgReleases } from '..'; import * as httpMock from '../../../test/httpMock'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as _hostRules from '../../util/host-rules'; import { id as datasource } from '.'; @@ -67,14 +67,14 @@ describe('datasource/hex', () => { httpMock.scope(baseUrl).get('/some_crate').reply(429); await expect( getPkgReleases({ datasource, depName: 'some_crate' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('throws for 5xx', async () => { httpMock.scope(baseUrl).get('/some_crate').reply(502); await expect( getPkgReleases({ datasource, depName: 'some_crate' }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for unknown error', async () => { diff --git a/lib/datasource/hex/index.ts b/lib/datasource/hex/index.ts index dc614c97ee1897..c619cbffc0ba13 100644 --- a/lib/datasource/hex/index.ts +++ b/lib/datasource/hex/index.ts @@ -1,6 +1,7 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'hex'; @@ -69,7 +70,7 @@ export async function getReleases({ err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.statusCode === 401) { diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts index 974003a4b0e4e9..34aa6531a7a9bc 100644 --- a/lib/datasource/index.spec.ts +++ b/lib/datasource/index.spec.ts @@ -1,7 +1,7 @@ import { mocked } from '../../test/util'; -import { DATASOURCE_FAILURE } from '../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../constants/error-messages'; +import { ExternalHostError } from '../types/error'; import { loadModules } from '../util/modules'; -import { DatasourceError } from './common'; import * as datasourceDocker from './docker'; import * as datasourceGithubTags from './github-tags'; import * as datasourceMaven from './maven'; @@ -131,9 +131,9 @@ describe('datasource/index', () => { }); expect(res).not.toBeNull(); }); - it('hunts registries and aborts on DatasourceError', async () => { + it('hunts registries and aborts on ExternalHostError', async () => { packagistDatasource.getReleases.mockImplementationOnce(() => { - throw new DatasourceError(new Error()); + throw new ExternalHostError(new Error()); }); await expect( datasource.getPkgReleases({ @@ -141,7 +141,7 @@ describe('datasource/index', () => { depName: 'something', registryUrls: ['https://reg1.com', 'https://reg2.io'], }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('hunts registries and passes on error', async () => { packagistDatasource.getReleases.mockImplementationOnce(() => { @@ -173,9 +173,9 @@ describe('datasource/index', () => { expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(2); }); - it('merges registries and aborts on DatasourceError', async () => { + it('merges registries and aborts on ExternalHostError', async () => { mavenDatasource.getReleases.mockImplementationOnce(() => { - throw new DatasourceError(new Error()); + throw new ExternalHostError(new Error()); }); await expect( datasource.getPkgReleases({ @@ -183,7 +183,7 @@ describe('datasource/index', () => { depName: 'something', registryUrls: ['https://reg1.com', 'https://reg2.io'], }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('merges registries and passes on error', async () => { mavenDatasource.getReleases.mockImplementationOnce(() => { diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts index 68cd63b9512783..dba7f32e5fef06 100644 --- a/lib/datasource/index.ts +++ b/lib/datasource/index.ts @@ -1,13 +1,13 @@ import is from '@sindresorhus/is'; import _ from 'lodash'; import { logger } from '../logger'; +import { ExternalHostError } from '../types/error'; import * as runCache from '../util/cache/run'; import { clone } from '../util/clone'; import * as allVersioning from '../versioning'; import datasources from './api.generated'; import { Datasource, - DatasourceError, DigestConfig, GetPkgReleasesConfig, GetReleasesConfig, @@ -65,7 +65,7 @@ async function huntRegistries( break; } } catch (err) { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } // We'll always save the last-thrown error @@ -100,7 +100,7 @@ async function mergeRegistries( combinedRes = res; } } catch (err) { - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { throw err; } // We'll always save the last-thrown error @@ -221,8 +221,8 @@ export async function getPkgReleases( }) ); } catch (e) /* istanbul ignore next */ { - if (e instanceof DatasourceError) { - e.datasource = config.datasource; + if (e instanceof ExternalHostError) { + e.hostType = config.datasource; e.lookupName = lookupName; } throw e; diff --git a/lib/datasource/maven/index.spec.ts b/lib/datasource/maven/index.spec.ts index bbe3d11b6d0709..4349d4ba29f149 100644 --- a/lib/datasource/maven/index.spec.ts +++ b/lib/datasource/maven/index.spec.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import { resolve } from 'path'; import nock from 'nock'; import { getPkgReleases } from '..'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as hostRules from '../../util/host-rules'; import * as mavenVersioning from '../../versioning/maven'; import { id as datasource } from '.'; @@ -182,7 +182,7 @@ describe('datasource/maven', () => { expect(releases.releases).toEqual(generateReleases(MYSQL_VERSIONS)); }); - it('should throw registry-failure if default maven repo fails', async () => { + it('should throw external-host-error if default maven repo fails', async () => { nock('https://repo.maven.apache.org') .get('/maven2/org/artifact/maven-metadata.xml') .times(4) @@ -195,7 +195,7 @@ describe('datasource/maven', () => { depName: 'org:artifact', registryUrls: ['https://repo.maven.apache.org/maven2/'], }) - ).rejects.toThrow(Error(DATASOURCE_FAILURE)); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('should return all versions of a specific library if a repository fails because invalid protocol', async () => { diff --git a/lib/datasource/maven/util.ts b/lib/datasource/maven/util.ts index 4639aea1c39302..bb03867e9a390f 100644 --- a/lib/datasource/maven/util.ts +++ b/lib/datasource/maven/util.ts @@ -1,7 +1,7 @@ import url from 'url'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http } from '../../util/http'; -import { DatasourceError } from '../common'; import { MAVEN_REPO, id } from './common'; @@ -77,7 +77,7 @@ export async function downloadHttpProtocol( } else if (isTemporalError(err)) { logger.debug({ failedUrl, err }, 'Temporary error'); if (isMavenCentral(pkgUrl)) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } } else if (isConnectionError(err)) { // istanbul ignore next diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts index 2d394b010d4ade..ec305f257982ec 100644 --- a/lib/datasource/npm/get.spec.ts +++ b/lib/datasource/npm/get.spec.ts @@ -1,6 +1,6 @@ import * as httpMock from '../../../test/httpMock'; import { getName } from '../../../test/util'; -import { DatasourceError } from '../common'; +import { ExternalHostError } from '../../types/error'; import { getDependency, resetMemCache } from './get'; import { setNpmrc } from './npmrc'; @@ -150,7 +150,7 @@ describe(getName(__filename), () => { .get('/npm-parse-error') .reply(200, 'not-a-json'); await expect(getDependency('npm-parse-error', 0)).rejects.toThrow( - DatasourceError + ExternalHostError ); httpMock diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts index d2f0e2ab362cc6..dee919732af7e7 100644 --- a/lib/datasource/npm/get.ts +++ b/lib/datasource/npm/get.ts @@ -6,11 +6,12 @@ import moment from 'moment'; import registryAuthToken from 'registry-auth-token'; import getRegistryUrl from 'registry-auth-token/registry-url'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { find } from '../../util/host-rules'; import { Http, HttpOptions } from '../../util/http'; import { maskToken } from '../../util/mask'; -import { DatasourceError, Release, ReleaseResult } from '../common'; +import { Release, ReleaseResult } from '../common'; import { id } from './common'; import { getNpmrc } from './npmrc'; @@ -280,7 +281,7 @@ export async function getDependency( if (err.name === 'ParseError' && err.body) { err.body = 'err.body deleted by Renovate'; } - throw new DatasourceError(err); + throw new ExternalHostError(err); } return null; } diff --git a/lib/datasource/npm/index.spec.ts b/lib/datasource/npm/index.spec.ts index f2106aacbc6c1e..00dfde3e164bce 100644 --- a/lib/datasource/npm/index.spec.ts +++ b/lib/datasource/npm/index.spec.ts @@ -3,7 +3,7 @@ import nock from 'nock'; import _registryAuthToken from 'registry-auth-token'; import { getPkgReleases } from '..'; import { getName } from '../../../test/util'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as hostRules from '../../util/host-rules'; import { id as datasource, getNpmrc, resetCache, setNpmrc } from '.'; @@ -204,13 +204,13 @@ describe(getName(__filename), () => { nock('https://registry.npmjs.org').get('/foobar').reply(503); await expect( getPkgReleases({ datasource, depName: 'foobar' }) - ).rejects.toThrow(Error(DATASOURCE_FAILURE)); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('should throw error for 408', async () => { nock('https://registry.npmjs.org').get('/foobar').reply(408); await expect( getPkgReleases({ datasource, depName: 'foobar' }) - ).rejects.toThrow(Error(DATASOURCE_FAILURE)); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); it('should throw error for others', async () => { nock('https://registry.npmjs.org').get('/foobar').reply(451); diff --git a/lib/datasource/packagist/index.ts b/lib/datasource/packagist/index.ts index 77946c2bb4cb3d..7fce259e40b1d5 100644 --- a/lib/datasource/packagist/index.ts +++ b/lib/datasource/packagist/index.ts @@ -2,11 +2,12 @@ import URL from 'url'; import pAll from 'p-all'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import * as runCache from '../../util/cache/run'; import * as hostRules from '../../util/host-rules'; import { Http, HttpOptions } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'packagist'; export const defaultRegistryUrls = ['https://packagist.org']; @@ -313,10 +314,10 @@ async function packageLookup( } if (err.host === 'packagist.org') { if (err.code === 'ECONNRESET' || err.code === 'ETIMEDOUT') { - throw new DatasourceError(err); + throw new ExternalHostError(err); } if (err.statusCode && err.statusCode >= 500 && err.statusCode < 600) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } } logger.warn({ err, name }, 'packagist registry failure: Unknown error'); diff --git a/lib/datasource/pod/index.spec.ts b/lib/datasource/pod/index.spec.ts index 7a637e56befb68..780f51d935b4d6 100644 --- a/lib/datasource/pod/index.spec.ts +++ b/lib/datasource/pod/index.spec.ts @@ -1,5 +1,6 @@ import { getPkgReleases } from '..'; import * as httpMock from '../../../test/httpMock'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import * as rubyVersioning from '../../versioning/ruby'; import * as pod from '.'; @@ -63,7 +64,7 @@ describe('datasource/cocoapods', () => { .scope(cocoapodsHost) .get('/all_pods_versions_a_c_b.txt') .reply(429); - await expect(getPkgReleases(config)).rejects.toThrow('registry-failure'); + await expect(getPkgReleases(config)).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); it('returns null for unknown error', async () => { diff --git a/lib/datasource/pod/index.ts b/lib/datasource/pod/index.ts index 24b0361f875175..391719f73187cb 100644 --- a/lib/datasource/pod/index.ts +++ b/lib/datasource/pod/index.ts @@ -1,5 +1,6 @@ import crypto from 'crypto'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; import { GithubHttp } from '../../util/http/github'; @@ -44,7 +45,7 @@ function handleError(lookupName: string, err: Error): void { (err.statusCode >= 500 && err.statusCode < 600) ) { logger.warn({ lookupName, err }, `CocoaPods registry failure`); - throw new Error('registry-failure'); + throw new ExternalHostError(err); } if (err.statusCode === 401) { diff --git a/lib/datasource/repology/index.spec.ts b/lib/datasource/repology/index.spec.ts index 56b34cdcab7086..bd08b9d04eb223 100644 --- a/lib/datasource/repology/index.spec.ts +++ b/lib/datasource/repology/index.spec.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import { getPkgReleases } from '..'; import * as httpMock from '../../../test/httpMock'; import { getName } from '../../../test/util'; -import { DATASOURCE_FAILURE } from '../../constants/error-messages'; +import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages'; import { id as versioning } from '../../versioning/loose'; import { RepologyPackage, id as datasource } from '.'; @@ -127,7 +127,7 @@ describe(getName(__filename), () => { versioning, depName: 'debian_stable/nginx', }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -140,7 +140,7 @@ describe(getName(__filename), () => { versioning, depName: 'debian_stable/nginx', }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -151,7 +151,7 @@ describe(getName(__filename), () => { versioning, depName: 'invalid-lookup-name', }) - ).rejects.toThrow(DATASOURCE_FAILURE); + ).rejects.toThrow(EXTERNAL_HOST_ERROR); expect(httpMock.getTrace()).toMatchSnapshot(); }); diff --git a/lib/datasource/repology/index.ts b/lib/datasource/repology/index.ts index e45c10f021ed23..7af17eb7753cff 100644 --- a/lib/datasource/repology/index.ts +++ b/lib/datasource/repology/index.ts @@ -1,8 +1,9 @@ import { URLSearchParams } from 'url'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'repology'; @@ -117,7 +118,7 @@ export async function getReleases({ // Ensure lookup name contains both repository and package const [repoName, pkgName] = lookupName.split('/', 2); if (!repoName || !pkgName) { - throw new DatasourceError( + throw new ExternalHostError( new Error( 'Repology lookup name must contain repository and package separated by slash (/)' ) @@ -142,6 +143,6 @@ export async function getReleases({ { lookupName, err }, 'Repology lookup failed with unexpected error' ); - throw new DatasourceError(err); + throw new ExternalHostError(err); } } diff --git a/lib/datasource/ruby-version/index.ts b/lib/datasource/ruby-version/index.ts index c3315f04e16dc2..d85ee6cec9d591 100644 --- a/lib/datasource/ruby-version/index.ts +++ b/lib/datasource/ruby-version/index.ts @@ -1,9 +1,10 @@ import { parse } from 'node-html-parser'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; import { isVersion } from '../../versioning/ruby'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'ruby-version'; @@ -51,6 +52,6 @@ export async function getReleases( await globalCache.set(cacheNamespace, 'all', res, 15); return res; } catch (err) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } } diff --git a/lib/datasource/rubygems/get-rubygems-org.ts b/lib/datasource/rubygems/get-rubygems-org.ts index 3eb01a4ed97a88..f3005573227fa5 100644 --- a/lib/datasource/rubygems/get-rubygems-org.ts +++ b/lib/datasource/rubygems/get-rubygems-org.ts @@ -1,6 +1,7 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http } from '../../util/http'; -import { DatasourceError, ReleaseResult } from '../common'; +import { ReleaseResult } from '../common'; import { id } from './common'; const http = new Http(id); @@ -38,7 +39,7 @@ async function updateRubyGemsVersions(): Promise { if (err.statusCode !== 416) { contentLength = 0; packageReleases = Object.create(null); // Because we might need a "constructor" key - throw new DatasourceError( + throw new ExternalHostError( new Error('Rubygems fetch error - need to reset cache') ); } diff --git a/lib/datasource/terraform-module/index.ts b/lib/datasource/terraform-module/index.ts index da0bce26adee16..4cac9a7d805a35 100644 --- a/lib/datasource/terraform-module/index.ts +++ b/lib/datasource/terraform-module/index.ts @@ -1,7 +1,8 @@ import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as globalCache from '../../util/cache/global'; import { Http } from '../../util/http'; -import { DatasourceError, GetReleasesConfig, ReleaseResult } from '../common'; +import { GetReleasesConfig, ReleaseResult } from '../common'; export const id = 'terraform-module'; export const defaultRegistryUrls = ['https://registry.terraform.io']; @@ -113,7 +114,7 @@ export async function getReleases({ const failureCodes = ['EAI_AGAIN']; // istanbul ignore if if (failureCodes.includes(err.code)) { - throw new DatasourceError(err); + throw new ExternalHostError(err); } logger.warn( { err, lookupName }, diff --git a/lib/manager/gradle/index.ts b/lib/manager/gradle/index.ts index 0d1ea33e155350..2b1d02d87b1946 100644 --- a/lib/manager/gradle/index.ts +++ b/lib/manager/gradle/index.ts @@ -3,9 +3,9 @@ import * as os from 'os'; import * as fs from 'fs-extra'; import upath from 'upath'; import { LANGUAGE_JAVA } from '../../constants/languages'; -import { DatasourceError } from '../../datasource'; import * as datasourceMaven from '../../datasource/maven'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { ExecOptions, exec } from '../../util/exec'; import { BinarySource } from '../../util/exec/common'; import { readLocalFile } from '../../util/fs'; @@ -106,9 +106,7 @@ export async function executeGradle( ({ stdout, stderr } = await exec(cmd, execOptions)); } catch (err) /* istanbul ignore next */ { if (err.code === TIMEOUT_CODE) { - const error = new DatasourceError(err); - error.datasource = 'gradle'; - throw error; + throw new ExternalHostError(err, 'gradle'); } logger.warn({ errMessage: err.message }, 'Gradle extraction failed'); return; diff --git a/lib/manager/npm/post-update/index.ts b/lib/manager/npm/post-update/index.ts index 7ba4aae6f2832b..75cf85f9c3f6c7 100644 --- a/lib/manager/npm/post-update/index.ts +++ b/lib/manager/npm/post-update/index.ts @@ -3,9 +3,10 @@ import fs from 'fs-extra'; import upath from 'upath'; // eslint-disable-next-line import/no-unresolved import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages'; -import { DatasourceError } from '../../../datasource/common'; +import { id as npmId } from '../../../datasource/npm'; import { logger } from '../../../logger'; import { platform } from '../../../platform'; +import { ExternalHostError } from '../../../types/error'; import { getChildProcessEnv } from '../../../util/exec/env'; import { deleteLocalFile } from '../../../util/fs'; import * as hostRules from '../../../util/host-rules'; @@ -432,7 +433,7 @@ export async function getAdditionalFiles( const err = new Error( 'lock file failed for the dependency being updated - skipping branch creation' ); - throw new DatasourceError(err); + throw new ExternalHostError(err, npmId); } } } @@ -492,10 +493,11 @@ export async function getAdditionalFiles( { dependency: upgrade.depName, type: 'yarn' }, 'lock file failed for the dependency being updated - skipping branch creation' ); - throw new DatasourceError( + throw new ExternalHostError( new Error( 'lock file failed for the dependency being updated - skipping branch creation' - ) + ), + npmId ); } /* eslint-enable no-useless-escape */ @@ -594,10 +596,11 @@ export async function getAdditionalFiles( { dependency: upgrade.depName, type: 'pnpm' }, 'lock file failed for the dependency being updated - skipping branch creation' ); - throw new DatasourceError( + throw new ExternalHostError( Error( 'lock file failed for the dependency being updated - skipping branch creation' - ) + ), + npmId ); } } @@ -672,10 +675,11 @@ export async function getAdditionalFiles( { dependency: upgrade.depName, type: 'yarn' }, 'lock file failed for the dependency being updated - skipping branch creation' ); - throw new DatasourceError( + throw new ExternalHostError( Error( 'lock file failed for the dependency being updated - skipping branch creation' - ) + ), + npmId ); } /* eslint-enable no-useless-escape */ @@ -688,10 +692,11 @@ export async function getAdditionalFiles( { dependency: upgrade.depName, type: 'npm' }, 'lock file failed for the dependency being updated - skipping branch creation' ); - throw new DatasourceError( + throw new ExternalHostError( Error( 'lock file failed for the dependency being updated - skipping branch creation' - ) + ), + npmId ); } } diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts index 81d2d6e8d7e32a..1dcc798c0b1884 100644 --- a/lib/manager/npm/post-update/yarn.ts +++ b/lib/manager/npm/post-update/yarn.ts @@ -4,8 +4,9 @@ import { validRange } from 'semver'; import { quote } from 'shlex'; import { join } from 'upath'; import { SYSTEM_INSUFFICIENT_DISK_SPACE } from '../../../constants/error-messages'; -import { DatasourceError } from '../../../datasource'; +import { id as npmId } from '../../../datasource/npm'; import { logger } from '../../../logger'; +import { ExternalHostError } from '../../../types/error'; import { ExecOptions, exec } from '../../../util/exec'; import { PostUpdateConfig, Upgrade } from '../../common'; import { getNodeConstraint } from './node-version'; @@ -151,7 +152,7 @@ export async function generateLockFile( err.stderr.includes('getaddrinfo ENOTFOUND registry.yarnpkg.com') || err.stderr.includes('getaddrinfo ENOTFOUND registry.npmjs.org') ) { - throw new DatasourceError(err); + throw new ExternalHostError(err, npmId); } } return { error: true, stderr: err.stderr }; diff --git a/lib/platform/git/storage.ts b/lib/platform/git/storage.ts index 1784abbfc1ebc3..b857ed7f36e753 100644 --- a/lib/platform/git/storage.ts +++ b/lib/platform/git/storage.ts @@ -4,13 +4,13 @@ import fs from 'fs-extra'; import Git from 'simple-git/promise'; import { CONFIG_VALIDATION, - PLATFORM_FAILURE, REPOSITORY_CHANGED, REPOSITORY_EMPTY, REPOSITORY_TEMPORARY_ERROR, SYSTEM_INSUFFICIENT_DISK_SPACE, } from '../../constants/error-messages'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import * as limits from '../../workers/global/limits'; import { CommitFilesConfig } from '../common'; import { writePrivateKey } from './private-key'; @@ -54,7 +54,7 @@ function checkForPlatformFailure(err: Error): void { ]; for (const errorStr of platformFailureStrings) { if (err.message.includes(errorStr)) { - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, 'git'); } } } @@ -176,7 +176,7 @@ export class Storage { if (err.message?.includes('write error: No space left on device')) { throw new Error(SYSTEM_INSUFFICIENT_DISK_SPACE); } - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, 'git'); } const durationMs = Math.round(Date.now() - cloneStart); logger.debug({ durationMs }, 'git clone completed'); diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 62f77f00ab60f4..644643b558675e 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -3,7 +3,6 @@ import is from '@sindresorhus/is'; import delay from 'delay'; import { configFileNames } from '../../config/app-strings'; import { - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, REPOSITORY_ACCESS_FORBIDDEN, REPOSITORY_ARCHIVED, @@ -24,6 +23,7 @@ import { } from '../../constants/pull-requests'; import { logger } from '../../logger'; import { BranchStatus } from '../../types'; +import { ExternalHostError } from '../../types/error'; import * as hostRules from '../../util/host-rules'; import * as githubHttp from '../../util/http/github'; import { sanitize } from '../../util/sanitize'; @@ -397,7 +397,7 @@ export async function initRepo({ } ); } catch (err) /* istanbul ignore next */ { - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } if ( @@ -965,7 +965,7 @@ export async function getPrList(): Promise { ); } catch (err) /* istanbul ignore next */ { logger.debug({ err }, 'getPrList err'); - throw new Error('platform-failure'); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } config.prList = res.body.map((pr) => ({ number: pr.number, @@ -1094,7 +1094,7 @@ export async function getBranchStatus( logger.debug({ result: checkRunsRaw }, 'No check runs found'); } } catch (err) /* istanbul ignore next */ { - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } if ( @@ -1506,7 +1506,7 @@ async function getComments(issueNo: number): Promise { } catch (err) /* istanbul ignore next */ { if (err.statusCode === 404) { logger.debug('404 respose when retrieving comments'); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } throw err; } @@ -1559,7 +1559,7 @@ export async function ensureComment({ } return true; } catch (err) /* istanbul ignore next */ { - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } if (err.body?.message?.includes('is locked')) { @@ -1690,7 +1690,7 @@ export async function updatePr( ); logger.debug({ pr: prNo }, 'PR updated'); } catch (err) /* istanbul ignore next */ { - if (err.message === PLATFORM_FAILURE) { + if (err instanceof ExternalHostError) { throw err; } logger.warn({ err }, 'Error updating PR'); diff --git a/lib/types/error.ts b/lib/types/error.ts new file mode 100644 index 00000000000000..b6e67592d0b28a --- /dev/null +++ b/lib/types/error.ts @@ -0,0 +1,19 @@ +import { EXTERNAL_HOST_ERROR } from '../constants/error-messages'; + +export class ExternalHostError extends Error { + hostType: string; + + err: Error; + + lookupName?: string; + + reason?: string; + + constructor(err: Error, hostType?: string) { + super(EXTERNAL_HOST_ERROR); + // Set the prototype explicitly: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, ExternalHostError.prototype); + this.hostType = hostType; + this.err = err; + } +} diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts index 444d9ef06c0cbd..fb4e5b38aa35fc 100644 --- a/lib/util/http/github.spec.ts +++ b/lib/util/http/github.spec.ts @@ -2,8 +2,8 @@ import delay from 'delay'; import * as httpMock from '../../../test/httpMock'; import { getName } from '../../../test/util'; import { + EXTERNAL_HOST_ERROR, PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED, @@ -126,7 +126,7 @@ describe(getName(__filename), () => { }, }); expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); + expect(e.message).toEqual(EXTERNAL_HOST_ERROR); }); it('should throw platform failure for ENOTFOUND, ETIMEDOUT or EAI_AGAIN', () => { const codes = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN']; @@ -137,7 +137,7 @@ describe(getName(__filename), () => { code, }); expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); + expect(e.message).toEqual(EXTERNAL_HOST_ERROR); } }); it('should throw platform failure for 500', () => { @@ -146,14 +146,14 @@ describe(getName(__filename), () => { message: 'Internal Server Error', }); expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); + expect(e.message).toEqual(EXTERNAL_HOST_ERROR); }); it('should throw platform failure ParseError', () => { const e = getError({ name: 'ParseError', }); expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); + expect(e.message).toEqual(EXTERNAL_HOST_ERROR); }); it('should throw for unauthorized integration', () => { const e = getError({ @@ -200,7 +200,7 @@ describe(getName(__filename), () => { }; const e = getError(gotErr); expect(e).toBeDefined(); - expect(e.message).toEqual(PLATFORM_FAILURE); + expect(e.message).toEqual(EXTERNAL_HOST_ERROR); }); it('should throw original error when failed to add reviewers', () => { const gotErr = { diff --git a/lib/util/http/github.ts b/lib/util/http/github.ts index 76c7d9d76db751..c2f6dd1b657c28 100644 --- a/lib/util/http/github.ts +++ b/lib/util/http/github.ts @@ -4,13 +4,13 @@ import pAll from 'p-all'; import parseLinkHeader from 'parse-link-header'; import { PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED, } from '../../constants/error-messages'; import { PLATFORM_TYPE_GITHUB } from '../../constants/platforms'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { maskToken } from '../mask'; import { Http, HttpPostOptions, HttpResponse, InternalHttpOptions } from '.'; @@ -61,15 +61,15 @@ export function handleGotError( err.code === 'EAI_AGAIN') ) { logger.debug({ err }, 'GitHub failure: RequestError'); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } if (err.name === 'ParseError') { logger.debug({ err }, ''); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } if (err.statusCode >= 500 && err.statusCode < 600) { logger.debug({ err }, 'GitHub failure: 5xx'); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } if ( err.statusCode === 403 && @@ -106,7 +106,7 @@ export function handleGotError( 'GitHub failure: Bad credentials' ); if (rateLimit === '60') { - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } throw new Error(PLATFORM_BAD_CREDENTIALS); } @@ -123,7 +123,7 @@ export function handleGotError( throw new Error(REPOSITORY_CHANGED); } logger.debug({ err }, '422 Error thrown from GitHub'); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITHUB); } if (err.statusCode === 404) { logger.debug({ url: err.url }, 'GitHub 404'); diff --git a/lib/util/http/gitlab.ts b/lib/util/http/gitlab.ts index 9a5d0f160509da..5e759d333952b2 100644 --- a/lib/util/http/gitlab.ts +++ b/lib/util/http/gitlab.ts @@ -1,8 +1,8 @@ import { URL } from 'url'; import parseLinkHeader from 'parse-link-header'; -import { PLATFORM_FAILURE } from '../../constants/error-messages'; import { PLATFORM_TYPE_GITLAB } from '../../constants/platforms'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { Http, HttpResponse, InternalHttpOptions } from '.'; let baseUrl = 'https://gitlab.com/api/v4/'; @@ -63,7 +63,7 @@ export class GitlabHttp extends Http { err.statusCode === 429 || (err.statusCode >= 500 && err.statusCode < 600) ) { - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB); } const platformFailureCodes = [ 'EAI_AGAIN', @@ -72,10 +72,10 @@ export class GitlabHttp extends Http { 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', ]; if (platformFailureCodes.includes(err.code)) { - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB); } if (err.name === 'ParseError') { - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError(err, PLATFORM_TYPE_GITLAB); } throw err; } diff --git a/lib/workers/branch/index.ts b/lib/workers/branch/index.ts index 31963c57719a7c..a646d6dc708264 100644 --- a/lib/workers/branch/index.ts +++ b/lib/workers/branch/index.ts @@ -4,11 +4,9 @@ import { DateTime } from 'luxon'; import minimatch from 'minimatch'; import { RenovateConfig } from '../../config'; import { - DATASOURCE_FAILURE, MANAGER_LOCKFILE_ERROR, PLATFORM_AUTHENTICATION_ERROR, PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED, @@ -24,6 +22,7 @@ import { logger } from '../../logger'; import { getAdditionalFiles } from '../../manager/npm/post-update'; import { platform } from '../../platform'; import { BranchStatus } from '../../types'; +import { ExternalHostError } from '../../types/error'; import { emojify } from '../../util/emoji'; import { exec } from '../../util/exec'; import { readLocalFile, writeLocalFile } from '../../util/fs'; @@ -541,10 +540,7 @@ export async function processBranch( err.message.includes('fatal: Authentication failed') ) { throw new Error(PLATFORM_AUTHENTICATION_ERROR); - } else if ( - err.message !== DATASOURCE_FAILURE && - err.message !== PLATFORM_FAILURE - ) { + } else if (!(err instanceof ExternalHostError)) { logger.error({ err }, `Error updating branch: ${err.message}`); } // Don't throw here - we don't want to stop the other renovations @@ -655,11 +651,8 @@ export async function processBranch( } } catch (err) /* istanbul ignore next */ { if ( - [ - PLATFORM_RATE_LIMIT_EXCEEDED, - PLATFORM_FAILURE, - REPOSITORY_CHANGED, - ].includes(err.message) + err instanceof ExternalHostError || + [PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED].includes(err.message) ) { logger.debug('Passing PR error up'); throw err; diff --git a/lib/workers/pr/index.ts b/lib/workers/pr/index.ts index 9fc7e586e968ca..17c2bcdca22108 100644 --- a/lib/workers/pr/index.ts +++ b/lib/workers/pr/index.ts @@ -2,7 +2,6 @@ import sampleSize from 'lodash/sampleSize'; import uniq from 'lodash/uniq'; import { RenovateConfig } from '../../config/common'; import { - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_CHANGED, @@ -10,6 +9,7 @@ import { import { logger } from '../../logger'; import { PlatformPrOptions, Pr, platform } from '../../platform'; import { BranchStatus } from '../../types'; +import { ExternalHostError } from '../../types/error'; import { BranchConfig, PrResult } from '../common'; import { getPrBody } from './body'; import { ChangeLogError } from './changelog'; @@ -427,9 +427,9 @@ export async function ensurePr( } catch (err) { // istanbul ignore if if ( + err instanceof ExternalHostError || err.message === REPOSITORY_CHANGED || err.message === PLATFORM_RATE_LIMIT_EXCEEDED || - err.message === PLATFORM_FAILURE || err.message === PLATFORM_INTEGRATION_UNAUTHORIZED ) { logger.debug('Passing error up'); diff --git a/lib/workers/repository/error.spec.ts b/lib/workers/repository/error.spec.ts index f0b10e9548f98e..5fce970db9260a 100644 --- a/lib/workers/repository/error.spec.ts +++ b/lib/workers/repository/error.spec.ts @@ -1,12 +1,11 @@ import { RenovateConfig, getConfig } from '../../../test/util'; import { CONFIG_VALIDATION, - DATASOURCE_FAILURE, + EXTERNAL_HOST_ERROR, MANAGER_LOCKFILE_ERROR, MANAGER_NO_PACKAGE_FILES, PLATFORM_AUTHENTICATION_ERROR, PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_ACCESS_FORBIDDEN, @@ -27,7 +26,7 @@ import { SYSTEM_INSUFFICIENT_MEMORY, UNKNOWN_ERROR, } from '../../constants/error-messages'; -import { DatasourceError } from '../../datasource/common'; +import { ExternalHostError } from '../../types/error'; import handleError from './error'; jest.mock('./error-config'); @@ -48,7 +47,6 @@ describe('workers/repository/error', () => { REPOSITORY_FORKED, MANAGER_NO_PACKAGE_FILES, CONFIG_VALIDATION, - DATASOURCE_FAILURE, REPOSITORY_ARCHIVED, REPOSITORY_MIRRORED, REPOSITORY_RENAMED, @@ -60,7 +58,6 @@ describe('workers/repository/error', () => { MANAGER_LOCKFILE_ERROR, SYSTEM_INSUFFICIENT_DISK_SPACE, SYSTEM_INSUFFICIENT_MEMORY, - PLATFORM_FAILURE, REPOSITORY_NO_VULNERABILITY, REPOSITORY_CANNOT_FORK, PLATFORM_INTEGRATION_UNAUTHORIZED, @@ -73,23 +70,26 @@ describe('workers/repository/error', () => { expect(res).toEqual(err); }); }); - it(`handles DatasourceError`, async () => { - const res = await handleError(config, new DatasourceError(new Error())); - expect(res).toEqual(DATASOURCE_FAILURE); + it(`handles ExternalHostError`, async () => { + const res = await handleError( + config, + new ExternalHostError(new Error(), 'some-host-type') + ); + expect(res).toEqual(EXTERNAL_HOST_ERROR); }); it('rewrites git 5xx error', async () => { const gitError = new Error( "fatal: unable to access 'https://**redacted**@gitlab.com/learnox/learnox.git/': The requested URL returned error: 500\n" ); const res = await handleError(config, gitError); - expect(res).toEqual(PLATFORM_FAILURE); + expect(res).toEqual(EXTERNAL_HOST_ERROR); }); it('rewrites git remote error', async () => { const gitError = new Error( 'fatal: remote error: access denied or repository not exported: /b/nw/bd/27/47/159945428/108610112.git\n' ); const res = await handleError(config, gitError); - expect(res).toEqual(PLATFORM_FAILURE); + expect(res).toEqual(EXTERNAL_HOST_ERROR); }); it('handles unknown error', async () => { const res = await handleError(config, new Error('abcdefg')); diff --git a/lib/workers/repository/error.ts b/lib/workers/repository/error.ts index 48f8e7a911a9af..5ff14d0aeac259 100644 --- a/lib/workers/repository/error.ts +++ b/lib/workers/repository/error.ts @@ -2,12 +2,11 @@ import { RenovateConfig } from '../../config'; import { CONFIG_VALIDATION, - DATASOURCE_FAILURE, + EXTERNAL_HOST_ERROR, MANAGER_LOCKFILE_ERROR, MANAGER_NO_PACKAGE_FILES, PLATFORM_AUTHENTICATION_ERROR, PLATFORM_BAD_CREDENTIALS, - PLATFORM_FAILURE, PLATFORM_INTEGRATION_UNAUTHORIZED, PLATFORM_RATE_LIMIT_EXCEEDED, REPOSITORY_ACCESS_FORBIDDEN, @@ -28,8 +27,8 @@ import { SYSTEM_INSUFFICIENT_MEMORY, UNKNOWN_ERROR, } from '../../constants/error-messages'; -import { DatasourceError } from '../../datasource/common'; import { logger } from '../../logger'; +import { ExternalHostError } from '../../types/error'; import { raiseConfigWarningIssue } from './error-config'; export default async function handleError( @@ -107,22 +106,12 @@ export default async function handleError( await raiseConfigWarningIssue(config, err); return err.message; } - if (err instanceof DatasourceError) { + if (err instanceof ExternalHostError) { logger.warn( - { datasource: err.datasource, lookupName: err.lookupName, err: err.err }, - 'Datasource failure' + { hostType: err.hostType, lookupName: err.lookupName, err: err.err }, + 'Host error' ); - logger.info('Registry error - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign - return err.message; - } - if (err.message === DATASOURCE_FAILURE) { - logger.info({ err }, 'Registry error - skipping'); - delete config.branchList; // eslint-disable-line no-param-reassign - return err.message; - } - if (err.message === PLATFORM_FAILURE) { - logger.info('Platform error - skipping'); + logger.info('External host error causing abort - skipping'); delete config.branchList; // eslint-disable-line no-param-reassign return err.message; } @@ -174,7 +163,7 @@ export default async function handleError( logger.warn({ err }, 'Git error - aborting'); delete config.branchList; // eslint-disable-line no-param-reassign // rewrite this error - return PLATFORM_FAILURE; + return EXTERNAL_HOST_ERROR; } if ( err.message.includes('The remote end hung up unexpectedly') || @@ -183,7 +172,7 @@ export default async function handleError( logger.warn({ err }, 'Git error - aborting'); delete config.branchList; // eslint-disable-line no-param-reassign // rewrite this error - return PLATFORM_FAILURE; + return EXTERNAL_HOST_ERROR; } // Swallow this error so that other repositories can be processed logger.error({ err }, `Repository has unknown error`); diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts index 547c2b39335b7b..21ce3d73254720 100644 --- a/lib/workers/repository/init/config.ts +++ b/lib/workers/repository/init/config.ts @@ -7,13 +7,11 @@ import { configFileNames } from '../../../config/app-strings'; import { decryptConfig } from '../../../config/decrypt'; import { migrateAndValidate } from '../../../config/migrate-validate'; import * as presets from '../../../config/presets'; -import { - CONFIG_VALIDATION, - PLATFORM_FAILURE, -} from '../../../constants/error-messages'; +import { CONFIG_VALIDATION } from '../../../constants/error-messages'; import * as npmApi from '../../../datasource/npm'; import { logger } from '../../../logger'; import { platform } from '../../../platform'; +import { ExternalHostError } from '../../../types/error'; import { readLocalFile } from '../../../util/fs'; import * as hostRules from '../../../util/host-rules'; import { flattenPackageRules } from './flatten'; @@ -59,7 +57,10 @@ export async function mergeRenovateConfig( // istanbul ignore if if (renovateConfig === null) { logger.warn('Fetching renovate config returns null'); - throw new Error(PLATFORM_FAILURE); + throw new ExternalHostError( + Error('Fetching renovate config returns null'), + config.platform + ); } // istanbul ignore if if (!renovateConfig.length) {