Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: ExternalHostError #6563

Merged
merged 12 commits into from Jun 22, 2020
4 changes: 2 additions & 2 deletions 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';
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion lib/config/presets/gitlab/__snapshots__/index.spec.ts.snap
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions 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 '.';

Expand All @@ -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();
});

Expand Down
4 changes: 2 additions & 2 deletions 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';
Expand Down Expand Up @@ -42,7 +42,7 @@ export async function fetchJSONFile(
const url = `${endpoint}projects/${urlEncodedRepo}/repository/files/${urlEncodedPkgName}/raw?ref=${defautlBranchName}`;
return (await gitlabApi.getJson<Preset>(url)).body;
} catch (err) {
if (err.message === PLATFORM_FAILURE) {
if (err instanceof ExternalHostError) {
throw err;
}
logger.debug(
Expand Down
12 changes: 3 additions & 9 deletions 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';
Expand Down Expand Up @@ -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);
Expand Down
5 changes: 2 additions & 3 deletions lib/constants/error-messages.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down
14 changes: 7 additions & 7 deletions 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(
Expand Down Expand Up @@ -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 () => {
Expand All @@ -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 () => {
Expand Down
7 changes: 4 additions & 3 deletions 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';

Expand Down Expand Up @@ -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);
}
}
17 changes: 0 additions & 17 deletions lib/datasource/common.ts
@@ -1,5 +1,3 @@
import { DATASOURCE_FAILURE } from '../constants/error-messages';

export interface Config {
datasource?: string;
depName?: string;
Expand Down Expand Up @@ -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;
}
}
2 changes: 1 addition & 1 deletion lib/datasource/crate/__snapshots__/index.spec.ts.snap
Expand Up @@ -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 [
Expand Down
10 changes: 3 additions & 7 deletions 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';

Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/datasource/dart/__snapshots__/index.spec.ts.snap
Expand Up @@ -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 [
Expand Down
5 changes: 3 additions & 2 deletions 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';

Expand Down Expand Up @@ -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 },
Expand Down
6 changes: 3 additions & 3 deletions lib/datasource/docker/index.spec.ts
Expand Up @@ -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 '.';

Expand Down Expand Up @@ -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(Error(EXTERNAL_HOST_ERROR));
rarkins marked this conversation as resolved.
Show resolved Hide resolved
});
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(Error(EXTERNAL_HOST_ERROR));
rarkins marked this conversation as resolved.
Show resolved Hide resolved
});
});
describe('getReleases', () => {
Expand Down
25 changes: 13 additions & 12 deletions lib/datasource/docker/index.ts
Expand Up @@ -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 ?
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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) {
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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) {
Expand Down