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

feat(host-rules): add support for disabling hosts #6715

Merged
merged 5 commits into from Jul 9, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 24 additions & 1 deletion docs/usage/configuration-options.md
Expand Up @@ -429,7 +429,7 @@ Currently the purpose of `hostRules` is to configure credentials for host authen

The lookup keys for a hostRule are: `hostType`, `domainName`, `hostName`, and `baseUrl`. All are optional, but you can only have one of the last three per rule.

Supported credential fields are `token`, `username`, `password`, `timeout` and `insecureRegistry`.
Supported credential fields are `token`, `username`, `password`, `timeout`, `enabled` and `insecureRegistry`.

Example for configuring `docker` auth:

Expand All @@ -445,6 +445,29 @@ Example for configuring `docker` auth:
}
```

To disable requests to a particular host, you can configure a rule like:

```json
{
"hostRules": [
{
"hostName": "registry.npmjs.org",
"enabled": false
}
]
}
```

A preset alternative to the above is:

```json
{
"extends": [":disableHost(registry.npmjs.org)"]
}
```

Note: Disabling a host is only 100% effective if added to self-hosted config. Renovate currently still checks its *cache* for results first before making connection attempts, so if a public host is blocked in your repository config (e.g. `renovate.json`) then it's possible you may get cached *results* from that host if another repository using the same bot has successfully queried for the same dependency recently.

### abortIgnoreStatusCodes

This field can be used to configure status codes that Renovate ignores and passes through when `abortOnError` is set to `true`. For example to also skip 404 responses then configure the following:
Expand Down
18 changes: 18 additions & 0 deletions lib/config/presets/internal/default.ts
Expand Up @@ -15,6 +15,24 @@ export const presets: Record<string, Preset> = {
enabled: false,
},
},
disableDomain: {
description: 'Disable requests to a particular domain',
hostRules: [
{
domainName: '{{arg0}}',
enabled: false,
},
],
},
disableHost: {
description: 'Disable requests to a particular hostName',
hostRules: [
{
hostName: '{{arg0}}',
enabled: false,
},
],
},
ignoreModulesAndTests: {
description:
'Ignore `node_modules`, `bower_components`, `vendor` and various test/tests directories',
Expand Down
1 change: 1 addition & 0 deletions lib/constants/error-messages.ts
Expand Up @@ -37,6 +37,7 @@ export const MANAGER_NO_PACKAGE_FILES = 'no-package-files';
// Host error
export const EXTERNAL_HOST_ERROR = 'external-host-error';
export const IGNORABLE_HOST_ERROR = 'ignorable-host-error';
export const HOST_DISABLED = 'host-disabled';

// Worker Error
export const WORKER_FILE_UPDATE_FAILED = 'update-failure';
Expand Down
17 changes: 16 additions & 1 deletion lib/datasource/index.spec.ts
@@ -1,5 +1,8 @@
import { mocked } from '../../test/util';
import { EXTERNAL_HOST_ERROR } from '../constants/error-messages';
import {
EXTERNAL_HOST_ERROR,
HOST_DISABLED,
} from '../constants/error-messages';
import { ExternalHostError } from '../types/errors/external-host-error';
import { loadModules } from '../util/modules';
import * as datasourceDocker from './docker';
Expand Down Expand Up @@ -131,6 +134,18 @@ describe('datasource/index', () => {
});
expect(res).not.toBeNull();
});
it('returns null for HOST_DISABLED', async () => {
packagistDatasource.getReleases.mockImplementationOnce(() => {
throw new ExternalHostError(new Error(HOST_DISABLED));
});
expect(
await datasource.getPkgReleases({
datasource: datasourcePackagist.id,
depName: 'something',
registryUrls: ['https://reg1.com'],
})
).toBeNull();
});
it('hunts registries and aborts on ExternalHostError', async () => {
packagistDatasource.getReleases.mockImplementationOnce(() => {
throw new ExternalHostError(new Error());
Expand Down
4 changes: 4 additions & 0 deletions lib/datasource/index.ts
@@ -1,5 +1,6 @@
import is from '@sindresorhus/is';
import equal from 'fast-deep-equal';
import { HOST_DISABLED } from '../constants/error-messages';
import { logger } from '../logger';
import { ExternalHostError } from '../types/errors/external-host-error';
import * as memCache from '../util/cache/memory';
Expand Down Expand Up @@ -196,6 +197,9 @@ async function fetchReleases(
});
}
} catch (err) {
if (err.messages === HOST_DISABLED || err.err?.message === HOST_DISABLED) {
rarkins marked this conversation as resolved.
Show resolved Hide resolved
return null;
}
if (err instanceof ExternalHostError) {
throw err;
}
Expand Down
1 change: 1 addition & 0 deletions lib/types/host-rules.ts
Expand Up @@ -15,4 +15,5 @@ export interface HostRule {
encrypted?: HostRule;
abortOnError?: boolean;
abortIgnoreStatusCodes?: number[];
enabled?: boolean;
}
4 changes: 3 additions & 1 deletion lib/util/http/host-rules.ts
Expand Up @@ -10,7 +10,7 @@ export function applyHostRules(url: string, inOptions: any): any {
hostType: options.hostType,
url,
}) || {};
const { username, password, token } = foundRules;
const { username, password, token, enabled } = foundRules;
if (options.headers?.authorization || options.auth || options.token) {
logger.trace('Authorization already set for host: ' + options.hostname);
} else if (password) {
Expand All @@ -19,6 +19,8 @@ export function applyHostRules(url: string, inOptions: any): any {
} else if (token) {
logger.trace('Applying Bearer authentication for host ' + options.hostname);
options.token = token;
} else if (enabled !== undefined) {
rarkins marked this conversation as resolved.
Show resolved Hide resolved
options.enabled = false;
}
// Apply optional params
['abortOnError', 'abortIgnoreStatusCodes', 'timeout'].forEach((param) => {
Expand Down
11 changes: 10 additions & 1 deletion lib/util/http/index.spec.ts
@@ -1,6 +1,9 @@
import nock from 'nock';
import { getName } from '../../../test/util';
import { EXTERNAL_HOST_ERROR } from '../../constants/error-messages';
import {
EXTERNAL_HOST_ERROR,
HOST_DISABLED,
} from '../../constants/error-messages';
import * as hostRules from '../host-rules';
import { Http } from '.';

Expand Down Expand Up @@ -34,6 +37,12 @@ describe(getName(__filename), () => {
);
expect(nock.isDone()).toBe(true);
});
it('disables hosts', async () => {
hostRules.add({ hostName: 'renovate.com', enabled: false });
await expect(http.get('http://renovate.com/test')).rejects.toThrow(
HOST_DISABLED
);
});
it('ignores 404 error and does not throw ExternalHostError', async () => {
nock(baseUrl).get('/test').reply(404);
hostRules.add({ abortOnError: true, abortIgnoreStatusCodes: [404] });
Expand Down
4 changes: 4 additions & 0 deletions lib/util/http/index.ts
@@ -1,6 +1,7 @@
import crypto from 'crypto';
import URL from 'url';
import got from 'got';
import { HOST_DISABLED } from '../../constants/error-messages';
import { ExternalHostError } from '../../types/errors/external-host-error';
import * as memCache from '../cache/memory';
import { clone } from '../clone';
Expand Down Expand Up @@ -89,6 +90,9 @@ export class Http<GetOptions = HttpOptions, PostOptions = HttpPostOptions> {
};

options = applyHostRules(url, options);
if (options.enabled === false) {
throw new Error(HOST_DISABLED);
}
options = applyAuthorization(options);

// Cache GET requests unless useCache=false
Expand Down