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
|
||||
|
||||
Use this figure to adjust the timeout for queries.
|
||||
|
|
|
@ -1728,6 +1728,15 @@ const options: RenovateOptions[] = [
|
|||
cli: 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',
|
||||
description: 'Timeout (in milliseconds) for queries to external endpoints.',
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from '../../../util/fs';
|
||||
import { branchExists, getFile, getRepoStatus } from '../../../util/git';
|
||||
import * as hostRules from '../../../util/host-rules';
|
||||
import { validateUrl } from '../../../util/url';
|
||||
import type { PackageFile, PostUpdateConfig, Upgrade } from '../../types';
|
||||
import * as lerna from './lerna';
|
||||
import * as npm from './npm';
|
||||
|
@ -444,9 +445,8 @@ export async function getAdditionalFiles(
|
|||
});
|
||||
for (const hostRule of npmHostRules) {
|
||||
if (hostRule.resolvedHost) {
|
||||
const uri = hostRule.baseUrl
|
||||
? hostRule.baseUrl.replace(/^https?:/, '')
|
||||
: `//${hostRule.resolvedHost}/`;
|
||||
let uri = hostRule.baseUrl || hostRule.matchHost || hostRule.resolvedHost;
|
||||
uri = validateUrl(uri) ? uri.replace(/^https?:/, '') : `//${uri}/`;
|
||||
if (hostRule.token) {
|
||||
const key = hostRule.authType === 'Basic' ? '_auth' : '_authToken';
|
||||
additionalNpmrcContent.push(`${uri}:${key}=${hostRule.token}`);
|
||||
|
|
|
@ -4,6 +4,7 @@ export interface HostRule {
|
|||
domainName?: string;
|
||||
hostName?: string;
|
||||
baseUrl?: string;
|
||||
matchHost?: string;
|
||||
token?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
|
|
|
@ -31,5 +31,7 @@ exports[`util/host-rules find() returns hosts 1`] = `
|
|||
Array [
|
||||
"nuget.local",
|
||||
"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' })
|
||||
).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', () => {
|
||||
add({
|
||||
hostType: datasourceNuget.id,
|
||||
|
@ -145,11 +168,21 @@ describe(getName(), () => {
|
|||
hostName: 'my.local.registry',
|
||||
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({
|
||||
hostType: datasourceNuget.id,
|
||||
});
|
||||
expect(res).toMatchSnapshot();
|
||||
expect(res).toHaveLength(2);
|
||||
expect(res).toHaveLength(4);
|
||||
});
|
||||
});
|
||||
describe('findAll()', () => {
|
||||
|
|
|
@ -4,10 +4,11 @@ import { logger } from '../logger';
|
|||
import { HostRule } from '../types';
|
||||
import { clone } from './clone';
|
||||
import * as sanitize from './sanitize';
|
||||
import { parseUrl, validateUrl } from './url';
|
||||
|
||||
let hostRules: HostRule[] = [];
|
||||
|
||||
const matchFields = ['hostName', 'domainName', 'baseUrl'];
|
||||
const matchFields = ['matchHost', 'hostName', 'domainName', 'baseUrl'];
|
||||
|
||||
export function add(params: HostRule): void {
|
||||
const matchedFields = matchFields.filter((field) => params[field]);
|
||||
|
@ -19,7 +20,8 @@ export function add(params: HostRule): void {
|
|||
);
|
||||
}
|
||||
const confidentialFields = ['password', 'token'];
|
||||
let resolvedHost = params.baseUrl || params.hostName || params.domainName;
|
||||
let resolvedHost =
|
||||
params.baseUrl || params.hostName || params.domainName || params.matchHost;
|
||||
if (resolvedHost) {
|
||||
resolvedHost = URL.parse(resolvedHost).hostname || resolvedHost;
|
||||
confidentialFields.forEach((field) => {
|
||||
|
@ -74,6 +76,10 @@ function isBaseUrlRule(rule: HostRule): boolean {
|
|||
return !rule.hostType && !!rule.baseUrl;
|
||||
}
|
||||
|
||||
function isHostOnlyRule(rule: HostRule): boolean {
|
||||
return !rule.hostType && !!rule.matchHost;
|
||||
}
|
||||
|
||||
function isMultiRule(rule: HostRule): boolean {
|
||||
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!(search.hostType || search.url)) {
|
||||
logger.warn({ search }, 'Invalid hostRules search');
|
||||
|
@ -140,6 +161,11 @@ export function find(search: HostRuleSearch): HostRule {
|
|||
.forEach((rule) => {
|
||||
res = merge(res, rule);
|
||||
});
|
||||
hostRules
|
||||
.filter((rule) => isHostOnlyRule(rule) && matchesHost(rule, search))
|
||||
.forEach((rule) => {
|
||||
res = merge(res, rule);
|
||||
});
|
||||
// Finally, find combination matches
|
||||
hostRules
|
||||
.filter(
|
||||
|
@ -147,6 +173,7 @@ export function find(search: HostRuleSearch): HostRule {
|
|||
isMultiRule(rule) &&
|
||||
matchesHostType(rule, search) &&
|
||||
(matchesDomainName(rule, search) ||
|
||||
matchesHost(rule, search) ||
|
||||
matchesHostName(rule, search) ||
|
||||
matchesBaseUrl(rule, search))
|
||||
)
|
||||
|
@ -158,6 +185,7 @@ export function find(search: HostRuleSearch): HostRule {
|
|||
delete res.hostName;
|
||||
delete res.baseUrl;
|
||||
delete res.resolvedHost;
|
||||
delete res.matchHost;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue