mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
feat: hostRules.matchHost (#9815)
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
This commit is contained in:
parent
89f587cbf2
commit
856b28841d
7 changed files with 85 additions and 6 deletions
|
@ -934,6 +934,12 @@ Example:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### matchHost
|
||||||
|
|
||||||
|
This can be a base URL (e.g. `https://api.github.com`) or a hostname like `github.com` or `api.github.com`.
|
||||||
|
If the value starts with `http(s)` then it will only match against URLs which start with the full base URL.
|
||||||
|
Otherwise, it will be matched by checking if the URL's hostname matches the `matchHost` directly or ends with it.
|
||||||
|
|
||||||
### timeout
|
### timeout
|
||||||
|
|
||||||
Use this figure to adjust the timeout for queries.
|
Use this figure to adjust the timeout for queries.
|
||||||
|
|
|
@ -1728,6 +1728,15 @@ const options: RenovateOptions[] = [
|
||||||
cli: false,
|
cli: false,
|
||||||
env: false,
|
env: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'matchHost',
|
||||||
|
description: 'A host name or base URL to match against',
|
||||||
|
type: 'string',
|
||||||
|
stage: 'repository',
|
||||||
|
parent: 'hostRules',
|
||||||
|
cli: false,
|
||||||
|
env: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'timeout',
|
name: 'timeout',
|
||||||
description: 'Timeout (in milliseconds) for queries to external endpoints.',
|
description: 'Timeout (in milliseconds) for queries to external endpoints.',
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
} from '../../../util/fs';
|
} from '../../../util/fs';
|
||||||
import { branchExists, getFile, getRepoStatus } from '../../../util/git';
|
import { branchExists, getFile, getRepoStatus } from '../../../util/git';
|
||||||
import * as hostRules from '../../../util/host-rules';
|
import * as hostRules from '../../../util/host-rules';
|
||||||
|
import { validateUrl } from '../../../util/url';
|
||||||
import type { PackageFile, PostUpdateConfig, Upgrade } from '../../types';
|
import type { PackageFile, PostUpdateConfig, Upgrade } from '../../types';
|
||||||
import * as lerna from './lerna';
|
import * as lerna from './lerna';
|
||||||
import * as npm from './npm';
|
import * as npm from './npm';
|
||||||
|
@ -444,9 +445,8 @@ export async function getAdditionalFiles(
|
||||||
});
|
});
|
||||||
for (const hostRule of npmHostRules) {
|
for (const hostRule of npmHostRules) {
|
||||||
if (hostRule.resolvedHost) {
|
if (hostRule.resolvedHost) {
|
||||||
const uri = hostRule.baseUrl
|
let uri = hostRule.baseUrl || hostRule.matchHost || hostRule.resolvedHost;
|
||||||
? hostRule.baseUrl.replace(/^https?:/, '')
|
uri = validateUrl(uri) ? uri.replace(/^https?:/, '') : `//${uri}/`;
|
||||||
: `//${hostRule.resolvedHost}/`;
|
|
||||||
if (hostRule.token) {
|
if (hostRule.token) {
|
||||||
const key = hostRule.authType === 'Basic' ? '_auth' : '_authToken';
|
const key = hostRule.authType === 'Basic' ? '_auth' : '_authToken';
|
||||||
additionalNpmrcContent.push(`${uri}:${key}=${hostRule.token}`);
|
additionalNpmrcContent.push(`${uri}:${key}=${hostRule.token}`);
|
||||||
|
|
|
@ -4,6 +4,7 @@ export interface HostRule {
|
||||||
domainName?: string;
|
domainName?: string;
|
||||||
hostName?: string;
|
hostName?: string;
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
|
matchHost?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
|
|
@ -31,5 +31,7 @@ exports[`util/host-rules find() returns hosts 1`] = `
|
||||||
Array [
|
Array [
|
||||||
"nuget.local",
|
"nuget.local",
|
||||||
"my.local.registry",
|
"my.local.registry",
|
||||||
|
"another.local.registry",
|
||||||
|
"yet.another.local.registry",
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -106,6 +106,29 @@ describe(getName(), () => {
|
||||||
find({ hostType: datasourceNuget.id, url: 'https://nuget.local/api' })
|
find({ hostType: datasourceNuget.id, url: 'https://nuget.local/api' })
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
it('matches on matchHost with protocol', () => {
|
||||||
|
add({
|
||||||
|
matchHost: 'https://domain.com',
|
||||||
|
token: 'def',
|
||||||
|
});
|
||||||
|
expect(find({ url: 'https://api.domain.com' }).token).toBeUndefined();
|
||||||
|
expect(find({ url: 'https://domain.com' }).token).toEqual('def');
|
||||||
|
expect(
|
||||||
|
find({
|
||||||
|
hostType: datasourceNuget.id,
|
||||||
|
url: 'https://domain.com/renovatebot',
|
||||||
|
}).token
|
||||||
|
).toEqual('def');
|
||||||
|
});
|
||||||
|
it('matches on matchHost without protocol', () => {
|
||||||
|
add({
|
||||||
|
matchHost: 'domain.com',
|
||||||
|
token: 'def',
|
||||||
|
});
|
||||||
|
expect(find({ url: 'https://api.domain.com' }).token).toEqual('def');
|
||||||
|
expect(find({ url: 'https://domain.com' }).token).toEqual('def');
|
||||||
|
expect(find({ url: 'httpsdomain.com' }).token).toBeUndefined();
|
||||||
|
});
|
||||||
it('matches on hostType and endpoint', () => {
|
it('matches on hostType and endpoint', () => {
|
||||||
add({
|
add({
|
||||||
hostType: datasourceNuget.id,
|
hostType: datasourceNuget.id,
|
||||||
|
@ -145,11 +168,21 @@ describe(getName(), () => {
|
||||||
hostName: 'my.local.registry',
|
hostName: 'my.local.registry',
|
||||||
token: 'def',
|
token: 'def',
|
||||||
});
|
});
|
||||||
|
add({
|
||||||
|
hostType: datasourceNuget.id,
|
||||||
|
matchHost: 'another.local.registry',
|
||||||
|
token: 'xyz',
|
||||||
|
});
|
||||||
|
add({
|
||||||
|
hostType: datasourceNuget.id,
|
||||||
|
matchHost: 'https://yet.another.local.registry',
|
||||||
|
token: '123',
|
||||||
|
});
|
||||||
const res = hosts({
|
const res = hosts({
|
||||||
hostType: datasourceNuget.id,
|
hostType: datasourceNuget.id,
|
||||||
});
|
});
|
||||||
expect(res).toMatchSnapshot();
|
expect(res).toMatchSnapshot();
|
||||||
expect(res).toHaveLength(2);
|
expect(res).toHaveLength(4);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('findAll()', () => {
|
describe('findAll()', () => {
|
||||||
|
|
|
@ -4,10 +4,11 @@ import { logger } from '../logger';
|
||||||
import { HostRule } from '../types';
|
import { HostRule } from '../types';
|
||||||
import { clone } from './clone';
|
import { clone } from './clone';
|
||||||
import * as sanitize from './sanitize';
|
import * as sanitize from './sanitize';
|
||||||
|
import { parseUrl, validateUrl } from './url';
|
||||||
|
|
||||||
let hostRules: HostRule[] = [];
|
let hostRules: HostRule[] = [];
|
||||||
|
|
||||||
const matchFields = ['hostName', 'domainName', 'baseUrl'];
|
const matchFields = ['matchHost', 'hostName', 'domainName', 'baseUrl'];
|
||||||
|
|
||||||
export function add(params: HostRule): void {
|
export function add(params: HostRule): void {
|
||||||
const matchedFields = matchFields.filter((field) => params[field]);
|
const matchedFields = matchFields.filter((field) => params[field]);
|
||||||
|
@ -19,7 +20,8 @@ export function add(params: HostRule): void {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const confidentialFields = ['password', 'token'];
|
const confidentialFields = ['password', 'token'];
|
||||||
let resolvedHost = params.baseUrl || params.hostName || params.domainName;
|
let resolvedHost =
|
||||||
|
params.baseUrl || params.hostName || params.domainName || params.matchHost;
|
||||||
if (resolvedHost) {
|
if (resolvedHost) {
|
||||||
resolvedHost = URL.parse(resolvedHost).hostname || resolvedHost;
|
resolvedHost = URL.parse(resolvedHost).hostname || resolvedHost;
|
||||||
confidentialFields.forEach((field) => {
|
confidentialFields.forEach((field) => {
|
||||||
|
@ -74,6 +76,10 @@ function isBaseUrlRule(rule: HostRule): boolean {
|
||||||
return !rule.hostType && !!rule.baseUrl;
|
return !rule.hostType && !!rule.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHostOnlyRule(rule: HostRule): boolean {
|
||||||
|
return !rule.hostType && !!rule.matchHost;
|
||||||
|
}
|
||||||
|
|
||||||
function isMultiRule(rule: HostRule): boolean {
|
function isMultiRule(rule: HostRule): boolean {
|
||||||
return rule.hostType && !!rule.resolvedHost;
|
return rule.hostType && !!rule.resolvedHost;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +110,21 @@ function matchesBaseUrl(rule: HostRule, search: HostRuleSearch): boolean {
|
||||||
return search.url && rule.baseUrl && search.url.startsWith(rule.baseUrl);
|
return search.url && rule.baseUrl && search.url.startsWith(rule.baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function matchesHost(rule: HostRule, search: HostRuleSearch): boolean {
|
||||||
|
if (!rule.matchHost) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (validateUrl(rule.matchHost)) {
|
||||||
|
return search.url.startsWith(rule.matchHost);
|
||||||
|
}
|
||||||
|
const parsedUrl = parseUrl(search.url);
|
||||||
|
if (!parsedUrl?.hostname) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { hostname } = parsedUrl;
|
||||||
|
return hostname === rule.matchHost || hostname.endsWith(`.${rule.matchHost}`);
|
||||||
|
}
|
||||||
|
|
||||||
export function find(search: HostRuleSearch): HostRule {
|
export function find(search: HostRuleSearch): HostRule {
|
||||||
if (!(search.hostType || search.url)) {
|
if (!(search.hostType || search.url)) {
|
||||||
logger.warn({ search }, 'Invalid hostRules search');
|
logger.warn({ search }, 'Invalid hostRules search');
|
||||||
|
@ -140,6 +161,11 @@ export function find(search: HostRuleSearch): HostRule {
|
||||||
.forEach((rule) => {
|
.forEach((rule) => {
|
||||||
res = merge(res, rule);
|
res = merge(res, rule);
|
||||||
});
|
});
|
||||||
|
hostRules
|
||||||
|
.filter((rule) => isHostOnlyRule(rule) && matchesHost(rule, search))
|
||||||
|
.forEach((rule) => {
|
||||||
|
res = merge(res, rule);
|
||||||
|
});
|
||||||
// Finally, find combination matches
|
// Finally, find combination matches
|
||||||
hostRules
|
hostRules
|
||||||
.filter(
|
.filter(
|
||||||
|
@ -147,6 +173,7 @@ export function find(search: HostRuleSearch): HostRule {
|
||||||
isMultiRule(rule) &&
|
isMultiRule(rule) &&
|
||||||
matchesHostType(rule, search) &&
|
matchesHostType(rule, search) &&
|
||||||
(matchesDomainName(rule, search) ||
|
(matchesDomainName(rule, search) ||
|
||||||
|
matchesHost(rule, search) ||
|
||||||
matchesHostName(rule, search) ||
|
matchesHostName(rule, search) ||
|
||||||
matchesBaseUrl(rule, search))
|
matchesBaseUrl(rule, search))
|
||||||
)
|
)
|
||||||
|
@ -158,6 +185,7 @@ export function find(search: HostRuleSearch): HostRule {
|
||||||
delete res.hostName;
|
delete res.hostName;
|
||||||
delete res.baseUrl;
|
delete res.baseUrl;
|
||||||
delete res.resolvedHost;
|
delete res.resolvedHost;
|
||||||
|
delete res.matchHost;
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue