feat(config): detectGlobalManagerConfig (#11951)

This commit is contained in:
Rhys Arkins 2021-09-29 21:58:42 +02:00 committed by GitHub
parent 79e65bd0c7
commit cd72cdf2ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 113 additions and 0 deletions

View file

@ -130,6 +130,15 @@ e.g.
This configuration will be applied after all other environment variables so that it can be used to override defaults. This configuration will be applied after all other environment variables so that it can be used to override defaults.
## detectGlobalManagerConfig
The purpose of this capability is to allow a bot admin to configure manager-specific files such as a global `.npmrc` file, instead of configuring it in Renovate config.
This feature is disabled by default because it may prove surprising or undesirable for some users who don't expect Renovate to go into their home directory and import registry or credential information.
Currently this capability is supported for the `npm` manager only - specifically the `~/.npmrc` file.
If found, it will be imported into `config.npmrc` with `config.npmrcMerge` will be set to `true`.
## dockerChildPrefix ## dockerChildPrefix
Adds a custom prefix to the default Renovate sidecar Docker containers name and label. Adds a custom prefix to the default Renovate sidecar Docker containers name and label.

View file

@ -7,6 +7,14 @@ import * as pep440Versioning from '../../versioning/pep440';
import type { RenovateOptions } from '../types'; import type { RenovateOptions } from '../types';
const options: RenovateOptions[] = [ const options: RenovateOptions[] = [
{
name: 'detectGlobalManagerConfig',
description:
'If true, Renovate will attempt to read global manager config from the file system.',
type: 'boolean',
default: false,
globalOnly: true,
},
{ {
name: 'allowPostUpgradeCommandTemplating', name: 'allowPostUpgradeCommandTemplating',
description: 'If true allow templating for post-upgrade commands.', description: 'If true allow templating for post-upgrade commands.',

View file

@ -2,6 +2,8 @@ import { loadModules } from '../util/modules';
import type { ManagerApi } from './types'; import type { ManagerApi } from './types';
import * as manager from '.'; import * as manager from '.';
jest.mock('../util/fs');
describe('manager/index', () => { describe('manager/index', () => {
describe('get()', () => { describe('get()', () => {
it('gets something', () => { it('gets something', () => {
@ -43,6 +45,12 @@ describe('manager/index', () => {
} }
}); });
describe('detectGlobalConfig()', () => {
it('iterates through managers', async () => {
expect(await manager.detectAllGlobalConfig()).toEqual({});
});
});
describe('extractAllPackageFiles()', () => { describe('extractAllPackageFiles()', () => {
it('returns null', async () => { it('returns null', async () => {
manager.getManagers().set('dummy', { manager.getManagers().set('dummy', {

View file

@ -15,6 +15,7 @@ import type { RangeStrategy } from '../types';
import managers from './api'; import managers from './api';
import type { import type {
ExtractConfig, ExtractConfig,
GlobalManagerConfig,
ManagerApi, ManagerApi,
PackageFile, PackageFile,
RangeConfig, RangeConfig,
@ -47,6 +48,18 @@ export const getLanguageList = (): string[] => languageList;
export const getManagerList = (): string[] => managerList; export const getManagerList = (): string[] => managerList;
export const getManagers = (): Map<string, ManagerApi> => managers; export const getManagers = (): Map<string, ManagerApi> => managers;
export async function detectAllGlobalConfig(): Promise<GlobalManagerConfig> {
let config: GlobalManagerConfig = {};
for (const managerName of managerList) {
const manager = managers.get(managerName);
if (manager.detectGlobalConfig) {
// This should use mergeChildConfig once more than one manager is supported, but introduces a cyclic dependency
config = { ...config, ...(await manager.detectGlobalConfig()) };
}
}
return config;
}
export async function extractAllPackageFiles( export async function extractAllPackageFiles(
manager: string, manager: string,
config: ExtractConfig, config: ExtractConfig,

View file

@ -0,0 +1,29 @@
import { fs } from '../../../test/util';
import { detectGlobalConfig } from './detect';
jest.mock('../../util/fs');
describe('manager/npm/detect', () => {
describe('.detectGlobalConfig()', () => {
it('detects .npmrc in home directory', async () => {
fs.readFile.mockResolvedValueOnce(
'registry=https://registry.npmjs.org\n'
);
const res = await detectGlobalConfig();
expect(res).toMatchInlineSnapshot(`
Object {
"npmrc": "registry=https://registry.npmjs.org
",
"npmrcMerge": true,
}
`);
expect(res.npmrc).toBeDefined();
expect(res.npmrcMerge).toBe(true);
});
it('handles no .npmrc', async () => {
fs.readFile.mockImplementationOnce(() => Promise.reject());
const res = await detectGlobalConfig();
expect(res).toEqual({});
});
});
});

23
lib/manager/npm/detect.ts Normal file
View file

@ -0,0 +1,23 @@
import os from 'os';
import is from '@sindresorhus/is';
import { join } from 'upath';
import { logger } from '../../logger';
import { readFile } from '../../util/fs';
import { GlobalManagerConfig } from '../types';
export async function detectGlobalConfig(): Promise<GlobalManagerConfig> {
const res: GlobalManagerConfig = {};
const homedir = os.homedir();
const npmrcFileName = join(homedir, '.npmrc');
try {
const npmrc = await readFile(npmrcFileName, 'utf8');
if (is.nonEmptyString(npmrc)) {
res.npmrc = npmrc;
res.npmrcMerge = true;
logger.debug(`Detected ${npmrcFileName} and adding it to global config`);
}
} catch (err) {
logger.warn({ npmrcFileName }, 'Error reading .npmrc file');
}
return res;
}

View file

@ -1,6 +1,7 @@
import { LANGUAGE_JAVASCRIPT } from '../../constants/languages'; import { LANGUAGE_JAVASCRIPT } from '../../constants/languages';
import * as npmVersioning from '../../versioning/npm'; import * as npmVersioning from '../../versioning/npm';
export { detectGlobalConfig } from './detect';
export { extractAllPackageFiles } from './extract'; export { extractAllPackageFiles } from './extract';
export { export {
bumpPackageVersion, bumpPackageVersion,

View file

@ -220,6 +220,11 @@ export interface UpdateLockedConfig {
newVersion?: string; newVersion?: string;
} }
export interface GlobalManagerConfig {
npmrc?: string;
npmrcMerge?: boolean;
}
export interface ManagerApi { export interface ManagerApi {
defaultConfig: Record<string, unknown>; defaultConfig: Record<string, unknown>;
language?: string; language?: string;
@ -231,6 +236,8 @@ export interface ManagerApi {
bumpVersion: ReleaseType | string bumpVersion: ReleaseType | string
): Result<BumpPackageVersionResult>; ): Result<BumpPackageVersionResult>;
detectGlobalConfig?(): Result<GlobalManagerConfig>;
extractAllPackageFiles?( extractAllPackageFiles?(
config: ExtractConfig, config: ExtractConfig,
files: string[] files: string[]

View file

@ -3,6 +3,8 @@ import { readFile } from '../../../../util/fs';
import getArgv from './__fixtures__/argv'; import getArgv from './__fixtures__/argv';
jest.mock('../../../../datasource/npm'); jest.mock('../../../../datasource/npm');
jest.mock('../../../../util/fs');
try { try {
jest.mock('../../config.js'); jest.mock('../../config.js');
} catch (err) { } catch (err) {
@ -118,5 +120,10 @@ describe('workers/global/config/parse/index', () => {
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed.endpoint).toEqual('https://github.renovatebot.com/api/v3/'); expect(parsed.endpoint).toEqual('https://github.renovatebot.com/api/v3/');
}); });
it('parses global manager config', async () => {
defaultArgv = defaultArgv.concat(['--detect-global-manager-config=true']);
const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv);
expect(parsed.npmrc).toBeNull();
});
}); });
}); });

View file

@ -2,6 +2,7 @@ import * as defaultsParser from '../../../../config/defaults';
import { AllConfig } from '../../../../config/types'; import { AllConfig } from '../../../../config/types';
import { mergeChildConfig } from '../../../../config/utils'; import { mergeChildConfig } from '../../../../config/utils';
import { addStream, logger, setContext } from '../../../../logger'; import { addStream, logger, setContext } from '../../../../logger';
import { detectAllGlobalConfig } from '../../../../manager';
import { ensureDir, getSubDirectory, readFile } from '../../../../util/fs'; import { ensureDir, getSubDirectory, readFile } from '../../../../util/fs';
import { ensureTrailingSlash } from '../../../../util/url'; import { ensureTrailingSlash } from '../../../../util/url';
import * as cliParser from './cli'; import * as cliParser from './cli';
@ -73,6 +74,13 @@ export async function parseConfigs(
logger.debug({ config: envConfig }, 'Env config'); logger.debug({ config: envConfig }, 'Env config');
logger.debug({ config: combinedConfig }, 'Combined config'); logger.debug({ config: combinedConfig }, 'Combined config');
if (config.detectGlobalManagerConfig) {
logger.debug('Detecting global manager config');
const globalManagerConfig = await detectAllGlobalConfig();
logger.debug({ config: globalManagerConfig }, 'Global manager config');
config = mergeChildConfig(config, globalManagerConfig);
}
// Get global config // Get global config
logger.trace({ config }, 'Full config'); logger.trace({ config }, 'Full config');